summaryrefslogtreecommitdiffstats
path: root/src/preproc/eqn
diff options
context:
space:
mode:
Diffstat (limited to 'src/preproc/eqn')
-rw-r--r--src/preproc/eqn/TODO49
-rw-r--r--src/preproc/eqn/box.cpp651
-rw-r--r--src/preproc/eqn/box.h278
-rw-r--r--src/preproc/eqn/delim.cpp418
-rw-r--r--src/preproc/eqn/eqn.1.man2240
-rw-r--r--src/preproc/eqn/eqn.am79
-rw-r--r--src/preproc/eqn/eqn.cpp2112
-rw-r--r--src/preproc/eqn/eqn.h57
-rw-r--r--src/preproc/eqn/eqn.hpp210
-rw-r--r--src/preproc/eqn/eqn.ypp333
-rw-r--r--src/preproc/eqn/lex.cpp1236
-rw-r--r--src/preproc/eqn/limit.cpp217
-rw-r--r--src/preproc/eqn/list.cpp241
-rw-r--r--src/preproc/eqn/main.cpp485
-rw-r--r--src/preproc/eqn/mark.cpp120
-rw-r--r--src/preproc/eqn/neqn.1.man92
-rw-r--r--src/preproc/eqn/neqn.sh26
-rw-r--r--src/preproc/eqn/other.cpp706
-rw-r--r--src/preproc/eqn/over.cpp204
-rw-r--r--src/preproc/eqn/pbox.h140
-rw-r--r--src/preproc/eqn/pile.cpp354
-rw-r--r--src/preproc/eqn/script.cpp250
-rw-r--r--src/preproc/eqn/special.cpp117
-rw-r--r--src/preproc/eqn/sqrt.cpp186
-rwxr-xr-xsrc/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh55
-rw-r--r--src/preproc/eqn/text.cpp957
26 files changed, 11813 insertions, 0 deletions
diff --git a/src/preproc/eqn/TODO b/src/preproc/eqn/TODO
new file mode 100644
index 0000000..2a22183
--- /dev/null
+++ b/src/preproc/eqn/TODO
@@ -0,0 +1,49 @@
+Use the same size increases for sum prod int as eqn does.
+
+Perhaps chartype should be renamed.
+
+TeX makes {sub,super}script on a single character with an accent
+into an accent onto the (character with the script). Should we do this?
+
+Implement mark and lineups within scripts, matrices and piles, and accents.
+(Why would this be useful?)
+
+Perhaps push hmotions down through lists to avoid upsetting spacing
+adjustments.
+
+Possibly generate .lf commands during compute_metrics phase.
+
+Consider whether there should be extra space at the side of piles.
+
+Provide scriptstyle displaystyle etc.
+
+Provide a nicer matrix syntax, e.g.,
+matrix ccc {
+a then b then c above
+e then f then g above
+h then i then k
+}
+
+Perhaps generate syntax error messages using the style of gpic.
+
+Wide accents.
+
+More use of \Z.
+
+Extensible square roots.
+
+Vphantom
+
+Smash.
+
+Provide a variant of vec that extends over the length of the accentee.
+
+Support vertical arrow delimiters.
+
+Make the following work:
+.EQ
+delim @@
+.EN
+.EQ @<-@
+some equation
+.EN
diff --git a/src/preproc/eqn/box.cpp b/src/preproc/eqn/box.cpp
new file mode 100644
index 0000000..5f6343e
--- /dev/null
+++ b/src/preproc/eqn/box.cpp
@@ -0,0 +1,651 @@
+/* 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 <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+const char *current_roman_font;
+
+char *gfont = 0;
+char *grfont = 0;
+char *gbfont = 0;
+int gsize = 0;
+
+int script_size_reduction = -1; // negative means reduce by a percentage
+
+int positive_space = -1;
+int negative_space = -1;
+
+int minimum_size = 5;
+
+int fat_offset = 4;
+int body_height = 85;
+int body_depth = 35;
+
+int over_hang = 0;
+int accent_width = 31;
+int delimiter_factor = 900;
+int delimiter_shortfall = 50;
+
+int null_delimiter_space = 12;
+int script_space = 5;
+int thin_space = 17;
+int medium_space = 22;
+int thick_space = 28;
+
+int num1 = 70;
+int num2 = 40;
+// we don't use num3, because we don't have \atop
+int denom1 = 70;
+int denom2 = 36;
+int axis_height = 26; // in 100ths of an em
+int sup1 = 42;
+int sup2 = 37;
+int sup3 = 28;
+int default_rule_thickness = 4;
+int sub1 = 20;
+int sub2 = 23;
+int sup_drop = 38;
+int sub_drop = 5;
+int x_height = 45;
+int big_op_spacing1 = 11;
+int big_op_spacing2 = 17;
+int big_op_spacing3 = 20;
+int big_op_spacing4 = 60;
+int big_op_spacing5 = 10;
+
+// These are for piles and matrices.
+
+int baseline_sep = 140; // = num1 + denom1
+int shift_down = 26; // = axis_height
+int column_sep = 100; // = em space
+int matrix_side_sep = 17; // = thin space
+
+int nroff = 0; // should we grok ndefine or tdefine?
+
+struct S {
+ const char *name;
+ int *ptr;
+} param_table[] = {
+ { "fat_offset", &fat_offset },
+ { "over_hang", &over_hang },
+ { "accent_width", &accent_width },
+ { "delimiter_factor", &delimiter_factor },
+ { "delimiter_shortfall", &delimiter_shortfall },
+ { "null_delimiter_space", &null_delimiter_space },
+ { "script_space", &script_space },
+ { "thin_space", &thin_space },
+ { "medium_space", &medium_space },
+ { "thick_space", &thick_space },
+ { "num1", &num1 },
+ { "num2", &num2 },
+ { "denom1", &denom1 },
+ { "denom2", &denom2 },
+ { "axis_height", &axis_height },
+ { "sup1", &sup1 },
+ { "sup2", &sup2 },
+ { "sup3", &sup3 },
+ { "default_rule_thickness", &default_rule_thickness },
+ { "sub1", &sub1 },
+ { "sub2", &sub2 },
+ { "sup_drop", &sup_drop },
+ { "sub_drop", &sub_drop },
+ { "x_height", &x_height },
+ { "big_op_spacing1", &big_op_spacing1 },
+ { "big_op_spacing2", &big_op_spacing2 },
+ { "big_op_spacing3", &big_op_spacing3 },
+ { "big_op_spacing4", &big_op_spacing4 },
+ { "big_op_spacing5", &big_op_spacing5 },
+ { "minimum_size", &minimum_size },
+ { "baseline_sep", &baseline_sep },
+ { "shift_down", &shift_down },
+ { "column_sep", &column_sep },
+ { "matrix_side_sep", &matrix_side_sep },
+ { "draw_lines", &draw_flag },
+ { "body_height", &body_height },
+ { "body_depth", &body_depth },
+ { "nroff", &nroff },
+ { 0, 0 }
+};
+
+void set_param(const char *name, int value)
+{
+ for (int i = 0; param_table[i].name != 0; i++)
+ if (strcmp(param_table[i].name, name) == 0) {
+ *param_table[i].ptr = value;
+ return;
+ }
+ error("unrecognised parameter '%1'", name);
+}
+
+int script_style(int style)
+{
+ return style > SCRIPT_STYLE ? style - 2 : style;
+}
+
+int cramped_style(int style)
+{
+ return (style & 1) ? style - 1 : style;
+}
+
+void set_space(int n)
+{
+ if (n < 0)
+ negative_space = -n;
+ else
+ positive_space = n;
+}
+
+// Return 0 if the specified size is bad.
+// The caller is responsible for giving the error message.
+
+int set_gsize(const char *s)
+{
+ const char *p = (*s == '+' || *s == '-') ? s + 1 : s;
+ char *end;
+ long n = strtol(p, &end, 10);
+ if (n <= 0 || *end != '\0' || n > INT_MAX)
+ return 0;
+ if (p > s) {
+ if (!gsize)
+ gsize = 10;
+ if (*s == '+') {
+ if (gsize > INT_MAX - n)
+ return 0;
+ gsize += int(n);
+ }
+ else {
+ if (gsize - n <= 0)
+ return 0;
+ gsize -= int(n);
+ }
+ }
+ else
+ gsize = int(n);
+ return 1;
+}
+
+void set_script_reduction(int n)
+{
+ script_size_reduction = n;
+}
+
+const char *get_gfont()
+{
+ return gfont ? gfont : "I";
+}
+
+const char *get_grfont()
+{
+ return grfont ? grfont : "R";
+}
+
+const char *get_gbfont()
+{
+ return gbfont ? gbfont : "B";
+}
+
+void set_gfont(const char *s)
+{
+ delete[] gfont;
+ gfont = strsave(s);
+}
+
+void set_grfont(const char *s)
+{
+ delete[] grfont;
+ grfont = strsave(s);
+}
+
+void set_gbfont(const char *s)
+{
+ delete[] gbfont;
+ gbfont = strsave(s);
+}
+
+// this must be precisely 2 characters in length
+#define COMPATIBLE_REG "0C"
+
+void start_string()
+{
+ if (output_format == troff) {
+ printf(".nr " COMPATIBLE_REG " \\n(.C\n");
+ printf(".cp 0\n");
+ printf(".ds " LINE_STRING "\n");
+ }
+}
+
+void output_string()
+{
+ if (output_format == troff)
+ printf("\\*(" LINE_STRING "\n");
+ else if (output_format == mathml && !xhtml)
+ putchar('\n');
+}
+
+void restore_compatibility()
+{
+ if (output_format == troff)
+ printf(".cp \\n(" COMPATIBLE_REG "\n");
+}
+
+void do_text(const char *s)
+{
+ if (output_format == troff) {
+ printf(".eo\n");
+ printf(".as " LINE_STRING " \"%s\n", s);
+ printf(".ec\n");
+ }
+ else if (output_format == mathml) {
+ fputs(s, stdout);
+ if (xhtml && strlen(s) > 0)
+ printf("\n");
+ }
+}
+
+void set_minimum_size(int n)
+{
+ minimum_size = n;
+}
+
+void set_script_size()
+{
+ if (minimum_size < 0)
+ minimum_size = 0;
+ if (script_size_reduction >= 0)
+ printf(".ps \\n[.s]-%d>?%d\n", script_size_reduction, minimum_size);
+ else
+ printf(".ps (u;\\n[.ps]*7+5/10>?%dz)\n", minimum_size);
+}
+
+int box::next_uid = 0;
+
+box::box() : spacing_type(ORDINARY_TYPE), uid(next_uid++)
+{
+}
+
+box::~box()
+{
+}
+
+void box::top_level()
+{
+ box *b = this;
+ if (output_format == troff) {
+ // debug_print();
+ // putc('\n', stderr);
+ printf(".nr " SAVED_FONT_REG " \\n[.f]\n");
+ printf(".ft\n");
+ printf(".nr " SAVED_PREV_FONT_REG " \\n[.f]\n");
+ printf(".ft %s\n", get_gfont());
+ printf(".nr " SAVED_SIZE_REG " \\n[.ps]\n");
+ if (gsize > 0) {
+ char buf[INT_DIGITS + 1];
+ sprintf(buf, "%d", gsize);
+ b = new size_box(strsave(buf), b);
+ }
+ current_roman_font = get_grfont();
+ // This catches tabs used within \Z (which aren't allowed).
+ b->check_tabs(0);
+ int r = b->compute_metrics(DISPLAY_STYLE);
+ printf(".ft \\n[" SAVED_PREV_FONT_REG "]\n");
+ printf(".ft \\n[" SAVED_FONT_REG "]\n");
+ printf(".nr " MARK_OR_LINEUP_FLAG_REG " %d\n", r);
+ if (r == FOUND_MARK) {
+ printf(".nr " SAVED_MARK_REG " \\n[" MARK_REG "]\n");
+ printf(".nr " MARK_WIDTH_REG " 0\\n[" WIDTH_FORMAT "]\n", b->uid);
+ }
+ else if (r == FOUND_LINEUP)
+ printf(".if r" SAVED_MARK_REG " .as1 " LINE_STRING " \\h'\\n["
+ SAVED_MARK_REG "]u-\\n[" MARK_REG "]u'\n");
+ else
+ assert(r == FOUND_NOTHING);
+ // If we use \R directly, the space will prevent it working in a
+ // macro argument; so we hide it in a string instead.
+ printf(".ds " SAVE_FONT_STRING " "
+ "\\R'" SAVED_INLINE_FONT_REG " \\En[.f]'"
+ "\\fP"
+ "\\R'" SAVED_INLINE_PREV_FONT_REG " \\En[.f]'"
+ "\\R'" SAVED_INLINE_SIZE_REG " \\En[.ps]'"
+ "\\s0"
+ "\\R'" SAVED_INLINE_PREV_SIZE_REG " \\En[.ps]'"
+ "\n"
+ ".ds " RESTORE_FONT_STRING " "
+ "\\f[\\En[" SAVED_INLINE_PREV_FONT_REG "]]"
+ "\\f[\\En[" SAVED_INLINE_FONT_REG "]]"
+ "\\s'\\En[" SAVED_INLINE_PREV_SIZE_REG "]u'"
+ "\\s'\\En[" SAVED_INLINE_SIZE_REG "]u'"
+ "\n");
+ printf(".as1 " LINE_STRING " \\&\\E*[" SAVE_FONT_STRING "]");
+ printf("\\f[%s]", get_gfont());
+ printf("\\s'\\En[" SAVED_SIZE_REG "]u'");
+ current_roman_font = get_grfont();
+ b->output();
+ printf("\\E*[" RESTORE_FONT_STRING "]\n");
+ if (r == FOUND_LINEUP)
+ printf(".if r" SAVED_MARK_REG " .as1 " LINE_STRING " \\h'\\n["
+ MARK_WIDTH_REG "]u-\\n[" SAVED_MARK_REG "]u-(\\n["
+ WIDTH_FORMAT "]u-\\n[" MARK_REG "]u)'\n",
+ b->uid);
+ b->extra_space();
+ if (!inline_flag)
+ printf(".ne \\n[" HEIGHT_FORMAT "]u-%dM>?0+(\\n["
+ DEPTH_FORMAT "]u-%dM>?0)\n",
+ b->uid, body_height, b->uid, body_depth);
+ }
+ else if (output_format == mathml) {
+ if (xhtml)
+ printf(".MATHML ");
+ printf("<math>");
+ b->output();
+ printf("</math>");
+ }
+ delete b;
+ next_uid = 0;
+}
+
+// gpic defines this register so as to make geqn not produce '\x's
+#define EQN_NO_EXTRA_SPACE_REG "0x"
+
+void box::extra_space()
+{
+ printf(".if !r" EQN_NO_EXTRA_SPACE_REG " "
+ ".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
+ if (positive_space >= 0 || negative_space >= 0) {
+ if (positive_space > 0)
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".as1 " LINE_STRING " \\x'-%dM'\n", positive_space);
+ if (negative_space > 0)
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".as1 " LINE_STRING " \\x'%dM'\n", negative_space);
+ positive_space = negative_space = -1;
+ }
+ else {
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".if \\n[" HEIGHT_FORMAT "]>%dM .as1 " LINE_STRING
+ " \\x'-(\\n[" HEIGHT_FORMAT
+ "]u-%dM)'\n",
+ uid, body_height, uid, body_height);
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".if \\n[" DEPTH_FORMAT "]>%dM .as1 " LINE_STRING
+ " \\x'\\n[" DEPTH_FORMAT
+ "]u-%dM'\n",
+ uid, body_depth, uid, body_depth);
+ }
+}
+
+int box::compute_metrics(int)
+{
+ printf(".nr " WIDTH_FORMAT " 0\n", uid);
+ printf(".nr " HEIGHT_FORMAT " 0\n", uid);
+ printf(".nr " DEPTH_FORMAT " 0\n", uid);
+ return FOUND_NOTHING;
+}
+
+void box::compute_subscript_kern()
+{
+ printf(".nr " SUB_KERN_FORMAT " 0\n", uid);
+}
+
+void box::compute_skew()
+{
+ printf(".nr " SKEW_FORMAT " 0\n", uid);
+}
+
+void box::output()
+{
+}
+
+void box::check_tabs(int)
+{
+}
+
+int box::is_char()
+{
+ return 0;
+}
+
+int box::left_is_italic()
+{
+ return 0;
+}
+
+int box::right_is_italic()
+{
+ return 0;
+}
+
+void box::hint(unsigned)
+{
+}
+
+void box::handle_char_type(int, int)
+{
+}
+
+
+box_list::box_list(box *pp)
+{
+ p = new box*[10];
+ for (int i = 0; i < 10; i++)
+ p[i] = 0;
+ maxlen = 10;
+ len = 1;
+ p[0] = pp;
+}
+
+void box_list::append(box *pp)
+{
+ if (len + 1 > maxlen) {
+ box **oldp = p;
+ maxlen *= 2;
+ p = new box*[maxlen];
+ memcpy(p, oldp, sizeof(box*)*len);
+ delete[] oldp;
+ }
+ p[len++] = pp;
+}
+
+box_list::~box_list()
+{
+ for (int i = 0; i < len; i++)
+ delete p[i];
+ delete[] p;
+}
+
+void box_list::list_check_tabs(int level)
+{
+ for (int i = 0; i < len; i++)
+ p[i]->check_tabs(level);
+}
+
+
+pointer_box::pointer_box(box *pp) : p(pp)
+{
+ spacing_type = p->spacing_type;
+}
+
+pointer_box::~pointer_box()
+{
+ delete p;
+}
+
+int pointer_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void pointer_box::compute_subscript_kern()
+{
+ p->compute_subscript_kern();
+ printf(".nr " SUB_KERN_FORMAT " \\n[" SUB_KERN_FORMAT "]\n", uid, p->uid);
+}
+
+void pointer_box::compute_skew()
+{
+ p->compute_skew();
+ printf(".nr " SKEW_FORMAT " 0\\n[" SKEW_FORMAT "]\n",
+ uid, p->uid);
+}
+
+void pointer_box::check_tabs(int level)
+{
+ p->check_tabs(level);
+}
+
+int simple_box::compute_metrics(int)
+{
+ printf(".nr " WIDTH_FORMAT " 0\\w" DELIMITER_CHAR, uid);
+ output();
+ printf(DELIMITER_CHAR "\n");
+ printf(".nr " HEIGHT_FORMAT " 0>?\\n[rst]\n", uid);
+ printf(".nr " DEPTH_FORMAT " 0-\\n[rsb]>?0\n", uid);
+ printf(".nr " SUB_KERN_FORMAT " 0-\\n[ssc]>?0\n", uid);
+ printf(".nr " SKEW_FORMAT " 0\\n[skw]\n", uid);
+ return FOUND_NOTHING;
+}
+
+void simple_box::compute_subscript_kern()
+{
+ // do nothing, we already computed it in do_metrics
+}
+
+void simple_box::compute_skew()
+{
+ // do nothing, we already computed it in do_metrics
+}
+
+int box::is_simple()
+{
+ return 0;
+}
+
+int simple_box::is_simple()
+{
+ return 1;
+}
+
+quoted_text_box::quoted_text_box(char *s) : text(s)
+{
+}
+
+quoted_text_box::~quoted_text_box()
+{
+ free(text);
+}
+
+void quoted_text_box::output()
+{
+ if (text) {
+ if (output_format == troff)
+ fputs(text, stdout);
+ else if (output_format == mathml) {
+ fputs("<mtext>", stdout);
+ fputs(text, stdout);
+ fputs("</mtext>", stdout);
+ }
+ }
+}
+
+tab_box::tab_box() : disabled(0)
+{
+}
+
+// We treat a tab_box as having width 0 for width computations.
+
+void tab_box::output()
+{
+ if (!disabled)
+ printf("\\t");
+}
+
+void tab_box::check_tabs(int level)
+{
+ if (level > 0) {
+ error("tabs allowed only at outermost level");
+ disabled = 1;
+ }
+}
+
+space_box::space_box()
+{
+ spacing_type = SUPPRESS_TYPE;
+}
+
+void space_box::output()
+{
+ if (output_format == troff)
+ printf("\\h'%dM'", thick_space);
+ else if (output_format == mathml)
+ // &ThickSpace; doesn't display right under Firefox 1.5.
+ printf("<mtext>&ensp;</mtext>");
+}
+
+half_space_box::half_space_box()
+{
+ spacing_type = SUPPRESS_TYPE;
+}
+
+void half_space_box::output()
+{
+ if (output_format == troff)
+ printf("\\h'%dM'", thin_space);
+ else if (output_format == mathml)
+ printf("<mtext>&ThinSpace;</mtext>");
+}
+
+void box_list::list_debug_print(const char *sep)
+{
+ p[0]->debug_print();
+ for (int i = 1; i < len; i++) {
+ fprintf(stderr, "%s", sep);
+ p[i]->debug_print();
+ }
+}
+
+void quoted_text_box::debug_print()
+{
+ fprintf(stderr, "\"%s\"", (text ? text : ""));
+}
+
+void half_space_box::debug_print()
+{
+ fprintf(stderr, "^");
+}
+
+void space_box::debug_print()
+{
+ fprintf(stderr, "~");
+}
+
+void tab_box::debug_print()
+{
+ fprintf(stderr, "<tab>");
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/box.h b/src/preproc/eqn/box.h
new file mode 100644
index 0000000..981b464
--- /dev/null
+++ b/src/preproc/eqn/box.h
@@ -0,0 +1,278 @@
+// -*- C++ -*-
+/* 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/>. */
+
+class list_box;
+
+class box {
+private:
+ static int next_uid;
+public:
+ int spacing_type;
+ const int uid;
+ box();
+ virtual void debug_print() = 0;
+ virtual ~box();
+ void top_level();
+ virtual int compute_metrics(int);
+ virtual void compute_subscript_kern();
+ virtual void compute_skew();
+ virtual void output();
+ void extra_space();
+ virtual list_box *to_list_box();
+ virtual int is_simple();
+ virtual int is_char();
+ virtual int left_is_italic();
+ virtual int right_is_italic();
+ virtual void handle_char_type(int, int);
+ enum { FOUND_NOTHING = 0, FOUND_MARK = 1, FOUND_LINEUP = 2 };
+ void set_spacing_type(char *type);
+ virtual void hint(unsigned);
+ virtual void check_tabs(int);
+};
+
+class box_list {
+private:
+ int maxlen;
+public:
+ box **p;
+ int len;
+
+ box_list(box *);
+ ~box_list();
+ void append(box *);
+ void list_check_tabs(int);
+ void list_debug_print(const char *sep);
+ friend class list_box;
+};
+
+// declarations to avoid friend name injection problems
+box *make_script_box(box *, box *, box *);
+box *make_mark_box(box *);
+box *make_lineup_box(box *);
+
+class list_box : public box {
+ int is_script;
+ box_list list;
+ int sty;
+public:
+ list_box(box *);
+ void debug_print();
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void output();
+ void check_tabs(int);
+ void append(box *);
+ list_box *to_list_box();
+ void handle_char_type(int, int);
+ void compute_sublist_width(int n);
+ friend box *make_script_box(box *, box *, box *);
+ friend box *make_mark_box(box *);
+ friend box *make_lineup_box(box *);
+};
+
+enum alignment { LEFT_ALIGN, RIGHT_ALIGN, CENTER_ALIGN };
+
+class column : public box_list {
+ alignment align;
+ int space;
+public:
+ column(box *);
+ void set_alignment(alignment);
+ void set_space(int);
+ void debug_print(const char *);
+
+ friend class matrix_box;
+ friend class pile_box;
+};
+
+class pile_box : public box {
+ column col;
+public:
+ pile_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+ void set_alignment(alignment a) { col.set_alignment(a); }
+ void set_space(int n) { col.set_space(n); }
+ void append(box *p) { col.append(p); }
+};
+
+class matrix_box : public box {
+private:
+ int len;
+ int maxlen;
+ column **p;
+public:
+ matrix_box(column *);
+ ~matrix_box();
+ void append(column *);
+ int compute_metrics(int);
+ void output();
+ void check_tabs(int);
+ void debug_print();
+};
+
+class pointer_box : public box {
+protected:
+ box *p;
+public:
+ pointer_box(box *);
+ ~pointer_box();
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void compute_skew();
+ void debug_print() = 0;
+ void check_tabs(int);
+};
+
+class vcenter_box : public pointer_box {
+public:
+ vcenter_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class simple_box : public box {
+public:
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void compute_skew();
+ void output() = 0;
+ void debug_print() = 0;
+ int is_simple();
+};
+
+class quoted_text_box : public simple_box {
+ char *text;
+public:
+ quoted_text_box(char *);
+ ~quoted_text_box();
+ void debug_print();
+ void output();
+};
+
+class half_space_box : public simple_box {
+public:
+ half_space_box();
+ void output();
+ void debug_print();
+};
+
+class space_box : public simple_box {
+public:
+ space_box();
+ void output();
+ void debug_print();
+};
+
+class tab_box : public box {
+ int disabled;
+public:
+ tab_box();
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+class size_box : public pointer_box {
+private:
+ char *size;
+public:
+ size_box(char *, box *);
+ ~size_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class font_box : public pointer_box {
+private:
+ char *f;
+public:
+ font_box(char *, box *);
+ ~font_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class fat_box : public pointer_box {
+public:
+ fat_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class vmotion_box : public pointer_box {
+private:
+ int n; // up is >= 0
+public:
+ vmotion_box(int, box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class hmotion_box : public pointer_box {
+ int n;
+public:
+ hmotion_box(int, box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+box *split_text(char *);
+box *make_delim_box(char *, box *, char *);
+box *make_sqrt_box(box *);
+box *make_prime_box(box *);
+box *make_over_box(box *, box *);
+box *make_small_over_box(box *, box *);
+box *make_limit_box(box *, box *, box *);
+box *make_accent_box(box *, box *);
+box *make_uaccent_box(box *, box *);
+box *make_overline_box(box *);
+box *make_underline_box(box *);
+box *make_special_box(char *, box *);
+
+void set_space(int);
+int set_gsize(const char *);
+void set_gfont(const char *);
+void set_grfont(const char *);
+void set_gbfont(const char *);
+const char *get_gfont();
+const char *get_grfont();
+const char *get_gbfont();
+void start_string();
+void output_string();
+void do_text(const char *);
+void restore_compatibility();
+void set_script_reduction(int n);
+void set_minimum_size(int n);
+void set_param(const char *name, int value);
+
+void set_char_type(const char *type, char *ch);
+
+void init_char_table();
+void init_extensible();
+void define_extensible(const char *name, const char *ext, const char *top = 0,
+ const char *mid = 0, const char *bot = 0);
diff --git a/src/preproc/eqn/delim.cpp b/src/preproc/eqn/delim.cpp
new file mode 100644
index 0000000..01557c5
--- /dev/null
+++ b/src/preproc/eqn/delim.cpp
@@ -0,0 +1,418 @@
+/* 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 <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+enum left_or_right_t { LEFT_DELIM = 01, RIGHT_DELIM = 02 };
+
+// Small must be none-zero and must exist in each device.
+// Small will be put in the roman font, others are assumed to be
+// on the special font (so no font change will be necessary.)
+
+struct delimiter {
+ const char *name;
+ int flags;
+ const char *small;
+ const char *chain_format;
+ const char *ext;
+ const char *top;
+ const char *mid;
+ const char *bot;
+} delim_table[] = {
+ {
+ "(", LEFT_DELIM|RIGHT_DELIM, "(", "\\[parenleft%s]",
+ "\\[parenleftex]",
+ "\\[parenlefttp]",
+ 0,
+ "\\[parenleftbt]",
+ },
+ {
+ ")", LEFT_DELIM|RIGHT_DELIM, ")", "\\[parenright%s]",
+ "\\[parenrightex]",
+ "\\[parenrighttp]",
+ 0,
+ "\\[parenrightbt]",
+ },
+ {
+ "[", LEFT_DELIM|RIGHT_DELIM, "[", "\\[bracketleft%s]",
+ "\\[bracketleftex]",
+ "\\[bracketlefttp]",
+ 0,
+ "\\[bracketleftbt]",
+ },
+ {
+ "]", LEFT_DELIM|RIGHT_DELIM, "]", "\\[bracketright%s]",
+ "\\[bracketrightex]",
+ "\\[bracketrighttp]",
+ 0,
+ "\\[bracketrightbt]",
+ },
+ {
+ "{", LEFT_DELIM|RIGHT_DELIM, "{", "\\[braceleft%s]",
+ "\\[braceleftex]",
+ "\\[bracelefttp]",
+ "\\[braceleftmid]",
+ "\\[braceleftbt]",
+ },
+ {
+ "}", LEFT_DELIM|RIGHT_DELIM, "}", "\\[braceright%s]",
+ "\\[bracerightex]",
+ "\\[bracerighttp]",
+ "\\[bracerightmid]",
+ "\\[bracerightbt]",
+ },
+ {
+ "|", LEFT_DELIM|RIGHT_DELIM, "|", "\\[bar%s]",
+ "\\[barex]",
+ 0,
+ 0,
+ 0,
+ },
+ {
+ "floor", LEFT_DELIM, "\\(lf", "\\[floorleft%s]",
+ "\\[bracketleftex]",
+ 0,
+ 0,
+ "\\[bracketleftbt]",
+ },
+ {
+ "floor", RIGHT_DELIM, "\\(rf", "\\[floorright%s]",
+ "\\[bracketrightex]",
+ 0,
+ 0,
+ "\\[bracketrightbt]",
+ },
+ {
+ "ceiling", LEFT_DELIM, "\\(lc", "\\[ceilingleft%s]",
+ "\\[bracketleftex]",
+ "\\[bracketlefttp]",
+ 0,
+ 0,
+ },
+ {
+ "ceiling", RIGHT_DELIM, "\\(rc", "\\[ceilingright%s]",
+ "\\[bracketrightex]",
+ "\\[bracketrighttp]",
+ 0,
+ 0,
+ },
+ {
+ "||", LEFT_DELIM|RIGHT_DELIM, "|", "\\[bar%s]",
+ "\\[bardblex]",
+ 0,
+ 0,
+ 0,
+ },
+ {
+ "<", LEFT_DELIM|RIGHT_DELIM, "\\(la", "\\[angleleft%s]",
+ 0,
+ 0,
+ 0,
+ 0,
+ },
+ {
+ ">", LEFT_DELIM|RIGHT_DELIM, "\\(ra", "\\[angleright%s]",
+ 0,
+ 0,
+ 0,
+ 0,
+ },
+ {
+ "uparrow", LEFT_DELIM|RIGHT_DELIM, "\\(ua", "\\[arrowup%s]",
+ "\\[arrowvertex]",
+ "\\[arrowverttp]",
+ 0,
+ 0,
+ },
+ {
+ "downarrow", LEFT_DELIM|RIGHT_DELIM, "\\(da", "\\[arrowdown%s]",
+ "\\[arrowvertex]",
+ 0,
+ 0,
+ "\\[arrowvertbt]",
+ },
+ {
+ "updownarrow", LEFT_DELIM|RIGHT_DELIM, "\\(va", "\\[arrowupdown%s]",
+ "\\[arrowvertex]",
+ "\\[arrowverttp]",
+ 0,
+ "\\[arrowvertbt]",
+ },
+};
+
+const int DELIM_TABLE_SIZE = int(sizeof(delim_table)/sizeof(delim_table[0]));
+
+class delim_box : public box {
+private:
+ char *left;
+ char *right;
+ box *p;
+public:
+ delim_box(char *, box *, char *);
+ ~delim_box();
+ int compute_metrics(int);
+ void output();
+ void check_tabs(int);
+ void debug_print();
+};
+
+box *make_delim_box(char *l, box *pp, char *r)
+{
+ if (l != 0 && *l == '\0') {
+ delete[] l;
+ l = 0;
+ }
+ if (r != 0 && *r == '\0') {
+ delete[] r;
+ r = 0;
+ }
+ return new delim_box(l, pp, r);
+}
+
+delim_box::delim_box(char *l, box *pp, char *r)
+: left(l), right(r), p(pp)
+{
+}
+
+delim_box::~delim_box()
+{
+ delete[] left;
+ delete[] right;
+ delete p;
+}
+
+static void build_extensible(const char *ext, const char *top, const char *mid,
+ const char *bot)
+{
+ assert(ext != 0);
+ printf(".nr " DELIM_WIDTH_REG " 0\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ ext);
+ printf(".nr " EXT_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " EXT_DEPTH_REG " 0-\\n[rsb]\n");
+ if (top) {
+ printf(".nr " DELIM_WIDTH_REG " 0\\n[" DELIM_WIDTH_REG "]"
+ ">?\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ top);
+ printf(".nr " TOP_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " TOP_DEPTH_REG " 0-\\n[rsb]\n");
+ }
+ if (mid) {
+ printf(".nr " DELIM_WIDTH_REG " 0\\n[" DELIM_WIDTH_REG "]"
+ ">?\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ mid);
+ printf(".nr " MID_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " MID_DEPTH_REG " 0-\\n[rsb]\n");
+ }
+ if (bot) {
+ printf(".nr " DELIM_WIDTH_REG " 0\\n[" DELIM_WIDTH_REG "]"
+ ">?\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ bot);
+ printf(".nr " BOT_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " BOT_DEPTH_REG " 0-\\n[rsb]\n");
+ }
+ printf(".nr " TOTAL_HEIGHT_REG " 0");
+ if (top)
+ printf("+\\n[" TOP_HEIGHT_REG "]+\\n[" TOP_DEPTH_REG "]");
+ if (bot)
+ printf("+\\n[" BOT_HEIGHT_REG "]+\\n[" BOT_DEPTH_REG "]");
+ if (mid)
+ printf("+\\n[" MID_HEIGHT_REG "]+\\n[" MID_DEPTH_REG "]");
+ printf("\n");
+ // determine how many extensible characters we need
+ printf(".nr " TEMP_REG " \\n[" DELTA_REG "]-\\n[" TOTAL_HEIGHT_REG "]");
+ if (mid)
+ printf("/2");
+ printf(">?0+\\n[" EXT_HEIGHT_REG "]+\\n[" EXT_DEPTH_REG "]-1/(\\n["
+ EXT_HEIGHT_REG "]+\\n[" EXT_DEPTH_REG "])\n");
+
+ printf(".nr " TOTAL_HEIGHT_REG " +(\\n[" EXT_HEIGHT_REG "]+\\n["
+ EXT_DEPTH_REG "]*\\n[" TEMP_REG "]");
+ if (mid)
+ printf("*2");
+ printf(")\n");
+ printf(".ds " DELIM_STRING " \\Z" DELIMITER_CHAR
+ "\\v'-%dM-(\\n[" TOTAL_HEIGHT_REG "]u/2u)'\n",
+ axis_height);
+ if (top)
+ printf(".as " DELIM_STRING " \\v'\\n[" TOP_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" TOP_DEPTH_REG "]u'\n",
+ top);
+
+ // this macro appends $2 copies of $3 to string $1
+ printf(".de " REPEAT_APPEND_STRING_MACRO "\n"
+ ".if \\\\$2 \\{.as \\\\$1 \"\\\\$3\n"
+ "." REPEAT_APPEND_STRING_MACRO " \\\\$1 \\\\$2-1 \"\\\\$3\"\n"
+ ".\\}\n"
+ "..\n");
+
+ printf("." REPEAT_APPEND_STRING_MACRO " " DELIM_STRING " \\n[" TEMP_REG "] "
+ "\\v'\\n[" EXT_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" EXT_DEPTH_REG "]u'\n",
+ ext);
+
+ if (mid) {
+ printf(".as " DELIM_STRING " \\v'\\n[" MID_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" MID_DEPTH_REG "]u'\n",
+ mid);
+ printf("." REPEAT_APPEND_STRING_MACRO " " DELIM_STRING
+ " \\n[" TEMP_REG "] "
+ "\\v'\\n[" EXT_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" EXT_DEPTH_REG "]u'\n",
+ ext);
+ }
+ if (bot)
+ printf(".as " DELIM_STRING " \\v'\\n[" BOT_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" BOT_DEPTH_REG "]u'\n",
+ bot);
+ printf(".as " DELIM_STRING " " DELIMITER_CHAR "\n");
+}
+
+static void define_extensible_string(char *delim, int uid,
+ left_or_right_t left_or_right)
+{
+ printf(".ds " DELIM_STRING "\n");
+ delimiter *d = delim_table;
+ int delim_len = strlen(delim);
+ int i;
+ for (i = 0; i < DELIM_TABLE_SIZE; i++, d++)
+ if (strncmp(delim, d->name, delim_len) == 0
+ && (left_or_right & d->flags) != 0)
+ break;
+ if (i >= DELIM_TABLE_SIZE) {
+ error("there is no '%1' delimiter", delim);
+ printf(".nr " DELIM_WIDTH_REG " 0\n");
+ return;
+ }
+
+ printf(".nr " DELIM_WIDTH_REG " 0\\w" DELIMITER_CHAR "\\f[%s]%s\\fP" DELIMITER_CHAR "\n"
+ ".ds " DELIM_STRING " \\Z" DELIMITER_CHAR
+ "\\v'\\n[rsb]u+\\n[rst]u/2u-%dM'\\f[%s]%s\\fP" DELIMITER_CHAR "\n"
+ ".nr " TOTAL_HEIGHT_REG " \\n[rst]-\\n[rsb]\n"
+ ".if \\n[" TOTAL_HEIGHT_REG "]<\\n[" DELTA_REG "] "
+ "\\{",
+ current_roman_font, d->small, axis_height,
+ current_roman_font, d->small);
+
+ char buf[256];
+// The format string in the sprintf below comes from a struct
+// initializer above, and is not subject to external influence.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ sprintf(buf, d->chain_format, "\\\\n[" INDEX_REG "]");
+#pragma GCC diagnostic pop
+ printf(".nr " INDEX_REG " 0\n"
+ ".de " TEMP_MACRO "\n"
+ ".ie c%s \\{\\\n"
+ ".nr " DELIM_WIDTH_REG " 0\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n"
+ ".ds " DELIM_STRING " \\Z" DELIMITER_CHAR
+ "\\v'\\\\n[rsb]u+\\\\n[rst]u/2u-%dM'%s" DELIMITER_CHAR "\n"
+ ".nr " TOTAL_HEIGHT_REG " \\\\n[rst]-\\\\n[rsb]\n"
+ ".if \\\\n[" TOTAL_HEIGHT_REG "]<\\n[" DELTA_REG "] "
+ "\\{.nr " INDEX_REG " +1\n"
+ "." TEMP_MACRO "\n"
+ ".\\}\\}\n"
+ ".el .nr " INDEX_REG " 0-1\n"
+ "..\n"
+ "." TEMP_MACRO "\n",
+ buf, buf, axis_height, buf);
+ if (d->ext) {
+ printf(".if \\n[" INDEX_REG "]<0 \\{.if c%s \\{\\\n", d->ext);
+ build_extensible(d->ext, d->top, d->mid, d->bot);
+ printf(".\\}\\}\n");
+ }
+ printf(".\\}\n");
+ printf(".as " DELIM_STRING " \\h'\\n[" DELIM_WIDTH_REG "]u'\n");
+ printf(".nr " WIDTH_FORMAT " +\\n[" DELIM_WIDTH_REG "]\n", uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?(\\n[" TOTAL_HEIGHT_REG "]/2+%dM)\n",
+ uid, uid, axis_height);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ ">?(\\n[" TOTAL_HEIGHT_REG "]/2-%dM)\n",
+ uid, uid, axis_height);
+}
+
+int delim_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DELTA_REG " \\n[" HEIGHT_FORMAT "]-%dM"
+ ">?(\\n[" DEPTH_FORMAT "]+%dM)\n",
+ p->uid, axis_height, p->uid, axis_height);
+ printf(".nr " DELTA_REG " 0\\n[" DELTA_REG "]*%d/500"
+ ">?(\\n[" DELTA_REG "]*2-%dM)\n",
+ delimiter_factor, delimiter_shortfall);
+ if (left) {
+ define_extensible_string(left, uid, LEFT_DELIM);
+ printf(".rn " DELIM_STRING " " LEFT_DELIM_STRING_FORMAT "\n",
+ uid);
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" DELIM_WIDTH_REG "]\n");
+ }
+ if (right) {
+ define_extensible_string(right, uid, RIGHT_DELIM);
+ printf(".rn " DELIM_STRING " " RIGHT_DELIM_STRING_FORMAT "\n",
+ uid);
+ }
+ return r;
+}
+
+void delim_box::output()
+{
+ if (output_format == troff) {
+ if (left)
+ printf("\\*[" LEFT_DELIM_STRING_FORMAT "]", uid);
+ p->output();
+ if (right)
+ printf("\\*[" RIGHT_DELIM_STRING_FORMAT "]", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<mrow><mo>%s</mo>", left);
+ p->output();
+ printf("<mo>%s</mo></mrow>", right);
+ }
+}
+
+void delim_box::check_tabs(int level)
+{
+ p->check_tabs(level);
+}
+
+void delim_box::debug_print()
+{
+ fprintf(stderr, "left \"%s\" { ", left ? left : "");
+ p->debug_print();
+ fprintf(stderr, " }");
+ if (right)
+ fprintf(stderr, " right \"%s\"", right);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/eqn.1.man b/src/preproc/eqn/eqn.1.man
new file mode 100644
index 0000000..9c3d43c
--- /dev/null
+++ b/src/preproc/eqn/eqn.1.man
@@ -0,0 +1,2240 @@
+'\" et
+.TH @g@eqn @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@eqn \- format mathematics (equations) for
+.I groff
+or MathML
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2023 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_eqn_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.ie \n(.V<\n(.v \
+. ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el \
+. ds tx TeX
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@eqn
+.RB [ \-CNrR ]
+.RB [ \- d
+.IR xy ]
+.RB [ \-f
+.IR F ]
+.RB [ \-m
+.IR n ]
+.RB [ \-M
+.IR dir ]
+.RB [ \-p
+.IR n ]
+.RB [ \-s
+.IR n ]
+.RB [ \-T
+.IR dev ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@eqn
+.B \-\-help
+.YS
+.
+.
+.SY @g@eqn
+.B \-v
+.
+.SY @g@eqn
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU implementation of
+.I eqn \" GNU
+is part of the
+.MR groff @MAN7EXT@
+document formatting system.
+.
+.I @g@eqn
+is a
+.MR @g@troff @MAN1EXT@
+preprocessor that translates expressions in its own language,
+embedded in
+.MR roff @MAN7EXT@
+input files,
+into mathematical notation typeset by
+.MR @g@troff @MAN1EXT@ .
+.
+It copies each
+.IR file 's
+contents to the standard output stream,
+translating each
+.I equation
+between lines starting with
+.B .EQ
+and
+.BR .EN ,
+or within a pair of user-specified delimiters.
+.
+Normally,
+.I @g@eqn
+is not executed directly by the user,
+but invoked by specifying the
+.B \-e
+option to
+.MR groff @MAN1EXT@ .
+.
+While GNU
+.IR eqn 's \" GNU
+input syntax is highly compatible with AT&T
+.IR eqn , \" AT&T
+the output
+.I @g@eqn
+produces cannot be processed by AT&T
+.IR troff ; \" AT&T
+GNU
+.I troff \" GNU
+(or a
+.I troff \" generic
+implementing relevant GNU extensions)
+must be used.
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I @g@eqn
+reads the standard input stream.
+.
+.
+.P
+Unless the
+.B \-R
+option is used,
+.I @g@eqn
+searches for the file
+.I eqnrc
+in the directories given with the
+.B \-M
+option first,
+then in
+.if !'@COMPATIBILITY_WRAPPERS@'no' .IR @SYSTEMMACRODIR@ ,
+.IR @LOCALMACRODIR@ ,
+and finally in the standard macro directory
+.IR @MACRODIR@ .
+.
+If it exists and is readable,
+.I @g@eqn
+processes it before any input files.
+.
+.
+.P
+This man page primarily discusses the differences between GNU
+.I eqn \" GNU
+and AT&T
+.IR eqn .\" AT&T
+.
+Most of the new features of the GNU
+.I eqn \" GNU
+input language are based on \*[tx].
+.
+There are some references to the differences between \*[tx] and GNU
+.I eqn \" GNU
+below;
+these may safely be ignored if you do not know \*[tx].
+.
+.
+.P
+Three points are worth special note. \" good, bad, and different
+.
+.
+.IP \[bu] 2n
+GNU
+.I eqn \" GNU
+emits Presentation MathML output when invoked with the
+.RB \[lq] "\-T\~MathML" \[rq]
+option.
+.
+.
+.IP \[bu]
+GNU
+.I eqn \" GNU
+does not support terminal devices well,
+though it may suffice for simple inputs.
+.
+.
+.IP \[bu]
+GNU
+.I eqn
+sets the input token
+.RB \[lq] .\|.\|.\& \[rq]
+as an ellipsis on the text baseline,
+not the three centered dots of AT&T
+.IR eqn . \" AT&T
+.
+Set an ellipsis on the math axis with the GNU extension macro
+.BR cdots .
+.
+.
+.\" ====================================================================
+.SS "Anatomy of an equation"
+.\" ====================================================================
+.
+.I eqn
+input consists of tokens.
+.
+Consider a form of Newton's second law of motion.
+.
+The input
+.
+.
+.P
+.RS
+.EX
+\&.EQ
+F =
+m a
+\&.EN
+.EE
+.RE
+.
+.
+.P
+becomes
+.EQ
+F =
+m a.
+.EN
+.
+Each of
+.BR F ,
+.BR = ,
+.BR m ,
+and
+.B a
+is a token.
+.
+.
+Spaces and newlines are interchangeable;
+they separate tokens but do not break lines or produce space in
+the output.
+.
+.
+.P
+The following input characters not only separate tokens,
+but manage their grouping and spacing as well.
+.
+.
+.TP
+.B "{ }"
+Braces perform grouping.
+.
+Whereas
+.RB \[lq] "e sup a b" \[rq]
+expresses
+.ie n .RI \[lq]( e "\~to the\~" a )\~times\~ b \[rq],
+.el \{\
+.EQ
+e sup a b ,
+.EN
+.\}
+.RB \[lq] "e sup { a b }" \[rq]
+means
+.ie n .RI \[lq] e "\~to the\~(" a \~times\~ b )\[rq].
+.el \{\
+.EQ
+e sup { a b } .
+.EN
+.\}
+.
+When immediately preceded by a
+.RB \[lq] left \[rq]
+or
+.RB \[lq] right \[rq]
+primitive,
+a brace loses its special meaning.
+.
+.
+.TP
+.B "\[ha] \[ti]
+are the
+.I "half space"
+and
+.I "full space,"
+respectively.
+.
+Use them to tune the appearance of the output.
+.
+.
+.P
+Tab and leader characters separate tokens as well as advancing the
+drawing position to the next tab stop,
+but are seldom used in
+.I eqn
+input.
+.
+When they occur,
+they must appear at the outermost lexical scope.
+.
+This roughly means that they can't appear within braces that are
+necessary to disambiguate the input;
+.I @g@eqn
+will diagnose an error in this event.
+.
+(See subsection \[lq]Macros\[rq] below for additional token separation
+rules.)
+.
+.
+.P
+Other tokens are primitives,
+macros,
+an argument to either of the foregoing,
+or components of an equation.
+.
+.
+.br
+.ne 4v
+.P
+.I Primitives
+are fundamental keywords of the
+.I eqn
+language.
+.
+They can configure an aspect of the preprocessor's state,
+as when setting a \[lq]global\[rq] font selection or type size
+.RB ( gfont
+and
+.BR gsize ),
+or declaring or deleting macros
+.RB \%(\[lq] define \[rq]
+and
+.BR undef );
+these are termed
+.I commands.
+.
+Other primitives perform formatting operations on the tokens after them
+(as with
+.BR fat ,
+.BR over ,
+.BR sqrt ,
+or
+.BR up ).
+.
+.
+.P
+Equation
+.I components
+include mathematical variables,
+constants,
+numeric literals,
+and operators.
+.
+.I @g@eqn
+remaps some input character sequences to
+.I groff
+special character escape sequences for economy in equation entry and to
+ensure that glyphs from an unstyled font are used;
+see
+.MR groff_char @MAN7EXT@ .
+.
+.
+.P
+.RS
+.TS
+tab(@);
+Lf(CR) Lf(CR) Lw(1i) Lf(CR) Lf(CR).
++@\[rs][pl]@\&@\[aq]@\[rs][fm]
+-@\[rs][mi]@\&@<=@\[rs][<=]
+\&=@\[rs][eq]@\&@>=@\[rs][>=]
+.TE
+.RE
+.
+.
+.P
+.I Macros
+permit primitives,
+components,
+and other macros to be collected and referred to by a single token.
+.
+Predefined macros make convenient the preparation of
+.I eqn
+input in a form resembling its spoken expression;
+for example,
+consider
+.BR cos ,
+.BR hat ,
+.BR inf ,
+and
+.BR lim .
+.
+.
+.\" ====================================================================
+.SS "Spacing and typeface"
+.\" ====================================================================
+.
+GNU
+.I eqn
+imputes types to the components of an equation,
+adjusting the spacing between them accordingly.
+.
+Recognized types are as follows;
+most affect spacing only,
+whereas the
+.RB \%\[lq] letter \[rq]
+subtype of
+.RB \%\[lq] ordinary \[rq]
+also assigns a style.
+.
+.
+.RS 2n \" we need quite a bit of horizontal space for this table
+.P
+.TS
+Lf(CR) Lx
+Af(CR) Lx
+Af(CR) Lx
+Lf(CR) Lx.
+ordinary T{
+character such as \[lq]1\[rq],
+\[lq]a\[rq],
+or
+\[lq]!\&\[rq]
+T}
+letter character to be italicized by default
+digit \f[I]n/a\f[]
+operator T{
+large operator such as
+.ds Su \[lq]\s+5\[*S]\s0\[rq]
+.if \n(.g .if !c\[*S] .ds Su the summation operator
+\*[Su]
+.rm Su
+T}
+binary binary operator such as \[lq]\[pl]\[rq]
+relation relational operator such as \[lq]=\[rq]
+opening opening bracket such as \[lq](\[rq]
+closing closing bracket such as \[lq])\[rq]
+punctuation punctuation character such as \[lq],\[rq]
+inner sub-formula contained within brackets
+suppress component to which automatic spacing is not applied
+.TE
+.RE
+.
+.
+.P
+Two primitives apply types to equation components.
+.
+.
+.TP
+.BI type\~ "t e"
+Apply
+.RI type\~ t
+to
+.RI expression\~ e .
+.
+.
+.TP
+.BI chartype\~ "t text"
+Assign each character in (unquoted)
+.I text
+.RI type\~ t ,
+persistently.
+.
+.
+.P
+.I @g@eqn \" GNU
+sets up spacings and styles as if by the following commands.
+.
+.P
+.RS
+.TS
+tab(@);
+Lf(CR)1 Lf(CR).
+chartype \[dq]letter\[dq]@abcdefghiklmnopqrstuvwxyz
+chartype \[dq]letter\[dq]@ABCDEFGHIKLMNOPQRSTUVWXYZ
+chartype \[dq]letter\[dq]@\[rs][*a]\[rs][*b]\[rs][*g]\[rs][*d]\[rs][*e]\
+\[rs][*z]
+chartype \[dq]letter\[dq]@\[rs][*y]\[rs][*h]\[rs][*i]\[rs][*k]\[rs][*l]\
+\[rs][*m]
+chartype \[dq]letter\[dq]@\[rs][*n]\[rs][*c]\[rs][*o]\[rs][*p]\[rs][*r]\
+\[rs][*s]
+chartype \[dq]letter\[dq]@\[rs][*t]\[rs][*u]\[rs][*f]\[rs][*x]\[rs][*q]\
+\[rs][*w]
+chartype \[dq]binary\[dq]@*\[rs][pl]\[rs][mi]
+chartype \[dq]relation\[dq]@<>\[rs][eq]\[rs][<=]\[rs][>=]
+chartype \[dq]opening\[dq]@{([
+chartype \[dq]closing\[dq]@})]
+chartype \[dq]punctuation\[dq]@,;:.
+chartype \[dq]suppress\[dq]@\[ha]\[ti]
+.TE
+.RE
+.
+.
+.P
+.I @g@eqn
+assigns all other ordinary and special
+.I roff
+characters,
+including numerals 0\[en]9,
+the
+.RB \%\[lq] ordinary \[rq]
+type.
+.
+(The
+.RB \[lq] digit \[rq]
+type is not used,
+but is available for customization.)
+.\" XXX: How would you actually customize it, though? There doesn't
+.\" seem to be a means of replacing the font associated with a type.
+.\" Is the "digit" type just cruft?
+.
+In keeping with common practice in mathematical typesetting,
+lowercase,
+but not uppercase,
+Greek letters are assigned the
+.RB \%\[lq] letter \[rq]
+type to style them in italics.
+.
+The macros for producing ellipses,
+.RB \[lq] .\|.\|. \[rq],
+.BR cdots ,
+and
+.BR ldots ,
+use the
+.RB \%\[lq] inner \[rq]
+type.
+.
+.
+.\" ====================================================================
+.SS Primitives
+.\" ====================================================================
+.
+.I @g@eqn
+supports without alteration the AT&T
+.I eqn \" AT&T
+primitives
+.BR above ,
+.BR back ,
+.BR bar ,
+.BR bold ,
+.BR \%define ,
+.BR down ,
+.BR fat ,
+.BR font ,
+.BR from ,
+.BR fwd ,
+.BR gfont ,
+.BR gsize ,
+.BR italic ,
+.BR left ,
+.BR lineup ,
+.BR mark ,
+.BR \%matrix ,
+.BR \%ndefine ,
+.BR over ,
+.BR right ,
+.BR roman ,
+.BR size ,
+.BR sqrt ,
+.BR sub ,
+.BR sup ,
+.BR \%tdefine ,
+.BR to ,
+.BR \%under ,
+and
+.BR up .
+.
+.
+.\" ====================================================================
+.SS "New primitives"
+.\" ====================================================================
+.
+The GNU extension primitives
+.RB \[lq] type \[rq]
+and
+.B \%chartype
+are discussed in subsection \[lq]Spacing and typeface\[rq] above;
+.RB \[lq] set \[rq]
+in subsection \[lq]Customization\[rq] below;
+and
+.B grfont
+and
+.B gbfont
+in subsection \[lq]Fonts\[rq] below.
+.
+In the following synopses,
+.I X
+can be any character not appearing in the parameter thus bracketed.
+.
+.
+.TP
+.IB e1 \~accent\~ e2
+Set
+.I e2
+as an accent over
+.IR e1 .
+.
+.I e2
+is assumed to be at the appropriate height for a lowercase letter
+without an ascender;
+.I @g@ eqn
+vertically shifts it depending on
+.IR e1 's
+height.
+.
+For example,
+.B hat
+is defined as follows.
+.
+.
+.RS
+.IP
+.EX
+accent { "\[ha]" }
+.EE
+.RE
+.
+.
+.IP
+.BR dotdot ,
+.BR dot ,
+.BR tilde ,
+.BR vec ,
+and
+.B dyad
+are also defined using the
+.B \%accent
+primitive.
+.
+.
+.TP
+.BI big\~ e
+Enlarge the expression
+.IR e ;
+semantics like those of CSS \[lq]large\[rq] are intended.
+.
+In
+.I @g@troff
+output,
+the type size is increased by\~5 scaled points.
+.
+MathML output emits the following.
+.
+.
+.RS
+.IP
+.EX
+<mstyle \%mathsize=\[aq]big\[aq]>
+.EE
+.RE
+.
+.
+.TP
+.BI copy\~ file
+.TQ
+.BI include\~ file
+Interpolate the contents of
+.IR file ,
+omitting lines
+beginning with
+.B .EQ
+or
+.BR .EN .
+.
+If a relative path name,
+.I file
+is sought relative to the current working directory.
+.
+.
+.TP
+.BI ifdef\~ "name X anything X"
+If
+.I name
+is defined as a primitive or macro,
+interpret
+.IR anything .
+.
+.
+.TP
+.BI nosplit\~ text
+As
+.RI \[dq] text \[dq],
+but since
+.I text
+is not quoted it is subject to macro expansion;
+it is not split up and the spacing between characters not adjusted per
+subsection \[lq]Spacing and typeface\[rq] above.
+.
+.
+.TP
+.IB e\~ opprime
+As
+.BR prime ,
+but set the prime symbol as an operator
+.RI on\~ e .
+.
+In the input
+.RB \[lq] "A opprime sub 1" \[rq],
+the\~\[lq]1\[rq] is tucked under the prime as a subscript to
+the\~\[lq]A\[rq]
+(as is conventional in mathematical typesetting),
+whereas when
+.B prime
+is used,
+the\~\[lq]1\[rq] is a subscript to the prime character.
+.
+The precedence of
+.B \%opprime
+is the same as that of
+.B bar
+and
+.RB \%\[lq] under \[rq],
+and higher than that of other primitives except
+.B \%accent
+and
+.BR uaccent .
+.
+In unquoted text,
+a neutral apostrophe
+.RB ( \[aq] )
+that is not the first character on the input line is treated like
+.BR \%opprime .
+.
+.
+.TP
+.BI sdefine\~ "name X anything X"
+As
+.RB \%\[lq] define \[rq],
+but
+.I name
+is not recognized as a macro if called with arguments.
+.
+.
+.TP
+.IB e1 \~smallover\~ e2
+As
+.BR over ,
+but reduces the type size of
+.I e1
+and
+.IR e2 ,
+and puts less vertical space between
+.I e1
+and
+.I e2
+and the fraction bar.
+.
+The
+.B over
+primitive corresponds to the \*[tx]
+.B \[rs]over
+primitive in displayed equation styles;
+.B smallover
+corresponds to
+.B \[rs]over
+in non-display (\[lq]inline\[rq]) styles.
+.
+.
+.br
+.ne 5v
+.TP
+.BI space\~ n
+Set extra vertical spacing around the equation,
+replacing the default values,
+where
+.IR n \~is
+an integer in hundredths of an em.
+.
+If positive,
+.IR n \~increases
+vertical spacing before the equation;
+if negative,
+it does so after the equation.
+.
+This primitive provides an interface to
+.IR groff 's
+.B \[rs]x
+escape sequence,
+but with the opposite sign convention.
+.
+It has no effect if the equation is part of a
+.MR @g@pic @MAN1EXT@
+picture.
+.
+.
+.TP
+.BI special\~ "troff-macro e"
+Construct an object by calling
+.I troff-macro
+.RI on\~ e .
+.
+The
+.I troff \" generic
+string
+.B 0s
+contains the
+.I eqn \" generic
+output
+.RI for\~ e ,
+and the registers
+.BR 0w ,
+.BR 0h ,
+.BR 0d ,
+.BR 0skern ,
+and
+.B 0skew
+the width,
+height,
+depth,
+subscript kern,
+and skew
+.RI of\~ e ,
+respectively.
+.
+(The
+.I subscript kern
+of an object indicates how much a subscript on that object should be
+\[lq]tucked in\[rq],
+or placed to the left relative to a non-subscripted glyph of the same
+size.
+.
+The
+.I skew
+of an object is how far to the right of the center of the object an
+accent over it should be placed.)
+.
+The macro must modify
+.B 0s
+so that it outputs the desired result,
+returns the drawing position to the text baseline at the beginning of
+.IR e ,
+and updates the foregoing registers to correspond to the new dimensions
+of the result.
+.
+.
+.IP
+Suppose you want a construct that \[lq]cancels\[rq] an expression by
+drawing a diagonal line through it.
+.
+.
+.br
+.ne 11v
+.RS
+.IP
+.EX
+\&.de Ca
+\&. ds 0s \[rs]
+\[rs]Z\[aq]\[rs]\[rs]*(0s\[aq]\[rs]
+\[rs]v\[aq]\[rs]\[rs]n(0du\[aq]\[rs]
+\[rs]D\[aq]l \[rs]\[rs]n(0wu \-\[rs]\[rs]n(0hu\-\[rs]\
+\[rs]n(0du\[aq]\[rs]
+\[rs]v\[aq]\[rs]\[rs]n(0hu\[aq]
+\&..
+\&.EQ
+special Ca "x \[rs][mi] 3 \[rs][pl] x" \[ti] 3
+\&.EN
+.EE
+.RE
+.
+.
+.IP
+We use the
+.B \[rs][mi]
+and
+.B \[rs][pl]
+special characters instead of + and \-
+because they are part of the argument to a
+.I @g@troff
+macro,
+so
+.I @g@eqn
+does not transform them to mathematical glyphs for us.
+.
+Here's a more complicated construct that draws a box around an
+expression;
+the bottom of the box rests on the text baseline.
+.
+We define the
+.I eqn \" generic
+macro
+.B box
+to wrap the call of the
+.I @g@troff
+macro
+.BR Bx .
+.
+.
+.br
+.ne 17v
+.RS
+.IP
+.EX
+\&.de Bx
+\&.ds 0s \[rs]
+\[rs]Z\[aq]\[rs]\[rs]h\[aq]1n\[aq]\[rs]\[rs]*[0s]\[aq]\[rs]
+\[rs]v\[aq]\[rs]\[rs]n(0du+1n\[aq]\[rs]
+\[rs]D\[aq]l \[rs]\[rs]n(0wu+2n 0\[aq]\[rs]
+\[rs]D\[aq]l 0 \-\[rs]\[rs]n(0hu\-\[rs]\[rs]n(0du\-2n\[aq]\[rs]
+\[rs]D\[aq]l \-\[rs]\[rs]n(0wu\-2n 0\[aq]\[rs]
+\[rs]D\[aq]l 0 \[rs]\[rs]n(0hu+\[rs]\[rs]n(0du+2n\[aq]\[rs]
+\[rs]h\[aq]\[rs]\[rs]n(0wu+2n\[aq]
+\&.nr 0w +2n
+\&.nr 0d +1n
+\&.nr 0h +1n
+\&..
+\&.EQ
+define box \[aq] special Bx $1 \[aq]
+box(foo) \[ti] "bar"
+\&.EN
+.EE
+.RE
+.
+.
+.TP
+.BI "split \[dq]" text \[dq]
+As
+.IR text ,
+but since
+.I text
+is quoted,
+it is not subject to macro expansion;
+it is split up and the spacing between characters adjusted per
+subsection \[lq]Spacing and typeface\[rq] above.
+.
+.
+.TP
+.IB e1 \~uaccent\~ e2
+Set
+.I e2
+as an accent under
+.IR e1 .
+.
+.I e2
+is assumed to be at the appropriate height for a letter without a
+descender;
+.I @g@ eqn
+vertically shifts it depending on whether
+.I e1
+has a descender.
+.
+.B utilde
+is predefined using
+.B uaccent
+as a tilde accent below the baseline.
+.
+.
+.TP
+.BI undef\~ name
+Remove definition of macro or primitive
+.IR name ,
+making it undefined.
+.
+.
+.TP
+.BI vcenter\~ e
+Vertically center
+.I e
+about the
+.IR "math axis" ,
+a horizontal line upon which fraction bars and characters such as
+\[lq]\[pl]\[rq] and \[lq]\[mi]\[rq] are aligned.
+.
+MathML already behaves this way,
+so
+.I @g@eqn
+ignores this primitive when producing that output format.
+.
+The built-in
+.B sum
+macro is defined as if by the following.
+.
+.RS
+.IP
+.EX
+define sum ! { type "operator" vcenter size +5 \[rs](*S } !
+.EE
+.RE
+.
+.
+.br
+.ne 8v
+.\" ====================================================================
+.SS "Extended primitives"
+.\" ====================================================================
+.
+GNU
+.I eqn \" GNU
+extends the syntax of some AT&T
+.I eqn \" AT&T
+primitives,
+introducing one deliberate incompatibility.
+.
+.
+.TP
+.B "delim on"
+.I @g@eqn
+recognizes an
+.RB \[lq] on \[rq]
+argument to the
+.B \%delim
+primitive specially,
+restoring any delimiters previously disabled with
+.RB \%\[lq] "delim off" \[rq].
+.
+If delimiters haven't been specified,
+neither command has effect.
+.
+Few
+.I eqn \" generic
+documents are expected to use \[lq]o\[rq] and \[lq]n\[rq] as left and
+right delimiters,
+respectively.
+.
+If yours does,
+consider swapping them,
+or select others.
+.
+.
+.TP
+.BI col\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI ccol\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI lcol\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI rcol\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI pile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI cpile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI lpile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI rpile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+The integer
+.RI value\~ n
+(in hundredths of an em)
+increases the vertical spacing between rows,
+using
+.IR groff 's
+.B \[rs]x
+escape sequence
+(the value has no effect in MathML mode).
+.
+Negative values are accepted but have no effect.
+.
+If more than one
+.I n
+occurs in a matrix or pile,
+the largest is used.
+.
+.
+.\" ====================================================================
+.SS Customization
+.\" ====================================================================
+.
+When
+.I @g@eqn
+generates
+.I @g@troff
+input,
+the appearance of equations is controlled by a large number of
+parameters.
+.
+They have no effect when generating MathML,
+which delegates typesetting to a MathML rendering engine.
+.
+Configure these parameters with the
+.B set
+primitive.
+.
+.
+.TP
+.BI set\~ "p n"
+assigns
+.RI parameter\~ p
+the integer
+.RI value\~ n ;
+.IR n \~is
+interpreted in units of hundredths of an em unless otherwise stated.
+.
+For example,
+.
+.
+.RS
+.IP
+.EX
+set x_height 45
+.EE
+.RE
+.
+.
+.IP
+says that
+.I @g@eqn
+should assume that the font's x-height is 0.45\~ems.
+.
+.
+.RS
+.P
+Available parameters are as follows;
+defaults are shown in parentheses.
+.
+We intend these descriptions to be expository rather than rigorous.
+.
+.
+.TP 17n
+.B minimum_size
+sets a floor for the type size
+(in scaled points)
+at which equations are set
+.RB ( 5 ).
+.
+.
+.TP
+.B fat_offset
+The
+.B fat
+primitive emboldens an equation by overprinting two copies of the
+equation horizontally offset by this amount
+.RB ( 4 ).
+.
+In MathML mode,
+components to which
+.B \%fat_offset
+applies instead use the following.
+.
+.RS
+.RS
+.EX
+<mstyle mathvariant=\[aq]double\-struck\[aq]>
+.EE
+.RE
+.RE
+.
+.
+.TP
+.B over_hang
+A fraction bar is longer by twice this amount than
+the maximum of the widths of the numerator and denominator;
+in other words,
+it overhangs the numerator and denominator by at least this amount
+.RB ( 0 ).
+.
+.
+.TP
+.B accent_width
+When
+.B bar
+or
+.B \%under
+is applied to a single character,
+the line is this long
+.RB ( 31 ).
+.
+Normally,
+.B bar
+or
+.B \%under
+produces a line whose length is the width of the object to which it
+applies;
+in the case of a single character,
+this tends to produce a line that looks too long.
+.
+.
+.TP
+.B delimiter_factor
+Extensible delimiters produced with the
+.B left
+and
+.B right
+primitives have a combined height and depth of at least this many
+thousandths of twice the maximum amount by which the sub-equation that
+the delimiters enclose extends away from the axis
+.RB ( 900 ).
+.
+.
+.TP
+.B delimiter_shortfall
+Extensible delimiters produced with the
+.B left
+and
+.B right
+primitives have a combined height and depth not less than the
+difference of twice the maximum amount by which the sub-equation that
+the delimiters enclose extends away from the axis and this amount
+.RB ( 50 ).
+.
+.
+.TP
+.B null_delimiter_space
+This much horizontal space is inserted on each side of a fraction
+.RB ( 12 ).
+.
+.
+.TP
+.B script_space
+The width of subscripts and superscripts is increased by this amount
+.RB ( 5 ).
+.
+.
+.TP
+.B thin_space
+This amount of space is automatically inserted after punctuation
+characters.
+.
+It also configures the width of the space produced by the
+.B \[ha]
+token
+.RB ( 17 ).
+.
+.
+.TP
+.B medium_space
+This amount of space is automatically inserted on either side of
+binary operators
+.RB ( 22 ).
+.
+.
+.TP
+.B thick_space
+This amount of space is automatically inserted on either side of
+relations.
+.
+It also configures the width of the space produced by the
+.B \[ti]
+token
+.RB ( 28 ).
+.
+.
+.TP
+.B x_height
+The height of lowercase letters without ascenders such as \[lq]x\[rq]
+.RB ( 45 ).
+.
+.
+.TP
+.B axis_height
+The height above the baseline of the center of characters such as
+\[lq]\[pl]\[rq] and \[lq]\[mi]\[rq]
+.RB ( 26 ).
+.
+It is important that this value is correct for the font
+you are using.
+.
+.
+.TP
+.B default_rule_thickness
+This should be set to the thickness of the
+.B \[rs][ru]
+character,
+or the thickness of horizontal lines produced with the
+.B \[rs]D
+escape sequence
+.RB ( 4 ).
+.
+.
+.TP
+.B num1
+The
+.B over
+primitive shifts up the numerator by at least this amount
+.RB ( 70 ).
+.
+.
+.TP
+.B num2
+The
+.B smallover
+primitive shifts up the numerator by at least this amount
+.RB ( 36 ).
+.
+.
+.TP
+.B denom1
+The
+.B over
+primitive shifts down the denominator by at least this amount
+.RB ( 70 ).
+.
+.
+.TP
+.B denom2
+The
+.B smallover
+primitive shifts down the denominator by at least this amount
+.RB ( 36 ).
+.
+.
+.TP
+.B sup1
+Normally superscripts are shifted up by at least this amount
+.RB ( 42 ).
+.
+.
+.TP
+.B sup2
+Superscripts within superscripts or upper limits
+or numerators of
+.B smallover
+fractions are shifted up by at least this amount
+.RB ( 37 ).
+.
+Conventionally,
+this is less than
+.BR sup1 .
+.
+.
+.TP
+.B sup3
+Superscripts within denominators or square roots
+or subscripts or lower limits are shifted up by at least
+this amount
+.RB ( 28 ).
+.
+Conventionally,
+this is less than
+.BR sup2 .
+.
+.
+.TP
+.B sub1
+Subscripts are normally shifted down by at least this amount
+.RB ( 20 ).
+.
+.
+.TP
+.B sub2
+When there is both a subscript and a superscript,
+the subscript is shifted down by at least this amount
+.RB ( 23 ).
+.
+.
+.TP
+.B sup_drop
+The baseline of a superscript is no more than this much below the top of
+the object on which the superscript is set
+.RB ( 38 ).
+.
+.
+.TP
+.B sub_drop
+The baseline of a subscript is at least this much below the bottom of
+the object on which the subscript is set
+.RB ( 5 ).
+.
+.
+.TP
+.B big_op_spacing1
+The baseline of an upper limit is at least this much above the top of
+the object on which the limit is set
+.RB ( 11 ).
+.
+.
+.TP
+.B big_op_spacing2
+The baseline of a lower limit is at least this much below the bottom
+of the object on which the limit is set
+.RB ( 17 ).
+.
+.
+.TP
+.B big_op_spacing3
+The bottom of an upper limit is at least this much above the top of
+the object on which the limit is set
+.RB ( 20 ).
+.
+.
+.TP
+.B big_op_spacing4
+The top of a lower limit is at least this much below the bottom of the
+object on which the limit is set
+.RB ( 60 ).
+.
+.
+.TP
+.B big_op_spacing5
+This much vertical space is added above and below limits
+.RB ( 10 ).
+.
+.
+.TP
+.B baseline_sep
+The baselines of the rows in a pile or matrix are normally this far
+apart
+.RB ( 140 ).
+.
+Usually equal to the sum of
+.B num1
+and
+.BR denom1 .
+.
+.
+.TP
+.B shift_down
+The midpoint between the top baseline and the bottom baseline in a
+matrix or pile is shifted down by this much from the axis
+.RB ( 26 ).
+.
+Usually equal to
+.BR axis_height .
+.
+.
+.TP
+.B column_sep
+This much space is added between columns in a matrix
+.RB ( 100 ).
+.
+.
+.TP
+.B matrix_side_sep
+This much space is added at each side of a matrix
+.RB ( 17 ).
+.
+.
+.br
+.ne 4v
+.TP
+.B draw_lines
+If non-zero,
+.I @g@eqn
+draws lines using the
+.I troff \" generic
+.B \[rs]D
+escape sequence,
+rather than the
+.B \[rs]l
+escape sequence and the
+.B \[rs][ru]
+special character.
+.
+The
+.I eqnrc
+file sets the default:
+.BR 1 \~on
+.BR ps ,
+.BR html ,
+and the X11 devices,
+.RB otherwise\~ 0 .
+.
+.
+.TP
+.B body_height
+is the presumed height of an equation above the text baseline;
+.I @g@eqn
+adds any excess as extra pre-vertical line spacing with
+.IR troff 's\" generic
+.B \[rs]x
+escape sequence
+.RB ( 85 ).
+.
+.
+.TP
+.B body_depth
+is the presumed depth of an equation below the text baseline;
+.I @g@eqn
+adds any excess as extra post-vertical line spacing with
+.IR troff 's\" generic
+.B \[rs]x
+escape sequence
+.RB ( 35 ).
+.
+.
+.TP
+.B nroff
+If non-zero,
+then
+.B \%ndefine
+behaves like
+.B \%define
+and
+.B \%tdefine
+is ignored,
+otherwise
+.B \%tdefine
+behaves like
+.B \%define
+and
+.B \%ndefine
+is ignored.
+.
+The
+.I eqnrc
+file sets the default:
+.BR 1 \~on
+.BR ascii ,
+.BR latin1 ,
+.BR utf8 ,
+and
+.B cp1047
+devices,
+.RB otherwise\~ 0 .
+.RE
+.
+.
+.\" ====================================================================
+.SS Macros
+.\" ====================================================================
+.
+In GNU
+.IR eqn , \" GNU
+macros can take arguments.
+.
+A word defined by any of the
+.BR \%define ,
+.BR \%ndefine ,
+or
+.B \%tdefine
+primitives followed immediately by a left parenthesis is treated as a
+.I "parameterized macro call:"
+subsequent tokens up to a matching right parenthesis are treated as
+comma-separated arguments.
+.
+In this context only,
+commas and parentheses also serve as token separators.
+.
+A macro argument is not terminated by a comma inside parentheses nested
+within it.
+.
+In a macro definition,
+.BI $ n\c
+,
+where
+.I n
+is between 1 and\~9 inclusive,
+is replaced by the
+.IR n th
+argument;
+if there are fewer than
+.IR n \~arguments,
+it is replaced by nothing.
+.
+.
+.\" ====================================================================
+.SS "Predefined macros"
+.\" ====================================================================
+.
+GNU
+.I eqn \" GNU
+supports the predefined macros offered by AT&T
+.IR eqn : \" AT&T
+.BR and ,
+.BR \%approx ,
+.BR arc ,
+.BR cos ,
+.BR cosh ,
+.BR del ,
+.BR det ,
+.BR dot ,
+.BR \%dotdot ,
+.BR dyad ,
+.BR exp ,
+.BR for ,
+.BR grad ,
+.BR half ,
+.BR hat ,
+.BR if ,
+.BR \%inter ,
+.BR Im ,
+.BR inf ,
+.BR int ,
+.BR lim ,
+.BR ln ,
+.BR log ,
+.BR max ,
+.BR min ,
+.BR \%nothing ,
+.BR \%partial ,
+.BR prime ,
+.BR prod ,
+.BR Re ,
+.BR sin ,
+.BR sinh ,
+.BR sum ,
+.BR tan ,
+.BR tanh ,
+.BR tilde ,
+.BR times ,
+.BR union ,
+.BR vec ,
+.BR == ,
+.BR != ,
+.BR += ,
+.BR \-> ,
+.BR <\- ,
+.BR << ,
+.BR >> ,
+and
+.RB \[lq] .\|.\|. \[rq].
+.
+The lowercase classical Greek letters are available as
+.BR \%alpha ,
+.BR beta ,
+.BR chi ,
+.BR delta ,
+.BR \%epsilon ,
+.BR eta ,
+.BR gamma ,
+.BR iota ,
+.BR kappa ,
+.BR lambda ,
+.BR mu ,
+.BR nu ,
+.BR omega ,
+.BR \%omicron ,
+.BR phi ,
+.BR pi ,
+.BR psi ,
+.BR rho ,
+.BR sigma ,
+.BR tau ,
+.BR theta ,
+.BR \%upsilon ,
+.BR xi ,
+and
+.BR zeta .
+.
+Spell them with an initial capital letter
+.RB \%( Alpha )
+or in full capitals
+.RB \%( ALPHA )
+to obtain uppercase forms.
+.
+.
+.P
+GNU
+.I eqn \" GNU
+further defines the macros
+.BR cdot ,
+.BR cdots ,
+and
+.B utilde
+(all discussed above),
+.BR \%dollar ,
+which sets a dollar sign,
+and
+.BR ldots ,
+which sets an ellipsis on the text baseline.
+.
+.
+.\" ====================================================================
+.SS Fonts
+.\" ====================================================================
+.
+.I @g@eqn
+uses up to three typefaces to set an equation:
+italic (oblique),
+roman (upright),
+and bold.
+.
+Assign each a
+.I groff
+typeface with the primitives
+.BR gfont ,
+.BR \%grfont ,
+and
+.B \%gbfont.
+.
+The defaults are the styles
+.BR I ,
+.BR R ,
+and
+.B B
+(applied to the current font family).
+.
+The
+.B \%chartype
+primitive
+(see above)
+sets a character's type,
+which determines the face used to set it.
+.
+The
+.RB \%\[lq] letter \[rq]
+type is set in italics;
+others are set in roman.
+.
+Use the
+.B bold
+primitive to select an (upright) bold style.
+.
+.
+.TP
+.BI gbfont\~ f
+.RI Select\~ f
+as the bold font.
+.
+This is a GNU extension.
+.
+.
+.TP
+.BI gfont\~ f
+.RI Select\~ f
+as the italic font.
+.
+.
+.TP
+.BI grfont\~ f
+.RI Select\~ f
+as the roman font.
+.
+This is a GNU extension.
+.
+.
+.br
+.ne 4v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-C
+Recognize
+.B .EQ
+and
+.B .EN
+even when followed by a character other than space or newline.
+.
+.
+.TP
+.BI \-d\~ xy
+Specify delimiters
+.I x
+for left
+.RI and\~ y
+for right ends
+of equations not bracketed by
+.BR .EQ / .EN .
+.
+.I x
+and
+.I y
+need not be distinct.
+.
+Any
+.RB \%\[lq] delim
+.IR xy \[rq]
+statements in the source file override this option.
+.
+.
+.TP
+.BI \-f\~ F
+is equivalent to
+.RB \[lq] gfont
+.IR F \[rq].
+.
+.
+.TP
+.BI \-m\~ n
+is equivalent to
+.RB \[lq] "set \%minimum_size"
+.IR n \[rq].
+.
+.
+.TP
+.BI \-M\~ dir
+Search
+.I dir
+for
+.I eqnrc
+before those listed in section \[lq]Description\[rq] above.
+.
+.
+.TP
+.B \-N
+Prohibit newlines within delimiters.
+.
+This option allows
+.I @g@eqn
+to recover better from missing closing delimiters.
+.
+.
+.TP
+.BI \-p\~ n
+Set sub- and superscripts
+.IR n \~points
+smaller than the surrounding text.
+.
+This option is deprecated.
+.
+.I @g@eqn
+normally sets sub- and superscripts at 70% of the type size of the
+surrounding text.
+.
+.
+.TP
+.B \-r
+Reduce the type size of subscripts at most once relative to the base
+type size for the equation.
+.
+.
+.TP
+.B \-R
+Don't load
+.IR eqnrc .
+.
+.
+.TP
+.BI \-s\~ n
+is equivalent to
+.RB \[lq] gsize
+.IR n \[rq].
+.
+This option is deprecated.
+.
+.
+.TP
+.BI \-T\~ dev
+Prepare output for the device
+.IR dev .
+.
+In most cases,
+the effect of this is to define a macro
+.I dev
+with a value
+.RB of\~ 1 ;
+.I eqnrc
+uses this to provide definitions appropriate for the device.
+.
+However,
+if the specified driver is \[lq]MathML\[rq],
+the output is MathML markup rather than
+.I @g@troff
+input,
+and
+.I eqnrc
+is not loaded at all.
+.
+The default output device is
+.BR @DEVICE@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/\:\%eqnrc
+Initialization file.
+.
+.
+.\" ====================================================================
+.SH "MathML mode limitations"
+.\" ====================================================================
+.
+MathML is designed on the assumption that it cannot know the exact
+physical characteristics of the media and devices on which it will
+be rendered.
+.
+It does not support control of motions and sizes to the same
+degree
+.I @g@troff
+does.
+.
+.
+.IP \[bu] 2n
+.I @g@eqn
+customization parameters have no effect on generated MathML.
+.
+.
+.IP \[bu]
+The
+.BR \%special ,
+.BR up ,
+.BR down ,
+.BR fwd ,
+and
+.B back
+primitives cannot be implemented,
+and yield a MathML \%\[lq]<merror>\[rq] message instead.
+.
+.
+.IP \[bu]
+The
+.B vcenter
+primitive is silently ignored,
+as centering on the math axis is the MathML default.
+.
+.
+.IP \[bu]
+Characters that
+.I @g@eqn
+sets extra large in
+.I troff \" mode
+mode\[em]notably the integral sign\[em]may appear too small and need to
+have their \[lq]<mstyle>\[rq] wrappers adjusted by hand.
+.
+.
+.P
+As in its
+.I troff \" mode
+mode,
+.I @g@eqn
+in MathML mode leaves the
+.B .EQ
+and
+.B .EN
+tokens in place,
+but emits nothing corresponding to
+.B \%delim
+delimiters.
+.
+They can,
+however,
+be recognized as character sequences that begin with \[lq]<math>\[rq],
+end with \[lq]</math>\[rq],
+and do not cross line boundaries.
+.
+.
+.\" ====================================================================
+.SH Caveats
+.\" ====================================================================
+.
+Tokens must be double-quoted in
+.I eqn \" generic
+input if they are not to be recognized as names of macros or primitives,
+or if they are to be interpreted by
+.IR troff . \" generic
+.
+In particular,
+short ones,
+like
+.RB \[lq] pi \[rq]
+and
+.RB \[lq] PI \[rq],
+can collide with
+.I troff \" generic
+identifiers.
+.
+For instance,
+the
+.I eqn \" generic
+command
+.RB \%\[lq]\^ "gfont PI" \^\[rq]
+does not select
+.IR groff 's
+Palatino italic font for the global italic face;
+you must use
+.RB \%\[lq]\^ "gfont \[dq]PI\[dq]" \^\[rq]
+instead.
+.
+.
+.P
+Delimited equations are set at the type size current at the beginning of
+the input line,
+not necessarily that immediately preceding the opening delimiter.
+.
+.
+.P
+Unlike \*[tx],
+.I eqn \" generic
+does not inherently distinguish displayed and inline equation styles;
+see the
+.B smallover
+primitive above.
+.
+However,
+macro packages frequently define
+.B EQ
+and
+.B EN
+macros such that the equation within is displayed.
+.
+These macros may accept arguments permitting the equation to be labeled
+or captioned;
+see the package's documentation.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.I eqn \" generic
+abuses terminology\[em]its
+\[lq]equations\[rq]
+can be inequalities,
+bare expressions,
+or unintelligible gibberish.
+.
+But there's no changing it now.
+.
+.
+.P
+In
+.I nroff \" mode
+mode,
+lowercase Greek letters are rendered in roman instead of italic style.
+.
+.
+.P
+In MathML mode,
+the
+.B mark
+and
+.B lineup
+features don't work.
+.
+These could,
+in theory,
+be implemented with \%\[lq]<maligngroup>\[rq] elements.
+.
+.
+.P
+In MathML mode,
+each digit of a numeric literal gets a separate \[lq]<mn>\:</mn>\[rq]
+pair,
+and decimal points are tagged with \[lq]<mo>\:</mo>\[rq].
+.
+This is allowed by the specification,
+but inefficient.
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+We first illustrate
+.I @g@eqn
+usage with a trigonometric identity.
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+sin ( alpha + beta ) = sin alpha cos beta + cos alpha sin beta
+\&.EN
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+sin ( alpha + beta ) = sin alpha cos beta + cos alpha sin beta
+.EN
+.RE
+.\}
+.RE
+.
+.
+.P
+It can be convenient to set up delimiters if mathematical content will
+appear frequently in running text.
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+delim $$
+\&.EN
+.
+Having cached a table of logarithms,
+the property $ln ( x y ) = ln x + ln y$ sped calculations.
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+delim $$
+.EN
+.
+Having cached a table of logarithms,
+the property $ln ( x y ) = ln x + ln y$ sped calculations.
+.
+.\" We _must_ shut delimiters back off when serially processing man
+.\" pages, or subsequent documents cannot safely use those characters.
+.EQ
+delim off
+.EN
+.RE
+.\}
+.RE
+.
+.
+.P
+The quadratic formula illustrates use of fractions and radicals,
+and affords an opportunity to use the full space token
+.BR \[ti] .
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+x = { \- b \[ti] \[rs][+\-] \[ti] sqrt { b sup 2 \- 4 a c } } \
+over { 2 a }
+\&.EN
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+x = { - b ~ \[+-] ~ sqrt { b sup 2 - 4 a c } } over { 2 a }
+.EN
+.RE
+.\}
+.RE
+.
+.
+.P
+Alternatively,
+we could define the plus-minus sign as a binary operator.
+.
+Automatic spacing puts 0.06\~em less space on either side of the
+plus-minus than \[ti] does,
+this being the difference between the widths of the
+.B medium_space
+parameter used by binary operators and that of the full space.
+.
+Independently,
+we can define a macro \[lq]frac\[rq] for setting fractions.
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+chartype "binary" \[rs][+\-]
+define frac ! { $1 } over { $2 } !
+x = frac(\- b \[rs][+\-] sqrt { b sup 2 \- 4 a c }, 2 a)
+\&.EN
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+chartype "binary" \[+-]
+define frac ! { $1 } over { $2 } !
+x = frac(- b \[+-] sqrt { b sup 2 - 4 a c }, 2 a)
+.EN
+.RE
+.\}
+.RE
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Typesetting Mathematics\[em]User's Guide\[rq]
+(2nd edition),
+by Brian W.\& Kernighan
+and Lorinda L.\& Cherry,
+1978,
+AT&T Bell Laboratories Computing Science Technical Report No.\& 17.
+.
+.
+.P
+.IR The\~\*[tx]book ,
+by Donald E.\& Knuth,
+1984,
+Addison-Wesley Professional.
+.
+Appendix\~G
+discusses many of the parameters from section \[lq]Customization\[rq]
+above in greater detail.
+.
+.
+.P
+.MR groff_char @MAN7EXT@ ,
+particularly subsections \[lq]Logical symbols\[rq],
+\[lq]Mathematical symbols\[rq],
+and \[lq]Greek glyphs\[rq],
+documents a variety of special character escape sequences useful in
+mathematical typesetting.
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR @g@pic @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm tx
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_eqn_1_man_C]
+.do rr *groff_eqn_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" tab-width: 12
+.\" End:
+.\" vim: set filetype=groff tabstop=12 textwidth=72:
diff --git a/src/preproc/eqn/eqn.am b/src/preproc/eqn/eqn.am
new file mode 100644
index 0000000..e32bfb5
--- /dev/null
+++ b/src/preproc/eqn/eqn.am
@@ -0,0 +1,79 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 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/>.
+
+prefixexecbin_PROGRAMS += eqn
+prefixexecbin_SCRIPTS += neqn
+eqn_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I $(top_srcdir)/src/preproc/eqn \
+ -I $(top_builddir)/src/preproc/eqn
+eqn_LDADD = $(LIBM) libgroff.a lib/libgnu.a
+eqn_SOURCES = \
+ src/preproc/eqn/main.cpp \
+ src/preproc/eqn/lex.cpp \
+ src/preproc/eqn/box.cpp \
+ src/preproc/eqn/limit.cpp \
+ src/preproc/eqn/list.cpp \
+ src/preproc/eqn/over.cpp \
+ src/preproc/eqn/text.cpp \
+ src/preproc/eqn/script.cpp \
+ src/preproc/eqn/mark.cpp \
+ src/preproc/eqn/other.cpp \
+ src/preproc/eqn/delim.cpp \
+ src/preproc/eqn/sqrt.cpp \
+ src/preproc/eqn/pile.cpp \
+ src/preproc/eqn/special.cpp \
+ src/preproc/eqn/eqn.ypp \
+ src/preproc/eqn/box.h \
+ src/preproc/eqn/pbox.h \
+ src/preproc/eqn/eqn.h
+
+PREFIXMAN1 += src/preproc/eqn/eqn.1 src/preproc/eqn/neqn.1
+EXTRA_DIST += \
+ src/preproc/eqn/TODO \
+ src/preproc/eqn/eqn.1.man \
+ src/preproc/eqn/neqn.1.man \
+ src/preproc/eqn/neqn.sh
+
+# Since eqn_CPPFLAGS was set, all .o files have an 'eqn-' prefix.
+src/preproc/eqn/eqn-lex.$(OBJEXT): src/preproc/eqn/eqn.hpp
+
+MAINTAINERCLEANFILES += \
+ src/preproc/eqn/eqn.hpp \
+ src/preproc/eqn/eqn.cpp \
+ src/preproc/eqn/eqn.output
+
+neqn: $(top_srcdir)/src/preproc/eqn/neqn.sh $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)sed -e 's/[@]g[@]/$(g)/g' \
+ -f $(SH_DEPS_SED_SCRIPT) \
+ -e $(SH_SCRIPT_SED_CMD) \
+ $(top_srcdir)/src/preproc/eqn/neqn.sh \
+ >$@.tmp \
+ && chmod +x $@.tmp \
+ && mv $@.tmp $@
+
+eqn_TESTS = \
+ src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh
+TESTS += $(eqn_TESTS)
+EXTRA_DIST += $(eqn_TESTS)
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/eqn/eqn.cpp b/src/preproc/eqn/eqn.cpp
new file mode 100644
index 0000000..6756b9e
--- /dev/null
+++ b/src/preproc/eqn/eqn.cpp
@@ -0,0 +1,2112 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program 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.
+
+ This program 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 <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30802
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.8.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 18 "../src/preproc/eqn/eqn.ypp"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "lib.h"
+#include "box.h"
+extern int non_empty_flag;
+int yylex();
+void yyerror(const char *);
+
+#line 87 "src/preproc/eqn/eqn.cpp"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Use api.header.include to #include this header
+ instead of duplicating it here. */
+#ifndef YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ OVER = 258, /* OVER */
+ SMALLOVER = 259, /* SMALLOVER */
+ SQRT = 260, /* SQRT */
+ SUB = 261, /* SUB */
+ SUP = 262, /* SUP */
+ LPILE = 263, /* LPILE */
+ RPILE = 264, /* RPILE */
+ CPILE = 265, /* CPILE */
+ PILE = 266, /* PILE */
+ LEFT = 267, /* LEFT */
+ RIGHT = 268, /* RIGHT */
+ TO = 269, /* TO */
+ FROM = 270, /* FROM */
+ SIZE = 271, /* SIZE */
+ FONT = 272, /* FONT */
+ ROMAN = 273, /* ROMAN */
+ BOLD = 274, /* BOLD */
+ ITALIC = 275, /* ITALIC */
+ FAT = 276, /* FAT */
+ ACCENT = 277, /* ACCENT */
+ BAR = 278, /* BAR */
+ UNDER = 279, /* UNDER */
+ ABOVE = 280, /* ABOVE */
+ TEXT = 281, /* TEXT */
+ QUOTED_TEXT = 282, /* QUOTED_TEXT */
+ FWD = 283, /* FWD */
+ BACK = 284, /* BACK */
+ DOWN = 285, /* DOWN */
+ UP = 286, /* UP */
+ MATRIX = 287, /* MATRIX */
+ COL = 288, /* COL */
+ LCOL = 289, /* LCOL */
+ RCOL = 290, /* RCOL */
+ CCOL = 291, /* CCOL */
+ MARK = 292, /* MARK */
+ LINEUP = 293, /* LINEUP */
+ TYPE = 294, /* TYPE */
+ VCENTER = 295, /* VCENTER */
+ PRIME = 296, /* PRIME */
+ SPLIT = 297, /* SPLIT */
+ NOSPLIT = 298, /* NOSPLIT */
+ UACCENT = 299, /* UACCENT */
+ SPECIAL = 300, /* SPECIAL */
+ SPACE = 301, /* SPACE */
+ GFONT = 302, /* GFONT */
+ GSIZE = 303, /* GSIZE */
+ DEFINE = 304, /* DEFINE */
+ NDEFINE = 305, /* NDEFINE */
+ TDEFINE = 306, /* TDEFINE */
+ SDEFINE = 307, /* SDEFINE */
+ UNDEF = 308, /* UNDEF */
+ IFDEF = 309, /* IFDEF */
+ INCLUDE = 310, /* INCLUDE */
+ DELIM = 311, /* DELIM */
+ CHARTYPE = 312, /* CHARTYPE */
+ SET = 313, /* SET */
+ GRFONT = 314, /* GRFONT */
+ GBFONT = 315 /* GBFONT */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define OVER 258
+#define SMALLOVER 259
+#define SQRT 260
+#define SUB 261
+#define SUP 262
+#define LPILE 263
+#define RPILE 264
+#define CPILE 265
+#define PILE 266
+#define LEFT 267
+#define RIGHT 268
+#define TO 269
+#define FROM 270
+#define SIZE 271
+#define FONT 272
+#define ROMAN 273
+#define BOLD 274
+#define ITALIC 275
+#define FAT 276
+#define ACCENT 277
+#define BAR 278
+#define UNDER 279
+#define ABOVE 280
+#define TEXT 281
+#define QUOTED_TEXT 282
+#define FWD 283
+#define BACK 284
+#define DOWN 285
+#define UP 286
+#define MATRIX 287
+#define COL 288
+#define LCOL 289
+#define RCOL 290
+#define CCOL 291
+#define MARK 292
+#define LINEUP 293
+#define TYPE 294
+#define VCENTER 295
+#define PRIME 296
+#define SPLIT 297
+#define NOSPLIT 298
+#define UACCENT 299
+#define SPECIAL 300
+#define SPACE 301
+#define GFONT 302
+#define GSIZE 303
+#define DEFINE 304
+#define NDEFINE 305
+#define TDEFINE 306
+#define SDEFINE 307
+#define UNDEF 308
+#define IFDEF 309
+#define INCLUDE 310
+#define DELIM 311
+#define CHARTYPE 312
+#define SET 313
+#define GRFONT 314
+#define GBFONT 315
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 34 "../src/preproc/eqn/eqn.ypp"
+
+ char *str;
+ box *b;
+ pile_box *pb;
+ matrix_box *mb;
+ int n;
+ column *col;
+
+#line 269 "src/preproc/eqn/eqn.cpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED */
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_OVER = 3, /* OVER */
+ YYSYMBOL_SMALLOVER = 4, /* SMALLOVER */
+ YYSYMBOL_SQRT = 5, /* SQRT */
+ YYSYMBOL_SUB = 6, /* SUB */
+ YYSYMBOL_SUP = 7, /* SUP */
+ YYSYMBOL_LPILE = 8, /* LPILE */
+ YYSYMBOL_RPILE = 9, /* RPILE */
+ YYSYMBOL_CPILE = 10, /* CPILE */
+ YYSYMBOL_PILE = 11, /* PILE */
+ YYSYMBOL_LEFT = 12, /* LEFT */
+ YYSYMBOL_RIGHT = 13, /* RIGHT */
+ YYSYMBOL_TO = 14, /* TO */
+ YYSYMBOL_FROM = 15, /* FROM */
+ YYSYMBOL_SIZE = 16, /* SIZE */
+ YYSYMBOL_FONT = 17, /* FONT */
+ YYSYMBOL_ROMAN = 18, /* ROMAN */
+ YYSYMBOL_BOLD = 19, /* BOLD */
+ YYSYMBOL_ITALIC = 20, /* ITALIC */
+ YYSYMBOL_FAT = 21, /* FAT */
+ YYSYMBOL_ACCENT = 22, /* ACCENT */
+ YYSYMBOL_BAR = 23, /* BAR */
+ YYSYMBOL_UNDER = 24, /* UNDER */
+ YYSYMBOL_ABOVE = 25, /* ABOVE */
+ YYSYMBOL_TEXT = 26, /* TEXT */
+ YYSYMBOL_QUOTED_TEXT = 27, /* QUOTED_TEXT */
+ YYSYMBOL_FWD = 28, /* FWD */
+ YYSYMBOL_BACK = 29, /* BACK */
+ YYSYMBOL_DOWN = 30, /* DOWN */
+ YYSYMBOL_UP = 31, /* UP */
+ YYSYMBOL_MATRIX = 32, /* MATRIX */
+ YYSYMBOL_COL = 33, /* COL */
+ YYSYMBOL_LCOL = 34, /* LCOL */
+ YYSYMBOL_RCOL = 35, /* RCOL */
+ YYSYMBOL_CCOL = 36, /* CCOL */
+ YYSYMBOL_MARK = 37, /* MARK */
+ YYSYMBOL_LINEUP = 38, /* LINEUP */
+ YYSYMBOL_TYPE = 39, /* TYPE */
+ YYSYMBOL_VCENTER = 40, /* VCENTER */
+ YYSYMBOL_PRIME = 41, /* PRIME */
+ YYSYMBOL_SPLIT = 42, /* SPLIT */
+ YYSYMBOL_NOSPLIT = 43, /* NOSPLIT */
+ YYSYMBOL_UACCENT = 44, /* UACCENT */
+ YYSYMBOL_SPECIAL = 45, /* SPECIAL */
+ YYSYMBOL_SPACE = 46, /* SPACE */
+ YYSYMBOL_GFONT = 47, /* GFONT */
+ YYSYMBOL_GSIZE = 48, /* GSIZE */
+ YYSYMBOL_DEFINE = 49, /* DEFINE */
+ YYSYMBOL_NDEFINE = 50, /* NDEFINE */
+ YYSYMBOL_TDEFINE = 51, /* TDEFINE */
+ YYSYMBOL_SDEFINE = 52, /* SDEFINE */
+ YYSYMBOL_UNDEF = 53, /* UNDEF */
+ YYSYMBOL_IFDEF = 54, /* IFDEF */
+ YYSYMBOL_INCLUDE = 55, /* INCLUDE */
+ YYSYMBOL_DELIM = 56, /* DELIM */
+ YYSYMBOL_CHARTYPE = 57, /* CHARTYPE */
+ YYSYMBOL_SET = 58, /* SET */
+ YYSYMBOL_GRFONT = 59, /* GRFONT */
+ YYSYMBOL_GBFONT = 60, /* GBFONT */
+ YYSYMBOL_61_ = 61, /* '^' */
+ YYSYMBOL_62_ = 62, /* '~' */
+ YYSYMBOL_63_t_ = 63, /* '\t' */
+ YYSYMBOL_64_ = 64, /* '{' */
+ YYSYMBOL_65_ = 65, /* '}' */
+ YYSYMBOL_YYACCEPT = 66, /* $accept */
+ YYSYMBOL_top = 67, /* top */
+ YYSYMBOL_equation = 68, /* equation */
+ YYSYMBOL_mark = 69, /* mark */
+ YYSYMBOL_from_to = 70, /* from_to */
+ YYSYMBOL_sqrt_over = 71, /* sqrt_over */
+ YYSYMBOL_script = 72, /* script */
+ YYSYMBOL_nonsup = 73, /* nonsup */
+ YYSYMBOL_simple = 74, /* simple */
+ YYSYMBOL_number = 75, /* number */
+ YYSYMBOL_pile_element_list = 76, /* pile_element_list */
+ YYSYMBOL_pile_arg = 77, /* pile_arg */
+ YYSYMBOL_column_list = 78, /* column_list */
+ YYSYMBOL_column_element_list = 79, /* column_element_list */
+ YYSYMBOL_column_arg = 80, /* column_arg */
+ YYSYMBOL_column = 81, /* column */
+ YYSYMBOL_text = 82, /* text */
+ YYSYMBOL_delim = 83 /* delim */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_uint8 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 72
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 379
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 66
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 18
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 75
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 142
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 315
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_int8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 63,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 61, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 64, 2, 65, 62, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60
+};
+
+#if YYDEBUG
+/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 125, 125, 127, 132, 134, 145, 147, 149, 154,
+ 156, 158, 160, 162, 167, 169, 171, 173, 178, 180,
+ 185, 187, 189, 194, 196, 198, 200, 202, 204, 206,
+ 208, 210, 212, 214, 216, 218, 220, 222, 224, 226,
+ 228, 230, 232, 234, 236, 238, 240, 242, 244, 246,
+ 248, 250, 252, 254, 256, 258, 263, 273, 275, 280,
+ 282, 287, 289, 294, 296, 301, 303, 308, 310, 312,
+ 314, 318, 320, 325, 327, 329
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if YYDEBUG || 0
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "OVER", "SMALLOVER",
+ "SQRT", "SUB", "SUP", "LPILE", "RPILE", "CPILE", "PILE", "LEFT", "RIGHT",
+ "TO", "FROM", "SIZE", "FONT", "ROMAN", "BOLD", "ITALIC", "FAT", "ACCENT",
+ "BAR", "UNDER", "ABOVE", "TEXT", "QUOTED_TEXT", "FWD", "BACK", "DOWN",
+ "UP", "MATRIX", "COL", "LCOL", "RCOL", "CCOL", "MARK", "LINEUP", "TYPE",
+ "VCENTER", "PRIME", "SPLIT", "NOSPLIT", "UACCENT", "SPECIAL", "SPACE",
+ "GFONT", "GSIZE", "DEFINE", "NDEFINE", "TDEFINE", "SDEFINE", "UNDEF",
+ "IFDEF", "INCLUDE", "DELIM", "CHARTYPE", "SET", "GRFONT", "GBFONT",
+ "'^'", "'~'", "'\\t'", "'{'", "'}'", "$accept", "top", "equation",
+ "mark", "from_to", "sqrt_over", "script", "nonsup", "simple", "number",
+ "pile_element_list", "pile_arg", "column_list", "column_element_list",
+ "column_arg", "column", "text", "delim", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#define YYPACT_NINF (-76)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-1)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int16 yypact[] =
+{
+ 230, 269, 6, 6, 6, 6, 2, 14, 14, 308,
+ 308, 308, 308, -76, -76, 14, 14, 14, 14, -50,
+ 230, 230, 14, 308, 4, 23, 14, -76, -76, -76,
+ 230, 24, 230, -76, -76, 70, -76, -76, 20, -76,
+ -76, -76, 230, -44, -76, -76, -76, -76, -76, -76,
+ -76, -76, 230, 308, 308, 57, 57, 57, 57, 308,
+ 308, 308, 308, 3, -76, -76, 308, 57, -76, -76,
+ 308, 130, -76, -76, 269, 269, 269, 269, 308, 308,
+ 308, -76, -76, -76, 308, 230, -12, 230, 191, 57,
+ 57, 57, 57, 57, 57, 8, 8, 8, 8, 12,
+ -76, 57, 57, -76, -76, -76, -76, 79, -76, 335,
+ -76, -76, -76, 230, -76, -6, 2, 230, 28, -76,
+ -76, -76, -76, -76, -76, 269, 269, 308, 230, -76,
+ -76, 230, -3, 230, -76, -76, -76, 230, -76, -2,
+ 230, -76
+};
+
+/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int8 yydefact[] =
+{
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 23, 24, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 27, 28, 29,
+ 0, 0, 3, 4, 6, 9, 14, 18, 20, 15,
+ 71, 72, 0, 0, 32, 56, 33, 34, 31, 74,
+ 75, 73, 0, 0, 0, 43, 44, 45, 46, 0,
+ 0, 0, 0, 0, 7, 8, 0, 54, 25, 26,
+ 0, 0, 1, 5, 0, 0, 0, 0, 0, 0,
+ 0, 38, 39, 40, 0, 57, 0, 0, 37, 48,
+ 47, 49, 50, 52, 51, 0, 0, 0, 0, 0,
+ 61, 53, 55, 30, 16, 17, 10, 11, 21, 20,
+ 19, 41, 42, 0, 59, 0, 0, 0, 0, 67,
+ 68, 69, 70, 35, 62, 0, 0, 0, 58, 60,
+ 36, 63, 0, 0, 12, 13, 22, 0, 65, 0,
+ 64, 66
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -76, -76, 0, -17, -75, 1, -67, -13, 46, -7,
+ 9, 13, -76, -47, 22, -4, -1, -29
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_uint8 yydefgoto[] =
+{
+ 0, 31, 85, 33, 34, 35, 36, 37, 38, 43,
+ 86, 44, 99, 132, 119, 100, 45, 52
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_uint8 yytable[] =
+{
+ 32, 106, 39, 64, 65, 51, 53, 54, 59, 60,
+ 61, 62, 110, 113, 63, 73, 46, 47, 48, 113,
+ 87, 66, 137, 137, 72, 70, 78, 79, 40, 41,
+ 71, 68, 40, 41, 40, 41, 95, 96, 97, 98,
+ 40, 41, 80, 81, 82, 95, 96, 97, 98, 69,
+ 134, 135, 88, 114, 73, 55, 56, 57, 58, 129,
+ 136, 83, 138, 141, 84, 108, 49, 50, 73, 67,
+ 42, 73, 117, 74, 75, 104, 105, 123, 107, 80,
+ 81, 82, 74, 75, 76, 77, 139, 130, 118, 118,
+ 118, 118, 133, 125, 126, 124, 115, 0, 83, 89,
+ 90, 84, 0, 0, 0, 91, 92, 93, 94, 0,
+ 0, 73, 101, 128, 73, 51, 102, 131, 120, 121,
+ 122, 0, 0, 73, 109, 0, 111, 0, 0, 0,
+ 112, 0, 0, 131, 0, 1, 0, 140, 2, 3,
+ 4, 5, 6, 0, 0, 0, 7, 8, 9, 10,
+ 11, 12, 0, 0, 0, 0, 13, 14, 15, 16,
+ 17, 18, 19, 0, 0, 0, 0, 20, 21, 22,
+ 23, 0, 24, 25, 0, 26, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 27, 28, 29, 30, 103, 1, 0, 0, 2,
+ 3, 4, 5, 6, 116, 0, 0, 7, 8, 9,
+ 10, 11, 12, 0, 0, 0, 0, 13, 14, 15,
+ 16, 17, 18, 19, 0, 0, 0, 0, 20, 21,
+ 22, 23, 0, 24, 25, 1, 26, 0, 2, 3,
+ 4, 5, 6, 0, 0, 0, 7, 8, 9, 10,
+ 11, 12, 27, 28, 29, 30, 13, 14, 15, 16,
+ 17, 18, 19, 0, 0, 0, 0, 20, 21, 22,
+ 23, 0, 24, 25, 1, 26, 0, 2, 3, 4,
+ 5, 6, 0, 0, 0, 7, 8, 9, 10, 11,
+ 12, 27, 28, 29, 30, 13, 14, 15, 16, 17,
+ 18, 19, 0, 0, 0, 0, 0, 0, 22, 23,
+ 0, 24, 25, 0, 26, 0, 2, 3, 4, 5,
+ 6, 0, 0, 0, 7, 8, 9, 10, 11, 12,
+ 27, 28, 29, 30, 13, 14, 15, 16, 17, 18,
+ 19, 78, 127, 0, 0, 0, 0, 22, 23, 0,
+ 24, 25, 0, 26, 0, 0, 0, 80, 81, 82,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 27,
+ 28, 29, 30, 0, 0, 0, 83, 0, 0, 84
+};
+
+static const yytype_int16 yycheck[] =
+{
+ 0, 76, 1, 20, 21, 6, 7, 8, 15, 16,
+ 17, 18, 79, 25, 64, 32, 3, 4, 5, 25,
+ 64, 22, 25, 25, 0, 26, 6, 7, 26, 27,
+ 30, 27, 26, 27, 26, 27, 33, 34, 35, 36,
+ 26, 27, 22, 23, 24, 33, 34, 35, 36, 26,
+ 125, 126, 52, 65, 71, 9, 10, 11, 12, 65,
+ 127, 41, 65, 65, 44, 78, 64, 65, 85, 23,
+ 64, 88, 64, 3, 4, 74, 75, 65, 77, 22,
+ 23, 24, 3, 4, 14, 15, 133, 116, 95, 96,
+ 97, 98, 64, 14, 15, 99, 87, -1, 41, 53,
+ 54, 44, -1, -1, -1, 59, 60, 61, 62, -1,
+ -1, 128, 66, 113, 131, 116, 70, 117, 96, 97,
+ 98, -1, -1, 140, 78, -1, 80, -1, -1, -1,
+ 84, -1, -1, 133, -1, 5, -1, 137, 8, 9,
+ 10, 11, 12, -1, -1, -1, 16, 17, 18, 19,
+ 20, 21, -1, -1, -1, -1, 26, 27, 28, 29,
+ 30, 31, 32, -1, -1, -1, -1, 37, 38, 39,
+ 40, -1, 42, 43, -1, 45, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 61, 62, 63, 64, 65, 5, -1, -1, 8,
+ 9, 10, 11, 12, 13, -1, -1, 16, 17, 18,
+ 19, 20, 21, -1, -1, -1, -1, 26, 27, 28,
+ 29, 30, 31, 32, -1, -1, -1, -1, 37, 38,
+ 39, 40, -1, 42, 43, 5, 45, -1, 8, 9,
+ 10, 11, 12, -1, -1, -1, 16, 17, 18, 19,
+ 20, 21, 61, 62, 63, 64, 26, 27, 28, 29,
+ 30, 31, 32, -1, -1, -1, -1, 37, 38, 39,
+ 40, -1, 42, 43, 5, 45, -1, 8, 9, 10,
+ 11, 12, -1, -1, -1, 16, 17, 18, 19, 20,
+ 21, 61, 62, 63, 64, 26, 27, 28, 29, 30,
+ 31, 32, -1, -1, -1, -1, -1, -1, 39, 40,
+ -1, 42, 43, -1, 45, -1, 8, 9, 10, 11,
+ 12, -1, -1, -1, 16, 17, 18, 19, 20, 21,
+ 61, 62, 63, 64, 26, 27, 28, 29, 30, 31,
+ 32, 6, 7, -1, -1, -1, -1, 39, 40, -1,
+ 42, 43, -1, 45, -1, -1, -1, 22, 23, 24,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 61,
+ 62, 63, 64, -1, -1, -1, 41, -1, -1, 44
+};
+
+/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ state STATE-NUM. */
+static const yytype_int8 yystos[] =
+{
+ 0, 5, 8, 9, 10, 11, 12, 16, 17, 18,
+ 19, 20, 21, 26, 27, 28, 29, 30, 31, 32,
+ 37, 38, 39, 40, 42, 43, 45, 61, 62, 63,
+ 64, 67, 68, 69, 70, 71, 72, 73, 74, 71,
+ 26, 27, 64, 75, 77, 82, 77, 77, 77, 64,
+ 65, 82, 83, 82, 82, 74, 74, 74, 74, 75,
+ 75, 75, 75, 64, 69, 69, 82, 74, 27, 26,
+ 82, 68, 0, 69, 3, 4, 14, 15, 6, 7,
+ 22, 23, 24, 41, 44, 68, 76, 64, 68, 74,
+ 74, 74, 74, 74, 74, 33, 34, 35, 36, 78,
+ 81, 74, 74, 65, 71, 71, 70, 71, 73, 74,
+ 72, 74, 74, 25, 65, 76, 13, 64, 75, 80,
+ 80, 80, 80, 65, 81, 14, 15, 7, 68, 65,
+ 83, 68, 79, 64, 70, 70, 72, 25, 65, 79,
+ 68, 65
+};
+
+/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr1[] =
+{
+ 0, 66, 67, 67, 68, 68, 69, 69, 69, 70,
+ 70, 70, 70, 70, 71, 71, 71, 71, 72, 72,
+ 73, 73, 73, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 75, 76, 76, 77,
+ 77, 78, 78, 79, 79, 80, 80, 81, 81, 81,
+ 81, 82, 82, 83, 83, 83
+};
+
+/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 0, 1, 1, 2, 1, 2, 2, 1,
+ 3, 3, 5, 5, 1, 2, 3, 3, 1, 3,
+ 1, 3, 5, 1, 1, 2, 2, 1, 1, 1,
+ 3, 2, 2, 2, 2, 4, 5, 3, 2, 2,
+ 2, 3, 3, 2, 2, 2, 2, 3, 3, 3,
+ 3, 3, 3, 3, 2, 3, 1, 1, 3, 3,
+ 4, 1, 2, 1, 3, 3, 4, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYNOMEM goto yyexhaustedlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ YYNOMEM;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ YYNOMEM;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ YYNOMEM;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 3: /* top: equation */
+#line 128 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].b)->top_level(); non_empty_flag = 1; }
+#line 1472 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 4: /* equation: mark */
+#line 133 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1478 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 5: /* equation: equation mark */
+#line 135 "../src/preproc/eqn/eqn.ypp"
+ {
+ list_box *lb = (yyvsp[-1].b)->to_list_box();
+ if (!lb)
+ lb = new list_box((yyvsp[-1].b));
+ lb->append((yyvsp[0].b));
+ (yyval.b) = lb;
+ }
+#line 1490 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 6: /* mark: from_to */
+#line 146 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1496 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 7: /* mark: MARK mark */
+#line 148 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_mark_box((yyvsp[0].b)); }
+#line 1502 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 8: /* mark: LINEUP mark */
+#line 150 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_lineup_box((yyvsp[0].b)); }
+#line 1508 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 9: /* from_to: sqrt_over */
+#line 155 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1514 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 10: /* from_to: sqrt_over TO from_to */
+#line 157 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-2].b), 0, (yyvsp[0].b)); }
+#line 1520 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 11: /* from_to: sqrt_over FROM sqrt_over */
+#line 159 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-2].b), (yyvsp[0].b), 0); }
+#line 1526 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 12: /* from_to: sqrt_over FROM sqrt_over TO from_to */
+#line 161 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-4].b), (yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1532 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 13: /* from_to: sqrt_over FROM sqrt_over FROM from_to */
+#line 163 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-4].b), make_limit_box((yyvsp[-2].b), (yyvsp[0].b), 0), 0); }
+#line 1538 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 14: /* sqrt_over: script */
+#line 168 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1544 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 15: /* sqrt_over: SQRT sqrt_over */
+#line 170 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_sqrt_box((yyvsp[0].b)); }
+#line 1550 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 16: /* sqrt_over: sqrt_over OVER sqrt_over */
+#line 172 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_over_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1556 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 17: /* sqrt_over: sqrt_over SMALLOVER sqrt_over */
+#line 174 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_small_over_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1562 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 18: /* script: nonsup */
+#line 179 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1568 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 19: /* script: simple SUP script */
+#line 181 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_script_box((yyvsp[-2].b), 0, (yyvsp[0].b)); }
+#line 1574 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 20: /* nonsup: simple */
+#line 186 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1580 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 21: /* nonsup: simple SUB nonsup */
+#line 188 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_script_box((yyvsp[-2].b), (yyvsp[0].b), 0); }
+#line 1586 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 22: /* nonsup: simple SUB simple SUP script */
+#line 190 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_script_box((yyvsp[-4].b), (yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1592 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 23: /* simple: TEXT */
+#line 195 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = split_text((yyvsp[0].str)); }
+#line 1598 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 24: /* simple: QUOTED_TEXT */
+#line 197 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new quoted_text_box((yyvsp[0].str)); }
+#line 1604 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 25: /* simple: SPLIT QUOTED_TEXT */
+#line 199 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = split_text((yyvsp[0].str)); }
+#line 1610 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 26: /* simple: NOSPLIT TEXT */
+#line 201 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new quoted_text_box((yyvsp[0].str)); }
+#line 1616 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 27: /* simple: '^' */
+#line 203 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new half_space_box; }
+#line 1622 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 28: /* simple: '~' */
+#line 205 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new space_box; }
+#line 1628 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 29: /* simple: '\t' */
+#line 207 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new tab_box; }
+#line 1634 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 30: /* simple: '{' equation '}' */
+#line 209 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[-1].b); }
+#line 1640 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 31: /* simple: PILE pile_arg */
+#line 211 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(CENTER_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1646 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 32: /* simple: LPILE pile_arg */
+#line 213 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(LEFT_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1652 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 33: /* simple: RPILE pile_arg */
+#line 215 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(RIGHT_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1658 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 34: /* simple: CPILE pile_arg */
+#line 217 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(CENTER_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1664 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 35: /* simple: MATRIX '{' column_list '}' */
+#line 219 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[-1].mb); }
+#line 1670 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 36: /* simple: LEFT delim equation RIGHT delim */
+#line 221 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_delim_box((yyvsp[-3].str), (yyvsp[-2].b), (yyvsp[0].str)); }
+#line 1676 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 37: /* simple: LEFT delim equation */
+#line 223 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_delim_box((yyvsp[-1].str), (yyvsp[0].b), 0); }
+#line 1682 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 38: /* simple: simple BAR */
+#line 225 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_overline_box((yyvsp[-1].b)); }
+#line 1688 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 39: /* simple: simple UNDER */
+#line 227 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_underline_box((yyvsp[-1].b)); }
+#line 1694 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 40: /* simple: simple PRIME */
+#line 229 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_prime_box((yyvsp[-1].b)); }
+#line 1700 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 41: /* simple: simple ACCENT simple */
+#line 231 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_accent_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1706 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 42: /* simple: simple UACCENT simple */
+#line 233 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_uaccent_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1712 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 43: /* simple: ROMAN simple */
+#line 235 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box(strsave(get_grfont()), (yyvsp[0].b)); }
+#line 1718 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 44: /* simple: BOLD simple */
+#line 237 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box(strsave(get_gbfont()), (yyvsp[0].b)); }
+#line 1724 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 45: /* simple: ITALIC simple */
+#line 239 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box(strsave(get_gfont()), (yyvsp[0].b)); }
+#line 1730 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 46: /* simple: FAT simple */
+#line 241 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new fat_box((yyvsp[0].b)); }
+#line 1736 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 47: /* simple: FONT text simple */
+#line 243 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box((yyvsp[-1].str), (yyvsp[0].b)); }
+#line 1742 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 48: /* simple: SIZE text simple */
+#line 245 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new size_box((yyvsp[-1].str), (yyvsp[0].b)); }
+#line 1748 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 49: /* simple: FWD number simple */
+#line 247 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new hmotion_box((yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1754 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 50: /* simple: BACK number simple */
+#line 249 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new hmotion_box(-(yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1760 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 51: /* simple: UP number simple */
+#line 251 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new vmotion_box((yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1766 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 52: /* simple: DOWN number simple */
+#line 253 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new vmotion_box(-(yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1772 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 53: /* simple: TYPE text simple */
+#line 255 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].b)->set_spacing_type((yyvsp[-1].str)); (yyval.b) = (yyvsp[0].b); }
+#line 1778 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 54: /* simple: VCENTER simple */
+#line 257 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new vcenter_box((yyvsp[0].b)); }
+#line 1784 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 55: /* simple: SPECIAL text simple */
+#line 259 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_special_box((yyvsp[-1].str), (yyvsp[0].b)); }
+#line 1790 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 56: /* number: text */
+#line 264 "../src/preproc/eqn/eqn.ypp"
+ {
+ int n;
+ if (sscanf((yyvsp[0].str), "%d", &n) == 1)
+ (yyval.n) = n;
+ delete[] (yyvsp[0].str);
+ }
+#line 1801 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 57: /* pile_element_list: equation */
+#line 274 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.pb) = new pile_box((yyvsp[0].b)); }
+#line 1807 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 58: /* pile_element_list: pile_element_list ABOVE equation */
+#line 276 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-2].pb)->append((yyvsp[0].b)); (yyval.pb) = (yyvsp[-2].pb); }
+#line 1813 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 59: /* pile_arg: '{' pile_element_list '}' */
+#line 281 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.pb) = (yyvsp[-1].pb); }
+#line 1819 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 60: /* pile_arg: number '{' pile_element_list '}' */
+#line 283 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-1].pb)->set_space((yyvsp[-3].n)); (yyval.pb) = (yyvsp[-1].pb); }
+#line 1825 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 61: /* column_list: column */
+#line 288 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.mb) = new matrix_box((yyvsp[0].col)); }
+#line 1831 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 62: /* column_list: column_list column */
+#line 290 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-1].mb)->append((yyvsp[0].col)); (yyval.mb) = (yyvsp[-1].mb); }
+#line 1837 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 63: /* column_element_list: equation */
+#line 295 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.col) = new column((yyvsp[0].b)); }
+#line 1843 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 64: /* column_element_list: column_element_list ABOVE equation */
+#line 297 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-2].col)->append((yyvsp[0].b)); (yyval.col) = (yyvsp[-2].col); }
+#line 1849 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 65: /* column_arg: '{' column_element_list '}' */
+#line 302 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.col) = (yyvsp[-1].col); }
+#line 1855 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 66: /* column_arg: number '{' column_element_list '}' */
+#line 304 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-1].col)->set_space((yyvsp[-3].n)); (yyval.col) = (yyvsp[-1].col); }
+#line 1861 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 67: /* column: COL column_arg */
+#line 309 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(CENTER_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1867 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 68: /* column: LCOL column_arg */
+#line 311 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(LEFT_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1873 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 69: /* column: RCOL column_arg */
+#line 313 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(RIGHT_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1879 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 70: /* column: CCOL column_arg */
+#line 315 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(CENTER_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1885 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 71: /* text: TEXT */
+#line 319 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1891 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 72: /* text: QUOTED_TEXT */
+#line 321 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1897 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 73: /* delim: text */
+#line 326 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1903 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 74: /* delim: '{' */
+#line 328 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = strsave("{"); }
+#line 1909 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 75: /* delim: '}' */
+#line 330 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = strsave("}"); }
+#line 1915 "src/preproc/eqn/eqn.cpp"
+ break;
+
+
+#line 1919 "src/preproc/eqn/eqn.cpp"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+ ++yynerrs;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturnlab;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturnlab;
+
+
+/*-----------------------------------------------------------.
+| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. |
+`-----------------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturnlab;
+
+
+/*----------------------------------------------------------.
+| yyreturnlab -- parsing is finished, clean up and return. |
+`----------------------------------------------------------*/
+yyreturnlab:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 333 "../src/preproc/eqn/eqn.ypp"
+
diff --git a/src/preproc/eqn/eqn.h b/src/preproc/eqn/eqn.h
new file mode 100644
index 0000000..a4143cb
--- /dev/null
+++ b/src/preproc/eqn/eqn.h
@@ -0,0 +1,57 @@
+/* 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 "lib.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include "cset.h"
+#include "errarg.h"
+#include "error.h"
+
+#include "box.h"
+
+typedef enum {troff, mathml} eqnmode_t;
+
+extern char start_delim;
+extern char end_delim;
+extern int non_empty_flag;
+extern int inline_flag;
+extern int draw_flag;
+extern int one_size_reduction_flag;
+extern int compatible_flag;
+extern int nroff;
+extern eqnmode_t output_format;
+extern int xhtml;
+
+void init_lex(const char *str, const char *filename, int lineno);
+void lex_error(const char *message,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+
+void init_table(const char *device);
+
+// prefix for all registers, strings, macros
+#define PREFIX "0"
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/eqn.hpp b/src/preproc/eqn/eqn.hpp
new file mode 100644
index 0000000..de02d7c
--- /dev/null
+++ b/src/preproc/eqn/eqn.hpp
@@ -0,0 +1,210 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison interface for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program 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.
+
+ This program 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 <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+#ifndef YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ OVER = 258, /* OVER */
+ SMALLOVER = 259, /* SMALLOVER */
+ SQRT = 260, /* SQRT */
+ SUB = 261, /* SUB */
+ SUP = 262, /* SUP */
+ LPILE = 263, /* LPILE */
+ RPILE = 264, /* RPILE */
+ CPILE = 265, /* CPILE */
+ PILE = 266, /* PILE */
+ LEFT = 267, /* LEFT */
+ RIGHT = 268, /* RIGHT */
+ TO = 269, /* TO */
+ FROM = 270, /* FROM */
+ SIZE = 271, /* SIZE */
+ FONT = 272, /* FONT */
+ ROMAN = 273, /* ROMAN */
+ BOLD = 274, /* BOLD */
+ ITALIC = 275, /* ITALIC */
+ FAT = 276, /* FAT */
+ ACCENT = 277, /* ACCENT */
+ BAR = 278, /* BAR */
+ UNDER = 279, /* UNDER */
+ ABOVE = 280, /* ABOVE */
+ TEXT = 281, /* TEXT */
+ QUOTED_TEXT = 282, /* QUOTED_TEXT */
+ FWD = 283, /* FWD */
+ BACK = 284, /* BACK */
+ DOWN = 285, /* DOWN */
+ UP = 286, /* UP */
+ MATRIX = 287, /* MATRIX */
+ COL = 288, /* COL */
+ LCOL = 289, /* LCOL */
+ RCOL = 290, /* RCOL */
+ CCOL = 291, /* CCOL */
+ MARK = 292, /* MARK */
+ LINEUP = 293, /* LINEUP */
+ TYPE = 294, /* TYPE */
+ VCENTER = 295, /* VCENTER */
+ PRIME = 296, /* PRIME */
+ SPLIT = 297, /* SPLIT */
+ NOSPLIT = 298, /* NOSPLIT */
+ UACCENT = 299, /* UACCENT */
+ SPECIAL = 300, /* SPECIAL */
+ SPACE = 301, /* SPACE */
+ GFONT = 302, /* GFONT */
+ GSIZE = 303, /* GSIZE */
+ DEFINE = 304, /* DEFINE */
+ NDEFINE = 305, /* NDEFINE */
+ TDEFINE = 306, /* TDEFINE */
+ SDEFINE = 307, /* SDEFINE */
+ UNDEF = 308, /* UNDEF */
+ IFDEF = 309, /* IFDEF */
+ INCLUDE = 310, /* INCLUDE */
+ DELIM = 311, /* DELIM */
+ CHARTYPE = 312, /* CHARTYPE */
+ SET = 313, /* SET */
+ GRFONT = 314, /* GRFONT */
+ GBFONT = 315 /* GBFONT */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define OVER 258
+#define SMALLOVER 259
+#define SQRT 260
+#define SUB 261
+#define SUP 262
+#define LPILE 263
+#define RPILE 264
+#define CPILE 265
+#define PILE 266
+#define LEFT 267
+#define RIGHT 268
+#define TO 269
+#define FROM 270
+#define SIZE 271
+#define FONT 272
+#define ROMAN 273
+#define BOLD 274
+#define ITALIC 275
+#define FAT 276
+#define ACCENT 277
+#define BAR 278
+#define UNDER 279
+#define ABOVE 280
+#define TEXT 281
+#define QUOTED_TEXT 282
+#define FWD 283
+#define BACK 284
+#define DOWN 285
+#define UP 286
+#define MATRIX 287
+#define COL 288
+#define LCOL 289
+#define RCOL 290
+#define CCOL 291
+#define MARK 292
+#define LINEUP 293
+#define TYPE 294
+#define VCENTER 295
+#define PRIME 296
+#define SPLIT 297
+#define NOSPLIT 298
+#define UACCENT 299
+#define SPECIAL 300
+#define SPACE 301
+#define GFONT 302
+#define GSIZE 303
+#define DEFINE 304
+#define NDEFINE 305
+#define TDEFINE 306
+#define SDEFINE 307
+#define UNDEF 308
+#define IFDEF 309
+#define INCLUDE 310
+#define DELIM 311
+#define CHARTYPE 312
+#define SET 313
+#define GRFONT 314
+#define GBFONT 315
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 34 "../src/preproc/eqn/eqn.ypp"
+
+ char *str;
+ box *b;
+ pile_box *pb;
+ matrix_box *mb;
+ int n;
+ column *col;
+
+#line 196 "src/preproc/eqn/eqn.hpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED */
diff --git a/src/preproc/eqn/eqn.ypp b/src/preproc/eqn/eqn.ypp
new file mode 100644
index 0000000..a22ad59
--- /dev/null
+++ b/src/preproc/eqn/eqn.ypp
@@ -0,0 +1,333 @@
+/* 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/>. */
+%{
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "lib.h"
+#include "box.h"
+extern int non_empty_flag;
+int yylex();
+void yyerror(const char *);
+%}
+
+%union {
+ char *str;
+ box *b;
+ pile_box *pb;
+ matrix_box *mb;
+ int n;
+ column *col;
+}
+
+%token OVER
+%token SMALLOVER
+%token SQRT
+%token SUB
+%token SUP
+%token LPILE
+%token RPILE
+%token CPILE
+%token PILE
+%token LEFT
+%token RIGHT
+%token TO
+%token FROM
+%token SIZE
+%token FONT
+%token ROMAN
+%token BOLD
+%token ITALIC
+%token FAT
+%token ACCENT
+%token BAR
+%token UNDER
+%token ABOVE
+%token <str> TEXT
+%token <str> QUOTED_TEXT
+%token FWD
+%token BACK
+%token DOWN
+%token UP
+%token MATRIX
+%token COL
+%token LCOL
+%token RCOL
+%token CCOL
+%token MARK
+%token LINEUP
+%token TYPE
+%token VCENTER
+%token PRIME
+%token SPLIT
+%token NOSPLIT
+%token UACCENT
+%token SPECIAL
+
+/* these are handled in the lexer */
+%token SPACE
+%token GFONT
+%token GSIZE
+%token DEFINE
+%token NDEFINE
+%token TDEFINE
+%token SDEFINE
+%token UNDEF
+%token IFDEF
+%token INCLUDE
+%token DELIM
+%token CHARTYPE
+%token SET
+%token GRFONT
+%token GBFONT
+
+/* The original eqn manual says that 'left' is right associative. It's lying.
+Consider 'left ( ~ left ( ~ right ) right )'. */
+
+%right LEFT
+%left RIGHT
+%right LPILE RPILE CPILE PILE TEXT QUOTED_TEXT MATRIX MARK LINEUP '^' '~' '\t' '{' SPLIT NOSPLIT
+%right FROM TO
+%left SQRT OVER SMALLOVER
+%right SUB SUP
+%right ROMAN BOLD ITALIC FAT FONT SIZE FWD BACK DOWN UP TYPE VCENTER SPECIAL
+%right BAR UNDER PRIME
+%left ACCENT UACCENT
+
+%type <b> mark from_to sqrt_over script simple equation nonsup
+%type <n> number
+%type <str> text delim
+%type <pb> pile_element_list pile_arg
+%type <mb> column_list
+%type <col> column column_arg column_element_list
+
+%%
+top:
+ /* empty */
+ | equation
+ { $1->top_level(); non_empty_flag = 1; }
+ ;
+
+equation:
+ mark
+ { $$ = $1; }
+ | equation mark
+ {
+ list_box *lb = $1->to_list_box();
+ if (!lb)
+ lb = new list_box($1);
+ lb->append($2);
+ $$ = lb;
+ }
+ ;
+
+mark:
+ from_to
+ { $$ = $1; }
+ | MARK mark
+ { $$ = make_mark_box($2); }
+ | LINEUP mark
+ { $$ = make_lineup_box($2); }
+ ;
+
+from_to:
+ sqrt_over %prec FROM
+ { $$ = $1; }
+ | sqrt_over TO from_to
+ { $$ = make_limit_box($1, 0, $3); }
+ | sqrt_over FROM sqrt_over
+ { $$ = make_limit_box($1, $3, 0); }
+ | sqrt_over FROM sqrt_over TO from_to
+ { $$ = make_limit_box($1, $3, $5); }
+ | sqrt_over FROM sqrt_over FROM from_to
+ { $$ = make_limit_box($1, make_limit_box($3, $5, 0), 0); }
+ ;
+
+sqrt_over:
+ script
+ { $$ = $1; }
+ | SQRT sqrt_over
+ { $$ = make_sqrt_box($2); }
+ | sqrt_over OVER sqrt_over
+ { $$ = make_over_box($1, $3); }
+ | sqrt_over SMALLOVER sqrt_over
+ { $$ = make_small_over_box($1, $3); }
+ ;
+
+script:
+ nonsup
+ { $$ = $1; }
+ | simple SUP script
+ { $$ = make_script_box($1, 0, $3); }
+ ;
+
+nonsup:
+ simple %prec SUP
+ { $$ = $1; }
+ | simple SUB nonsup
+ { $$ = make_script_box($1, $3, 0); }
+ | simple SUB simple SUP script
+ { $$ = make_script_box($1, $3, $5); }
+ ;
+
+simple:
+ TEXT
+ { $$ = split_text($1); }
+ | QUOTED_TEXT
+ { $$ = new quoted_text_box($1); }
+ | SPLIT QUOTED_TEXT
+ { $$ = split_text($2); }
+ | NOSPLIT TEXT
+ { $$ = new quoted_text_box($2); }
+ | '^'
+ { $$ = new half_space_box; }
+ | '~'
+ { $$ = new space_box; }
+ | '\t'
+ { $$ = new tab_box; }
+ | '{' equation '}'
+ { $$ = $2; }
+ | PILE pile_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ | LPILE pile_arg
+ { $2->set_alignment(LEFT_ALIGN); $$ = $2; }
+ | RPILE pile_arg
+ { $2->set_alignment(RIGHT_ALIGN); $$ = $2; }
+ | CPILE pile_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ | MATRIX '{' column_list '}'
+ { $$ = $3; }
+ | LEFT delim equation RIGHT delim
+ { $$ = make_delim_box($2, $3, $5); }
+ | LEFT delim equation
+ { $$ = make_delim_box($2, $3, 0); }
+ | simple BAR
+ { $$ = make_overline_box($1); }
+ | simple UNDER
+ { $$ = make_underline_box($1); }
+ | simple PRIME
+ { $$ = make_prime_box($1); }
+ | simple ACCENT simple
+ { $$ = make_accent_box($1, $3); }
+ | simple UACCENT simple
+ { $$ = make_uaccent_box($1, $3); }
+ | ROMAN simple
+ { $$ = new font_box(strsave(get_grfont()), $2); }
+ | BOLD simple
+ { $$ = new font_box(strsave(get_gbfont()), $2); }
+ | ITALIC simple
+ { $$ = new font_box(strsave(get_gfont()), $2); }
+ | FAT simple
+ { $$ = new fat_box($2); }
+ | FONT text simple
+ { $$ = new font_box($2, $3); }
+ | SIZE text simple
+ { $$ = new size_box($2, $3); }
+ | FWD number simple
+ { $$ = new hmotion_box($2, $3); }
+ | BACK number simple
+ { $$ = new hmotion_box(-$2, $3); }
+ | UP number simple
+ { $$ = new vmotion_box($2, $3); }
+ | DOWN number simple
+ { $$ = new vmotion_box(-$2, $3); }
+ | TYPE text simple
+ { $3->set_spacing_type($2); $$ = $3; }
+ | VCENTER simple
+ { $$ = new vcenter_box($2); }
+ | SPECIAL text simple
+ { $$ = make_special_box($2, $3); }
+ ;
+
+number:
+ text
+ {
+ int n;
+ if (sscanf($1, "%d", &n) == 1)
+ $$ = n;
+ delete[] $1;
+ }
+ ;
+
+pile_element_list:
+ equation
+ { $$ = new pile_box($1); }
+ | pile_element_list ABOVE equation
+ { $1->append($3); $$ = $1; }
+ ;
+
+pile_arg:
+ '{' pile_element_list '}'
+ { $$ = $2; }
+ | number '{' pile_element_list '}'
+ { $3->set_space($1); $$ = $3; }
+ ;
+
+column_list:
+ column
+ { $$ = new matrix_box($1); }
+ | column_list column
+ { $1->append($2); $$ = $1; }
+ ;
+
+column_element_list:
+ equation
+ { $$ = new column($1); }
+ | column_element_list ABOVE equation
+ { $1->append($3); $$ = $1; }
+ ;
+
+column_arg:
+ '{' column_element_list '}'
+ { $$ = $2; }
+ | number '{' column_element_list '}'
+ { $3->set_space($1); $$ = $3; }
+ ;
+
+column:
+ COL column_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ | LCOL column_arg
+ { $2->set_alignment(LEFT_ALIGN); $$ = $2; }
+ | RCOL column_arg
+ { $2->set_alignment(RIGHT_ALIGN); $$ = $2; }
+ | CCOL column_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ ;
+
+text: TEXT
+ { $$ = $1; }
+ | QUOTED_TEXT
+ { $$ = $1; }
+ ;
+
+delim:
+ text
+ { $$ = $1; }
+ | '{'
+ { $$ = strsave("{"); }
+ | '}'
+ { $$ = strsave("}"); }
+ ;
+
+%%
diff --git a/src/preproc/eqn/lex.cpp b/src/preproc/eqn/lex.cpp
new file mode 100644
index 0000000..e38a486
--- /dev/null
+++ b/src/preproc/eqn/lex.cpp
@@ -0,0 +1,1236 @@
+/* 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 "eqn.hpp"
+#include "stringclass.h"
+#include "ptable.h"
+
+
+// declarations to avoid friend name injection problems
+int get_char();
+int peek_char();
+int get_location(char **, int *);
+
+struct definition {
+ char is_macro;
+ char is_simple;
+ union {
+ int tok;
+ char *contents;
+ };
+ definition();
+ ~definition();
+};
+
+definition::definition() : is_macro(1), is_simple(0)
+{
+ contents = 0;
+}
+
+definition::~definition()
+{
+ if (is_macro)
+ free(contents);
+}
+
+declare_ptable(definition)
+implement_ptable(definition)
+
+PTABLE(definition) macro_table;
+
+static struct {
+ const char *name;
+ int token;
+} token_table[] = {
+ { "over", OVER },
+ { "smallover", SMALLOVER },
+ { "sqrt", SQRT },
+ { "sub", SUB },
+ { "sup", SUP },
+ { "lpile", LPILE },
+ { "rpile", RPILE },
+ { "cpile", CPILE },
+ { "pile", PILE },
+ { "left", LEFT },
+ { "right", RIGHT },
+ { "to", TO },
+ { "from", FROM },
+ { "size", SIZE },
+ { "font", FONT },
+ { "roman", ROMAN },
+ { "bold", BOLD },
+ { "italic", ITALIC },
+ { "fat", FAT },
+ { "bar", BAR },
+ { "under", UNDER },
+ { "accent", ACCENT },
+ { "uaccent", UACCENT },
+ { "above", ABOVE },
+ { "fwd", FWD },
+ { "back", BACK },
+ { "down", DOWN },
+ { "up", UP },
+ { "matrix", MATRIX },
+ { "col", COL },
+ { "lcol", LCOL },
+ { "rcol", RCOL },
+ { "ccol", CCOL },
+ { "mark", MARK },
+ { "lineup", LINEUP },
+ { "space", SPACE },
+ { "gfont", GFONT },
+ { "gsize", GSIZE },
+ { "define", DEFINE },
+ { "sdefine", SDEFINE },
+ { "ndefine", NDEFINE },
+ { "tdefine", TDEFINE },
+ { "undef", UNDEF },
+ { "ifdef", IFDEF },
+ { "include", INCLUDE },
+ { "copy", INCLUDE },
+ { "delim", DELIM },
+ { "chartype", CHARTYPE },
+ { "type", TYPE },
+ { "vcenter", VCENTER },
+ { "set", SET },
+ { "opprime", PRIME },
+ { "grfont", GRFONT },
+ { "gbfont", GBFONT },
+ { "split", SPLIT },
+ { "nosplit", NOSPLIT },
+ { "special", SPECIAL },
+};
+
+struct builtin_def {
+ const char *name;
+ const char *def;
+};
+
+static struct builtin_def common_defs[] = {
+ { "ALPHA", "\\(*A" },
+ { "BETA", "\\(*B" },
+ { "CHI", "\\(*X" },
+ { "DELTA", "\\(*D" },
+ { "EPSILON", "\\(*E" },
+ { "ETA", "\\(*Y" },
+ { "GAMMA", "\\(*G" },
+ { "IOTA", "\\(*I" },
+ { "KAPPA", "\\(*K" },
+ { "LAMBDA", "\\(*L" },
+ { "MU", "\\(*M" },
+ { "NU", "\\(*N" },
+ { "OMEGA", "\\(*W" },
+ { "OMICRON", "\\(*O" },
+ { "PHI", "\\(*F" },
+ { "PI", "\\(*P" },
+ { "PSI", "\\(*Q" },
+ { "RHO", "\\(*R" },
+ { "SIGMA", "\\(*S" },
+ { "TAU", "\\(*T" },
+ { "THETA", "\\(*H" },
+ { "UPSILON", "\\(*U" },
+ { "XI", "\\(*C" },
+ { "ZETA", "\\(*Z" },
+ { "Alpha", "\\(*A" },
+ { "Beta", "\\(*B" },
+ { "Chi", "\\(*X" },
+ { "Delta", "\\(*D" },
+ { "Epsilon", "\\(*E" },
+ { "Eta", "\\(*Y" },
+ { "Gamma", "\\(*G" },
+ { "Iota", "\\(*I" },
+ { "Kappa", "\\(*K" },
+ { "Lambda", "\\(*L" },
+ { "Mu", "\\(*M" },
+ { "Nu", "\\(*N" },
+ { "Omega", "\\(*W" },
+ { "Omicron", "\\(*O" },
+ { "Phi", "\\(*F" },
+ { "Pi", "\\(*P" },
+ { "Psi", "\\(*Q" },
+ { "Rho", "\\(*R" },
+ { "Sigma", "\\(*S" },
+ { "Tau", "\\(*T" },
+ { "Theta", "\\(*H" },
+ { "Upsilon", "\\(*U" },
+ { "Xi", "\\(*C" },
+ { "Zeta", "\\(*Z" },
+ { "alpha", "\\(*a" },
+ { "beta", "\\(*b" },
+ { "chi", "\\(*x" },
+ { "delta", "\\(*d" },
+ { "epsilon", "\\(*e" },
+ { "eta", "\\(*y" },
+ { "gamma", "\\(*g" },
+ { "iota", "\\(*i" },
+ { "kappa", "\\(*k" },
+ { "lambda", "\\(*l" },
+ { "mu", "\\(*m" },
+ { "nu", "\\(*n" },
+ { "omega", "\\(*w" },
+ { "omicron", "\\(*o" },
+ { "phi", "\\(*f" },
+ { "pi", "\\(*p" },
+ { "psi", "\\(*q" },
+ { "rho", "\\(*r" },
+ { "sigma", "\\(*s" },
+ { "tau", "\\(*t" },
+ { "theta", "\\(*h" },
+ { "upsilon", "\\(*u" },
+ { "xi", "\\(*c" },
+ { "zeta", "\\(*z" },
+ { "max", "{type \"operator\" roman \"max\"}" },
+ { "min", "{type \"operator\" roman \"min\"}" },
+ { "lim", "{type \"operator\" roman \"lim\"}" },
+ { "sin", "{type \"operator\" roman \"sin\"}" },
+ { "cos", "{type \"operator\" roman \"cos\"}" },
+ { "tan", "{type \"operator\" roman \"tan\"}" },
+ { "sinh", "{type \"operator\" roman \"sinh\"}" },
+ { "cosh", "{type \"operator\" roman \"cosh\"}" },
+ { "tanh", "{type \"operator\" roman \"tanh\"}" },
+ { "arc", "{type \"operator\" roman \"arc\"}" },
+ { "log", "{type \"operator\" roman \"log\"}" },
+ { "ln", "{type \"operator\" roman \"ln\"}" },
+ { "exp", "{type \"operator\" roman \"exp\"}" },
+ { "Re", "{type \"operator\" roman \"Re\"}" },
+ { "Im", "{type \"operator\" roman \"Im\"}" },
+ { "det", "{type \"operator\" roman \"det\"}" },
+ { "and", "{roman \"and\"}" },
+ { "if", "{roman \"if\"}" },
+ { "for", "{roman \"for\"}" },
+ { "times", "type \"binary\" \\(mu" },
+ { "ldots", "type \"inner\" { . . . }" },
+ { "inf", "\\(if" },
+ { "partial", "\\(pd" },
+ { "nothing", "\"\"" },
+ { "half", "{1 smallover 2}" },
+ { "hat_def", "roman \"^\"" },
+ { "hat", "accent { hat_def }" },
+ { "tilde_def", "\"~\"" },
+ { "tilde", "accent { tilde_def }" },
+ { "==", "type \"relation\" \\(==" },
+ { "!=", "type \"relation\" \\(!=" },
+ { "+-", "type \"binary\" \\(+-" },
+ { "->", "type \"relation\" \\(->" },
+ { "<-", "type \"relation\" \\(<-" },
+ { "<<", "type \"relation\" \\(<<" },
+ { ">>", "type \"relation\" \\(>>" },
+ { "prime", "'" },
+ { "approx", "type \"relation\" \"\\(~=\"" },
+ { "grad", "\\(gr" },
+ { "del", "\\(gr" },
+ { "cdot", "type \"binary\" \\(md" },
+ { "cdots", "type \"inner\" { \\(md \\(md \\(md }" },
+ { "dollar", "$" },
+};
+
+/* composite definitions that require troff size and motion operators */
+static struct builtin_def troff_defs[] = {
+ { "sum", "{type \"operator\" vcenter size +5 \\(*S}" },
+ { "prod", "{type \"operator\" vcenter size +5 \\(*P}" },
+ { "int", "{type \"operator\" vcenter size +8 \\(is}" },
+ { "union", "{type \"operator\" vcenter size +5 \\(cu}" },
+ { "inter", "{type \"operator\" vcenter size +5 \\(ca}" },
+ { "dot_def", "up 52 back 15 \".\"" },
+ { "dot", "accent { dot_def }" },
+ { "dotdot_def", "up 52 back 25 \"..\"" },
+ { "dotdot", "accent { dotdot_def }" },
+ { "utilde_def", "down 75 \"~\"" },
+ { "utilde", "uaccent { utilde_def }" },
+ { "vec_def", "up 52 size -5 \\(->" },
+ { "vec", "accent { vec_def }" },
+ { "dyad_def", "up 52 size -5 { \\(<> }" },
+ { "dyad", "accent { dyad_def }" },
+ { "...", "type \"inner\" { . . . }" },
+};
+
+/* equivalent definitions for MathML mode */
+static struct builtin_def mathml_defs[] = {
+ { "sum", "{type \"operator\" size big \\(*S}" },
+ { "prod", "{type \"operator\" size big \\(*P}" },
+ { "int", "{type \"operator\" size big \\(is}" },
+ { "union", "{type \"operator\" size big \\(cu}" },
+ { "inter", "{type \"operator\" size big \\(ca}" },
+ { "dot", "accent { \".\" }" },
+ { "dotdot", "accent { \"..\" }" },
+ { "utilde", "uaccent { \"~\" }" },
+ { "vec", "accent { \\(-> }" },
+ { "dyad", "accent { \\(<> }" },
+ { "...", "type \"inner\" { . . . }" },
+};
+
+void init_table(const char *device)
+{
+ unsigned int i;
+ for (i = 0; i < sizeof(token_table)/sizeof(token_table[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 0;
+ def->tok = token_table[i].token;
+ macro_table.define(token_table[i].name, def);
+ }
+ for (i = 0; i < sizeof(common_defs)/sizeof(common_defs[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave(common_defs[i].def);
+ def->is_simple = 1;
+ macro_table.define(common_defs[i].name, def);
+ }
+ if (output_format == troff) {
+ for (i = 0; i < sizeof(troff_defs)/sizeof(troff_defs[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave(troff_defs[i].def);
+ def->is_simple = 1;
+ macro_table.define(troff_defs[i].name, def);
+ }
+ }
+ else if (output_format == mathml) {
+ for (i = 0; i < sizeof(mathml_defs)/sizeof(mathml_defs[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave(mathml_defs[i].def);
+ def->is_simple = 1;
+ macro_table.define(mathml_defs[i].name, def);
+ }
+ }
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave("1");
+ macro_table.define(device, def);
+}
+
+class input {
+ input *next;
+public:
+ input(input *p);
+ virtual ~input();
+ virtual int get() = 0;
+ virtual int peek() = 0;
+ virtual int get_location(char **, int *);
+
+ friend int get_char();
+ friend int peek_char();
+ friend int get_location(char **, int *);
+ friend void init_lex(const char *str, const char *filename, int lineno);
+};
+
+class file_input : public input {
+ FILE *fp;
+ char *filename;
+ int lineno;
+ string line;
+ const char *ptr;
+ int read_line();
+public:
+ file_input(FILE *, const char *, input *);
+ ~file_input();
+ int get();
+ int peek();
+ int get_location(char **, int *);
+};
+
+
+class macro_input : public input {
+ char *s;
+ char *p;
+public:
+ macro_input(const char *, input *);
+ ~macro_input();
+ int get();
+ int peek();
+};
+
+class top_input : public macro_input {
+ char *filename;
+ int lineno;
+ public:
+ top_input(const char *, const char *, int, input *);
+ ~top_input();
+ int get();
+ int get_location(char **, int *);
+};
+
+class argument_macro_input: public input {
+ char *s;
+ char *p;
+ char *ap;
+ int argc;
+ char *argv[9];
+public:
+ argument_macro_input(const char *, int, char **, input *);
+ ~argument_macro_input();
+ int get();
+ int peek();
+};
+
+input::input(input *x) : next(x)
+{
+}
+
+input::~input()
+{
+}
+
+int input::get_location(char **, int *)
+{
+ return 0;
+}
+
+file_input::file_input(FILE *f, const char *fn, input *p)
+: input(p), lineno(0), ptr("")
+{
+ fp = f;
+ filename = strsave(fn);
+}
+
+file_input::~file_input()
+{
+ if (fclose(fp) < 0)
+ fatal("unable to close '%1': %2", filename, strerror(errno));
+ delete[] filename;
+}
+
+int file_input::read_line()
+{
+ for (;;) {
+ line.clear();
+ lineno++;
+ for (;;) {
+ int c = getc(fp);
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != '\n')
+ lex_error("invalid input character code %1", '\r');
+ }
+ if (c == EOF)
+ break;
+ else if (is_invalid_input_char(c))
+ lex_error("invalid input character code %1", c);
+ else {
+ line += char(c);
+ if (c == '\n')
+ break;
+ }
+ }
+ if (line.length() == 0)
+ return 0;
+ if (!(line.length() >= 3 && line[0] == '.' && line[1] == 'E'
+ && (line[2] == 'Q' || line[2] == 'N')
+ && (line.length() == 3 || line[3] == ' ' || line[3] == '\n'
+ || compatible_flag))) {
+ line += '\0';
+ ptr = line.contents();
+ return 1;
+ }
+ }
+}
+
+int file_input::get()
+{
+ if (*ptr != '\0' || read_line())
+ return *ptr++ & 0377;
+ else
+ return EOF;
+}
+
+int file_input::peek()
+{
+ if (*ptr != '\0' || read_line())
+ return *ptr;
+ else
+ return EOF;
+}
+
+int file_input::get_location(char **fnp, int *lnp)
+{
+ *fnp = filename;
+ *lnp = lineno;
+ return 1;
+}
+
+macro_input::macro_input(const char *str, input *x) : input(x)
+{
+ p = s = strsave(str);
+}
+
+macro_input::~macro_input()
+{
+ free(s);
+}
+
+int macro_input::get()
+{
+ if (p == 0 || *p == '\0')
+ return EOF;
+ else
+ return *p++ & 0377;
+}
+
+int macro_input::peek()
+{
+ if (p == 0 || *p == '\0')
+ return EOF;
+ else
+ return *p & 0377;
+}
+
+top_input::top_input(const char *str, const char *fn, int ln, input *x)
+: macro_input(str, x), lineno(ln)
+{
+ filename = strsave(fn);
+}
+
+top_input::~top_input()
+{
+ free(filename);
+}
+
+int top_input::get()
+{
+ int c = macro_input::get();
+ if (c == '\n')
+ lineno++;
+ return c;
+}
+
+int top_input::get_location(char **fnp, int *lnp)
+{
+ *fnp = filename;
+ *lnp = lineno;
+ return 1;
+}
+
+// Character representing $1. Must be invalid input character.
+#define ARG1 14
+
+argument_macro_input::argument_macro_input(const char *body, int ac,
+ char **av, input *x)
+: input(x), ap(0), argc(ac)
+{
+ int i;
+ for (i = 0; i < argc; i++)
+ argv[i] = av[i];
+ p = s = strsave(body);
+ int j = 0;
+ for (i = 0; s[i] != '\0'; i++)
+ if (s[i] == '$' && s[i+1] >= '0' && s[i+1] <= '9') {
+ if (s[i+1] != '0')
+ s[j++] = ARG1 + s[++i] - '1';
+ }
+ else
+ s[j++] = s[i];
+ s[j] = '\0';
+}
+
+
+argument_macro_input::~argument_macro_input()
+{
+ for (int i = 0; i < argc; i++)
+ delete[] argv[i];
+ delete[] s;
+}
+
+int argument_macro_input::get()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return *ap++ & 0377;
+ ap = 0;
+ }
+ if (p == 0)
+ return EOF;
+ while (*p >= ARG1 && *p <= ARG1 + 8) {
+ int i = *p++ - ARG1;
+ if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
+ ap = argv[i];
+ return *ap++ & 0377;
+ }
+ }
+ if (*p == '\0')
+ return EOF;
+ return *p++ & 0377;
+}
+
+int argument_macro_input::peek()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return *ap & 0377;
+ ap = 0;
+ }
+ if (p == 0)
+ return EOF;
+ while (*p >= ARG1 && *p <= ARG1 + 8) {
+ int i = *p++ - ARG1;
+ if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
+ ap = argv[i];
+ return *ap & 0377;
+ }
+ }
+ if (*p == '\0')
+ return EOF;
+ return *p & 0377;
+}
+
+static input *current_input = 0;
+
+/* we insert a newline between input from different levels */
+
+int get_char()
+{
+ if (current_input == 0)
+ return EOF;
+ else {
+ int c = current_input->get();
+ if (c != EOF)
+ return c;
+ else {
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ return '\n';
+ }
+ }
+}
+
+int peek_char()
+{
+ if (current_input == 0)
+ return EOF;
+ else {
+ int c = current_input->peek();
+ if (c != EOF)
+ return c;
+ else
+ return '\n';
+ }
+}
+
+int get_location(char **fnp, int *lnp)
+{
+ for (input *p = current_input; p; p = p->next)
+ if (p->get_location(fnp, lnp))
+ return 1;
+ return 0;
+}
+
+string token_buffer;
+const int NCONTEXT = 4;
+string context_ring[NCONTEXT];
+int context_index = 0;
+
+void flush_context()
+{
+ for (int i = 0; i < NCONTEXT; i++)
+ context_ring[i] = "";
+ context_index = 0;
+}
+
+void show_context()
+{
+ int i = context_index;
+ fputs(" context is\n\t", stderr);
+ for (;;) {
+ int j = (i + 1) % NCONTEXT;
+ if (j == context_index) {
+ fputs(">>> ", stderr);
+ put_string(context_ring[i], stderr);
+ fputs(" <<<", stderr);
+ break;
+ }
+ else if (context_ring[i].length() > 0) {
+ put_string(context_ring[i], stderr);
+ putc(' ', stderr);
+ }
+ i = j;
+ }
+ putc('\n', stderr);
+}
+
+void add_context(const string &s)
+{
+ context_ring[context_index] = s;
+ context_index = (context_index + 1) % NCONTEXT;
+}
+
+void add_context(char c)
+{
+ context_ring[context_index] = c;
+ context_index = (context_index + 1) % NCONTEXT;
+}
+
+void add_quoted_context(const string &s)
+{
+ string &r = context_ring[context_index];
+ r = '"';
+ for (int i = 0; i < s.length(); i++)
+ if (s[i] == '"')
+ r += "\\\"";
+ else
+ r += s[i];
+ r += '"';
+ context_index = (context_index + 1) % NCONTEXT;
+}
+
+void init_lex(const char *str, const char *filename, int lineno)
+{
+ while (current_input != 0) {
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ }
+ current_input = new top_input(str, filename, lineno, 0);
+ flush_context();
+}
+
+
+void get_delimited_text()
+{
+ char *filename, *last_seen_filename;
+ int lineno;
+ int got_location = get_location(&filename, &lineno);
+ // `filename` gets invalidated if we iterate off the end of the file.
+ last_seen_filename = strdup(filename);
+ int start = get_char();
+ while (start == ' ' || start == '\t' || start == '\n')
+ start = get_char();
+ token_buffer.clear();
+ if (start == EOF) {
+ current_lineno = 0;
+ if (got_location)
+ error_with_file_and_line(last_seen_filename, lineno,
+ "end of input while defining macro");
+ else
+ error("end of input while defining macro");
+ free(last_seen_filename);
+ return;
+ }
+ for (;;) {
+ int c = get_char();
+ if (c == EOF) {
+ current_lineno = 0;
+ if (got_location)
+ error_with_file_and_line(last_seen_filename, lineno,
+ "end of input while defining macro");
+ else
+ error("end of input while defining macro");
+ add_context(start + token_buffer);
+ free(last_seen_filename);
+ return;
+ }
+ if (c == start)
+ break;
+ token_buffer += char(c);
+ }
+ add_context(start + token_buffer + start);
+ free(last_seen_filename);
+}
+
+void interpolate_macro_with_args(const char *body)
+{
+ char *argv[9];
+ int argc = 0;
+ int i;
+ for (i = 0; i < 9; i++)
+ argv[i] = 0;
+ int level = 0;
+ int c;
+ do {
+ token_buffer.clear();
+ for (;;) {
+ c = get_char();
+ if (c == EOF) {
+ lex_error("end of input while scanning macro arguments");
+ break;
+ }
+ if (level == 0 && (c == ',' || c == ')')) {
+ if (token_buffer.length() > 0) {
+ token_buffer += '\0';
+ argv[argc] = strsave(token_buffer.contents());
+ }
+ // for 'foo()', argc = 0
+ if (argc > 0 || c != ')' || i > 0)
+ argc++;
+ break;
+ }
+ token_buffer += char(c);
+ if (c == '(')
+ level++;
+ else if (c == ')')
+ level--;
+ }
+ } while (c != ')' && c != EOF);
+ current_input = new argument_macro_input(body, argc, argv, current_input);
+}
+
+/* If lookup flag is non-zero the token will be looked up to see
+if it is macro. If it's 1, it will looked up to see if it's a token.
+*/
+
+int get_token(int lookup_flag = 0)
+{
+ for (;;) {
+ int c = get_char();
+ while (c == ' ' || c == '\n')
+ c = get_char();
+ switch (c) {
+ case EOF:
+ {
+ add_context("end of input");
+ }
+ return 0;
+ case '"':
+ {
+ int quoted = 0;
+ token_buffer.clear();
+ for (;;) {
+ c = get_char();
+ if (c == EOF) {
+ lex_error("missing \"");
+ break;
+ }
+ else if (c == '\n') {
+ lex_error("newline before end of quoted text");
+ break;
+ }
+ else if (c == '"') {
+ if (!quoted)
+ break;
+ token_buffer[token_buffer.length() - 1] = '"';
+ quoted = 0;
+ }
+ else {
+ token_buffer += c;
+ quoted = quoted ? 0 : c == '\\';
+ }
+ }
+ }
+ add_quoted_context(token_buffer);
+ return QUOTED_TEXT;
+ case '{':
+ case '}':
+ case '^':
+ case '~':
+ case '\t':
+ add_context(c);
+ return c;
+ default:
+ {
+ int break_flag = 0;
+ int quoted = 0;
+ token_buffer.clear();
+ if (c == '\\')
+ quoted = 1;
+ else
+ token_buffer += c;
+ int done = 0;
+ while (!done) {
+ c = peek_char();
+ if (!quoted && lookup_flag != 0 && c == '(') {
+ token_buffer += '\0';
+ definition *def = macro_table.lookup(token_buffer.contents());
+ if (def && def->is_macro && !def->is_simple) {
+ (void)get_char(); // skip initial '('
+ interpolate_macro_with_args(def->contents);
+ break_flag = 1;
+ break;
+ }
+ token_buffer.set_length(token_buffer.length() - 1);
+ }
+ if (quoted) {
+ quoted = 0;
+ switch (c) {
+ case EOF:
+ lex_error("'\\' ignored at end of equation");
+ done = 1;
+ break;
+ case '\n':
+ lex_error("'\\' ignored because followed by newline");
+ done = 1;
+ break;
+ case '\t':
+ lex_error("'\\' ignored because followed by tab");
+ done = 1;
+ break;
+ case '"':
+ (void)get_char();
+ token_buffer += '"';
+ break;
+ default:
+ (void)get_char();
+ token_buffer += '\\';
+ token_buffer += c;
+ break;
+ }
+ }
+ else {
+ switch (c) {
+ case EOF:
+ case '{':
+ case '}':
+ case '^':
+ case '~':
+ case '"':
+ case ' ':
+ case '\t':
+ case '\n':
+ done = 1;
+ break;
+ case '\\':
+ (void)get_char();
+ quoted = 1;
+ break;
+ default:
+ (void)get_char();
+ token_buffer += char(c);
+ break;
+ }
+ }
+ }
+ if (break_flag || token_buffer.length() == 0)
+ break;
+ if (lookup_flag != 0) {
+ token_buffer += '\0';
+ definition *def = macro_table.lookup(token_buffer.contents());
+ token_buffer.set_length(token_buffer.length() - 1);
+ if (def) {
+ if (def->is_macro) {
+ current_input = new macro_input(def->contents, current_input);
+ break;
+ }
+ else if (lookup_flag == 1) {
+ add_context(token_buffer);
+ return def->tok;
+ }
+ }
+ }
+ add_context(token_buffer);
+ return TEXT;
+ }
+ }
+ }
+}
+
+void do_include()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad filename for include");
+ return;
+ }
+ token_buffer += '\0';
+ const char *filename = token_buffer.contents();
+ errno = 0;
+ FILE *fp = fopen(filename, "r");
+ if (fp == 0) {
+ lex_error("can't open included file '%1'", filename);
+ return;
+ }
+ current_input = new file_input(fp, filename, current_input);
+}
+
+void ignore_definition()
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad definition");
+ return;
+ }
+ get_delimited_text();
+}
+
+void do_definition(int is_simple)
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad definition");
+ return;
+ }
+ token_buffer += '\0';
+ const char *name = token_buffer.contents();
+ definition *def = macro_table.lookup(name);
+ if (def == 0) {
+ def = new definition[1];
+ macro_table.define(name, def);
+ }
+ else if (def->is_macro) {
+ free(def->contents);
+ }
+ get_delimited_text();
+ token_buffer += '\0';
+ def->is_macro = 1;
+ def->contents = strsave(token_buffer.contents());
+ def->is_simple = is_simple;
+}
+
+void do_undef()
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad undef command");
+ return;
+ }
+ token_buffer += '\0';
+ macro_table.define(token_buffer.contents(), 0);
+}
+
+void do_gsize()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to gsize command");
+ return;
+ }
+ token_buffer += '\0';
+ if (!set_gsize(token_buffer.contents()))
+ lex_error("invalid size '%1'", token_buffer.contents());
+}
+
+void do_gfont()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to gfont command");
+ return;
+ }
+ token_buffer += '\0';
+ set_gfont(token_buffer.contents());
+}
+
+void do_grfont()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to grfont command");
+ return;
+ }
+ token_buffer += '\0';
+ set_grfont(token_buffer.contents());
+}
+
+void do_gbfont()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to gbfont command");
+ return;
+ }
+ token_buffer += '\0';
+ set_gbfont(token_buffer.contents());
+}
+
+void do_space()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to space command");
+ return;
+ }
+ token_buffer += '\0';
+ char *ptr;
+ long n = strtol(token_buffer.contents(), &ptr, 10);
+ if (n == 0 && ptr == token_buffer.contents())
+ lex_error("bad argument '%1' to space command", token_buffer.contents());
+ else
+ set_space(int(n));
+}
+
+void do_ifdef()
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad ifdef");
+ return;
+ }
+ token_buffer += '\0';
+ definition *def = macro_table.lookup(token_buffer.contents());
+ int result = def && def->is_macro && !def->is_simple;
+ get_delimited_text();
+ if (result) {
+ token_buffer += '\0';
+ current_input = new macro_input(token_buffer.contents(), current_input);
+ }
+}
+
+char start_delim_saved = '\0';
+char end_delim_saved = '\0';
+
+void do_delim()
+{
+ int c = get_char();
+ while (c == ' ' || c == '\n')
+ c = get_char();
+ int d;
+ if (c == EOF || (d = get_char()) == EOF)
+ lex_error("end of file while reading argument to 'delim'");
+ else {
+ if (c == 'o' && d == 'f' && peek_char() == 'f') {
+ (void)get_char();
+ start_delim_saved = start_delim;
+ end_delim_saved = end_delim;
+ start_delim = end_delim = '\0';
+ }
+ else if (c == 'o' && d == 'n') {
+ start_delim = start_delim_saved;
+ end_delim = end_delim_saved;
+ }
+ else {
+ start_delim = c;
+ end_delim = d;
+ }
+ }
+}
+
+void do_chartype()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad chartype");
+ return;
+ }
+ token_buffer += '\0';
+ string type = token_buffer;
+ t = get_token();
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad chartype");
+ return;
+ }
+ token_buffer += '\0';
+ set_char_type(type.contents(), strsave(token_buffer.contents()));
+}
+
+void do_set()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad set");
+ return;
+ }
+ token_buffer += '\0';
+ string param = token_buffer;
+ t = get_token();
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad set");
+ return;
+ }
+ token_buffer += '\0';
+ int n;
+ if (sscanf(&token_buffer[0], "%d", &n) != 1) {
+ lex_error("bad number '%1'", token_buffer.contents());
+ return;
+ }
+ set_param(param.contents(), n);
+}
+
+int yylex()
+{
+ for (;;) {
+ int tk = get_token(1);
+ switch(tk) {
+ case UNDEF:
+ do_undef();
+ break;
+ case SDEFINE:
+ do_definition(1);
+ break;
+ case DEFINE:
+ do_definition(0);
+ break;
+ case TDEFINE:
+ if (!nroff)
+ do_definition(0);
+ else
+ ignore_definition();
+ break;
+ case NDEFINE:
+ if (nroff)
+ do_definition(0);
+ else
+ ignore_definition();
+ break;
+ case GSIZE:
+ do_gsize();
+ break;
+ case GFONT:
+ do_gfont();
+ break;
+ case GRFONT:
+ do_grfont();
+ break;
+ case GBFONT:
+ do_gbfont();
+ break;
+ case SPACE:
+ do_space();
+ break;
+ case INCLUDE:
+ do_include();
+ break;
+ case IFDEF:
+ do_ifdef();
+ break;
+ case DELIM:
+ do_delim();
+ break;
+ case CHARTYPE:
+ do_chartype();
+ break;
+ case SET:
+ do_set();
+ break;
+ case QUOTED_TEXT:
+ case TEXT:
+ token_buffer += '\0';
+ yylval.str = strsave(token_buffer.contents());
+ // fall through
+ default:
+ return tk;
+ }
+ }
+}
+
+void lex_error(const char *message,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ char *filename;
+ int lineno;
+ if (!get_location(&filename, &lineno))
+ error(message, arg1, arg2, arg3);
+ else
+ error_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
+}
+
+void yyerror(const char *s)
+{
+ char *filename;
+ int lineno;
+ if (!get_location(&filename, &lineno))
+ error(s);
+ else
+ error_with_file_and_line(filename, lineno, s);
+ show_context();
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/limit.cpp b/src/preproc/eqn/limit.cpp
new file mode 100644
index 0000000..bf64a04
--- /dev/null
+++ b/src/preproc/eqn/limit.cpp
@@ -0,0 +1,217 @@
+// -*- C++ -*-
+/* 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 "pbox.h"
+
+class limit_box : public box {
+private:
+ box *p;
+ box *from;
+ box *to;
+public:
+ limit_box(box *, box *, box *);
+ ~limit_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+box *make_limit_box(box *pp, box *qq, box *rr)
+{
+ return new limit_box(pp, qq, rr);
+}
+
+limit_box::limit_box(box *pp, box *qq, box *rr)
+: p(pp), from(qq), to(rr)
+{
+ spacing_type = p->spacing_type;
+}
+
+limit_box::~limit_box()
+{
+ delete p;
+ delete from;
+ delete to;
+}
+
+int limit_box::compute_metrics(int style)
+{
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ if (!(style <= SCRIPT_STYLE && one_size_reduction_flag))
+ set_script_size();
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ int res = 0;
+ int mark_uid = -1;
+ if (from != 0) {
+ res = from->compute_metrics(cramped_style(script_style(style)));
+ if (res)
+ mark_uid = from->uid;
+ }
+ if (to != 0) {
+ int r = to->compute_metrics(script_style(style));
+ if (res && r)
+ error("multiple marks and lineups");
+ else {
+ mark_uid = to->uid;
+ res = r;
+ }
+ }
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ int r = p->compute_metrics(style);
+ p->compute_subscript_kern();
+ if (res && r)
+ error("multiple marks and lineups");
+ else {
+ mark_uid = p->uid;
+ res = r;
+ }
+ printf(".nr " LEFT_WIDTH_FORMAT " "
+ "0\\n[" WIDTH_FORMAT "]",
+ uid, p->uid);
+ if (from != 0)
+ printf(">?(\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, from->uid);
+ if (to != 0)
+ printf(">?(-\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, to->uid);
+ printf("/2\n");
+ printf(".nr " WIDTH_FORMAT " "
+ "0\\n[" WIDTH_FORMAT "]",
+ uid, p->uid);
+ if (from != 0)
+ printf(">?(-\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, from->uid);
+ if (to != 0)
+ printf(">?(\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, to->uid);
+ printf("/2+\\n[" LEFT_WIDTH_FORMAT "]\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]", uid, p->uid);
+ if (to != 0)
+ printf(">?\\n[" WIDTH_FORMAT "]", to->uid);
+ if (from != 0)
+ printf(">?\\n[" WIDTH_FORMAT "]", from->uid);
+ printf("\n");
+ if (res)
+ printf(".nr " MARK_REG " +(\\n[" LEFT_WIDTH_FORMAT "]"
+ "-(\\n[" WIDTH_FORMAT "]/2))\n",
+ uid, mark_uid);
+ if (to != 0) {
+ printf(".nr " SUP_RAISE_FORMAT " %dM+\\n[" DEPTH_FORMAT
+ "]>?%dM+\\n[" HEIGHT_FORMAT "]\n",
+ uid, big_op_spacing1, to->uid, big_op_spacing3, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" SUP_RAISE_FORMAT "]+\\n["
+ HEIGHT_FORMAT "]+%dM\n",
+ uid, uid, to->uid, big_op_spacing5);
+ }
+ else
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ if (from != 0) {
+ printf(".nr " SUB_LOWER_FORMAT " %dM+\\n[" HEIGHT_FORMAT
+ "]>?%dM+\\n[" DEPTH_FORMAT "]\n",
+ uid, big_op_spacing2, from->uid, big_op_spacing4, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" SUB_LOWER_FORMAT "]+\\n["
+ DEPTH_FORMAT "]+%dM\n",
+ uid, uid, from->uid, big_op_spacing5);
+ }
+ else
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return res;
+}
+
+void limit_box::output()
+{
+ if (output_format == troff) {
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ if (to != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u"
+ "+(-\\n[" WIDTH_FORMAT "]u+\\n[" SUB_KERN_FORMAT "]u/2u)'",
+ uid, to->uid, p->uid);
+ to->output();
+ printf(DELIMITER_CHAR);
+ }
+ if (from != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'\\n[" SUB_LOWER_FORMAT "]u'", uid);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u"
+ "+(-\\n[" SUB_KERN_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid, from->uid);
+ from->output();
+ printf(DELIMITER_CHAR);
+ }
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u"
+ "-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ if (from != 0 && to != 0) {
+ printf("<munderover>");
+ p->output();
+ from->output();
+ to->output();
+ printf("</munderover>");
+ }
+ else if (from != 0) {
+ printf("<munder>");
+ p->output();
+ from->output();
+ printf("</munder>");
+ }
+ else if (to != 0) {
+ printf("<mover>");
+ p->output();
+ to->output();
+ printf("</mover>");
+ }
+ }
+}
+
+void limit_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " }");
+ if (from) {
+ fprintf(stderr, " from { ");
+ from->debug_print();
+ fprintf(stderr, " }");
+ }
+ if (to) {
+ fprintf(stderr, " to { ");
+ to->debug_print();
+ fprintf(stderr, " }");
+ }
+}
+
+void limit_box::check_tabs(int level)
+{
+ if (to)
+ to->check_tabs(level + 1);
+ if (from)
+ from->check_tabs(level + 1);
+ p->check_tabs(level + 1);
+}
diff --git a/src/preproc/eqn/list.cpp b/src/preproc/eqn/list.cpp
new file mode 100644
index 0000000..52644c5
--- /dev/null
+++ b/src/preproc/eqn/list.cpp
@@ -0,0 +1,241 @@
+// -*- C++ -*-
+/* 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 "pbox.h"
+
+list_box *box::to_list_box()
+{
+ return 0;
+}
+
+list_box *list_box::to_list_box()
+{
+ return this;
+}
+
+void list_box::append(box *pp)
+{
+ list_box *q = pp->to_list_box();
+ if (q == 0)
+ list.append(pp);
+ else {
+ for (int i = 0; i < q->list.len; i++) {
+ list.append(q->list.p[i]);
+ q->list.p[i] = 0;
+ }
+ q->list.len = 0;
+ delete q;
+ }
+}
+
+list_box::list_box(box *pp) : list(pp), sty(-1)
+{
+ list_box *q = pp->to_list_box();
+ if (q != 0) {
+ // flatten it
+ list.p[0] = q->list.p[0];
+ for (int i = 1; i < q->list.len; i++) {
+ list.append(q->list.p[i]);
+ q->list.p[i] = 0;
+ }
+ q->list.len = 0;
+ delete q;
+ }
+}
+
+static int compute_spacing(int is_script, int left, int right)
+{
+ if (left == SUPPRESS_TYPE || right == SUPPRESS_TYPE)
+ return 0;
+ if (left == PUNCTUATION_TYPE)
+ return is_script ? 0 : thin_space;
+ if (left == OPENING_TYPE || right == CLOSING_TYPE)
+ return 0;
+ if (right == BINARY_TYPE || left == BINARY_TYPE)
+ return is_script ? 0 : medium_space;
+ if (right == RELATION_TYPE) {
+ if (left == RELATION_TYPE)
+ return 0;
+ else
+ return is_script ? 0 : thick_space;
+ }
+ if (left == RELATION_TYPE)
+ return is_script ? 0 : thick_space;
+ if (right == OPERATOR_TYPE)
+ return thin_space;
+ if (left == INNER_TYPE || right == INNER_TYPE)
+ return is_script ? 0 : thin_space;
+ if (left == OPERATOR_TYPE && right == ORDINARY_TYPE)
+ return thin_space;
+ return 0;
+}
+
+int list_box::compute_metrics(int style)
+{
+ sty = style;
+ int i;
+ for (i = 0; i < list.len; i++) {
+ int t = list.p[i]->spacing_type;
+ // 5
+ if (t == BINARY_TYPE) {
+ int prevt;
+ if (i == 0
+ || (prevt = list.p[i-1]->spacing_type) == BINARY_TYPE
+ || prevt == OPERATOR_TYPE
+ || prevt == RELATION_TYPE
+ || prevt == OPENING_TYPE
+ || prevt == SUPPRESS_TYPE
+ || prevt == PUNCTUATION_TYPE)
+ list.p[i]->spacing_type = ORDINARY_TYPE;
+ }
+ // 7
+ else if ((t == RELATION_TYPE || t == CLOSING_TYPE
+ || t == PUNCTUATION_TYPE)
+ && i > 0 && list.p[i-1]->spacing_type == BINARY_TYPE)
+ list.p[i-1]->spacing_type = ORDINARY_TYPE;
+ }
+ for (i = 0; i < list.len; i++) {
+ unsigned flags = 0;
+ if (i - 1 >= 0 && list.p[i - 1]->right_is_italic())
+ flags |= HINT_PREV_IS_ITALIC;
+ if (i + 1 < list.len && list.p[i + 1]->left_is_italic())
+ flags |= HINT_NEXT_IS_ITALIC;
+ if (flags)
+ list.p[i]->hint(flags);
+ }
+ is_script = (style <= SCRIPT_STYLE);
+ int total_spacing = 0;
+ for (i = 1; i < list.len; i++)
+ total_spacing += compute_spacing(is_script, list.p[i-1]->spacing_type,
+ list.p[i]->spacing_type);
+ int res = 0;
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple()) {
+ int r = list.p[i]->compute_metrics(style);
+ if (r) {
+ if (res)
+ error("multiple marks and lineups");
+ else {
+ compute_sublist_width(i);
+ printf(".nr " MARK_REG " +\\n[" TEMP_REG"]\n");
+ res = r;
+ }
+ }
+ }
+ printf(".nr " WIDTH_FORMAT " %dM", uid, total_spacing);
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple())
+ printf("+\\n[" WIDTH_FORMAT "]", list.p[i]->uid);
+ printf("\n");
+ printf(".nr " HEIGHT_FORMAT " 0", uid);
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple())
+ printf(">?\\n[" HEIGHT_FORMAT "]", list.p[i]->uid);
+ printf("\n");
+ printf(".nr " DEPTH_FORMAT " 0", uid);
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple())
+ printf(">?\\n[" DEPTH_FORMAT "]", list.p[i]->uid);
+ printf("\n");
+ int have_simple = 0;
+ for (i = 0; i < list.len && !have_simple; i++)
+ have_simple = list.p[i]->is_simple();
+ if (have_simple) {
+ printf(".nr " WIDTH_FORMAT " +\\w" DELIMITER_CHAR, uid);
+ for (i = 0; i < list.len; i++)
+ if (list.p[i]->is_simple())
+ list.p[i]->output();
+ printf(DELIMITER_CHAR "\n");
+ printf(".nr " HEIGHT_FORMAT " \\n[rst]>?\\n[" HEIGHT_FORMAT "]\n",
+ uid, uid);
+ printf(".nr " DEPTH_FORMAT " 0-\\n[rsb]>?\\n[" DEPTH_FORMAT "]\n",
+ uid, uid);
+ }
+ return res;
+}
+
+void list_box::compute_sublist_width(int n)
+{
+ int total_spacing = 0;
+ int i;
+ for (i = 1; i < n + 1 && i < list.len; i++)
+ total_spacing += compute_spacing(is_script, list.p[i-1]->spacing_type,
+ list.p[i]->spacing_type);
+ printf(".nr " TEMP_REG " %dM", total_spacing);
+ for (i = 0; i < n; i++)
+ if (!list.p[i]->is_simple())
+ printf("+\\n[" WIDTH_FORMAT "]", list.p[i]->uid);
+ int have_simple = 0;
+ for (i = 0; i < n && !have_simple; i++)
+ have_simple = list.p[i]->is_simple();
+ if (have_simple) {
+ printf("+\\w" DELIMITER_CHAR);
+ for (i = 0; i < n; i++)
+ if (list.p[i]->is_simple())
+ list.p[i]->output();
+ printf(DELIMITER_CHAR);
+ }
+ printf("\n");
+}
+
+void list_box::compute_subscript_kern()
+{
+ // We can only call compute_subscript_kern if we have called
+ // compute_metrics first.
+ if (list.p[list.len-1]->is_simple())
+ list.p[list.len-1]->compute_metrics(sty);
+ list.p[list.len-1]->compute_subscript_kern();
+ printf(".nr " SUB_KERN_FORMAT " \\n[" SUB_KERN_FORMAT "]\n",
+ uid, list.p[list.len-1]->uid);
+}
+
+void list_box::output()
+{
+ if (output_format == mathml)
+ printf("<mrow>");
+ for (int i = 0; i < list.len; i++) {
+ if (output_format == troff && i > 0) {
+ int n = compute_spacing(is_script,
+ list.p[i-1]->spacing_type,
+ list.p[i]->spacing_type);
+ if (n > 0)
+ printf("\\h'%dM'", n);
+ }
+ list.p[i]->output();
+ }
+ if (output_format == mathml)
+ printf("</mrow>");
+}
+
+void list_box::handle_char_type(int st, int ft)
+{
+ for (int i = 0; i < list.len; i++)
+ list.p[i]->handle_char_type(st, ft);
+}
+
+void list_box::debug_print()
+{
+ list.list_debug_print(" ");
+}
+
+void list_box::check_tabs(int level)
+{
+ list.list_check_tabs(level);
+}
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:
diff --git a/src/preproc/eqn/mark.cpp b/src/preproc/eqn/mark.cpp
new file mode 100644
index 0000000..9427b10
--- /dev/null
+++ b/src/preproc/eqn/mark.cpp
@@ -0,0 +1,120 @@
+// -*- C++ -*-
+/* 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 "pbox.h"
+
+class mark_box : public pointer_box {
+public:
+ mark_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+// we push down marks so that they don't interfere with spacing
+
+box *make_mark_box(box *p)
+{
+ list_box *b = p->to_list_box();
+ if (b != 0) {
+ b->list.p[0] = make_mark_box(b->list.p[0]);
+ return b;
+ }
+ else
+ return new mark_box(p);
+}
+
+mark_box::mark_box(box *pp) : pointer_box(pp)
+{
+}
+
+void mark_box::output()
+{
+ p->output();
+}
+
+int mark_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ if (res)
+ error("multiple marks and lineups");
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " MARK_REG " 0\n");
+ return FOUND_MARK;
+}
+
+void mark_box::debug_print()
+{
+ fprintf(stderr, "mark { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+
+class lineup_box : public pointer_box {
+public:
+ lineup_box(box *);
+ void output();
+ int compute_metrics(int style);
+ void debug_print();
+};
+
+// we push down lineups so that they don't interfere with spacing
+
+box *make_lineup_box(box *p)
+{
+ list_box *b = p->to_list_box();
+ if (b != 0) {
+ b->list.p[0] = make_lineup_box(b->list.p[0]);
+ return b;
+ }
+ else
+ return new lineup_box(p);
+}
+
+lineup_box::lineup_box(box *pp) : pointer_box(pp)
+{
+}
+
+void lineup_box::output()
+{
+ p->output();
+}
+
+int lineup_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ if (res)
+ error("multiple marks and lineups");
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " MARK_REG " 0\n");
+ return FOUND_LINEUP;
+}
+
+void lineup_box::debug_print()
+{
+ fprintf(stderr, "lineup { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
diff --git a/src/preproc/eqn/neqn.1.man b/src/preproc/eqn/neqn.1.man
new file mode 100644
index 0000000..758a150
--- /dev/null
+++ b/src/preproc/eqn/neqn.1.man
@@ -0,0 +1,92 @@
+.TH @g@neqn @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@neqn \- format equations for character-cell terminal output
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2001-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_neqn_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@neqn
+.RI [ @g@eqn-argument \~.\|.\|.]
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@neqn
+invokes the
+.MR @g@eqn @MAN1EXT@
+command with the
+.B ascii
+output device.
+.
+.
+.LP
+.I @g@eqn
+does not support low-resolution,
+typewriter-like devices,
+although it may work adequately for very simple input.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR @g@eqn @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_neqn_1_man_C]
+.do rr *groff_neqn_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=nroff textwidth=72:
diff --git a/src/preproc/eqn/neqn.sh b/src/preproc/eqn/neqn.sh
new file mode 100644
index 0000000..fb8d616
--- /dev/null
+++ b/src/preproc/eqn/neqn.sh
@@ -0,0 +1,26 @@
+#! /bin/sh
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 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 of the License (GPL2).
+#
+# 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.
+#
+# The GPL2 license text is available in the internet at
+# <http://www.gnu.org/licenses/gpl-2.0.txt>.
+
+# Provision of this shell script should not be taken to imply that use of
+# GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported.
+
+@GROFF_BIN_PATH_SETUP@
+PATH="$GROFF_RUNTIME$PATH"
+export PATH
+exec @g@eqn -Tascii ${1+"$@"}
+
+# eof
diff --git a/src/preproc/eqn/other.cpp b/src/preproc/eqn/other.cpp
new file mode 100644
index 0000000..1ddc8fb
--- /dev/null
+++ b/src/preproc/eqn/other.cpp
@@ -0,0 +1,706 @@
+// -*- C++ -*-
+/* 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/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+class accent_box : public pointer_box {
+private:
+ box *ab;
+public:
+ accent_box(box *, box *);
+ ~accent_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+box *make_accent_box(box *p, box *q)
+{
+ return new accent_box(p, q);
+}
+
+accent_box::accent_box(box *pp, box *qq) : pointer_box(pp), ab(qq)
+{
+}
+
+accent_box::~accent_box()
+{
+ delete ab;
+}
+
+#if 0
+int accent_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ p->compute_skew();
+ ab->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" HEIGHT_FORMAT "]-%dM>?0\n",
+ uid, p->uid, x_height);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+\\n["
+ SUP_RAISE_FORMAT "]\n",
+ uid, ab->uid, uid);
+ return r;
+}
+
+void accent_box::output()
+{
+ if (output_format == troff) {
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u+\\n["
+ SKEW_FORMAT "]u'",
+ p->uid, ab->uid, p->uid);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ ab->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", ab->uid);
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\h'-(\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u+\\n["
+ SKEW_FORMAT "]u)'",
+ p->uid, ab->uid, p->uid);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<mover accent='true'>");
+ p->output();
+ ab->output();
+ printf("</mover>")
+ }
+}
+#endif
+
+/* This version copes with the possibility of an accent's being wider
+than its accentee. LEFT_WIDTH_FORMAT gives the distance from the
+left edge of the resulting box to the middle of the accentee's box.*/
+
+int accent_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ p->compute_skew();
+ ab->compute_metrics(style);
+ printf(".nr " LEFT_WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2-\\n[" SKEW_FORMAT "])\n",
+ uid, p->uid, ab->uid, p->uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2+\\n[" SKEW_FORMAT "])"
+ "+\\n[" LEFT_WIDTH_FORMAT "]\n",
+ uid, p->uid, ab->uid, p->uid, uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" HEIGHT_FORMAT "]-%dM>?0\n",
+ uid, p->uid, x_height);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+\\n["
+ SUP_RAISE_FORMAT "]\n",
+ uid, ab->uid, uid);
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" LEFT_WIDTH_FORMAT "]"
+ "-(\\n[" WIDTH_FORMAT "]/2)'\n",
+ uid, p->uid);
+ return r;
+}
+
+void accent_box::output()
+{
+ if (output_format == troff) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u+\\n[" SKEW_FORMAT "]u"
+ "-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid, ab->uid);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ ab->output();
+ printf(DELIMITER_CHAR);
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<mover accent='true'>");
+ p->output();
+ ab->output();
+ printf("</mover>");
+ }
+}
+
+void accent_box::check_tabs(int level)
+{
+ ab->check_tabs(level + 1);
+ p->check_tabs(level + 1);
+}
+
+void accent_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } accent { ");
+ ab->debug_print();
+ fprintf(stderr, " }");
+}
+
+class overline_char_box : public simple_box {
+public:
+ overline_char_box();
+ void output();
+ void debug_print();
+};
+
+overline_char_box::overline_char_box()
+{
+}
+
+void overline_char_box::output()
+{
+ if (output_format == troff) {
+ printf("\\v'-%dM/2u-%dM'", 7*default_rule_thickness, x_height);
+ printf((draw_flag ? "\\D'l%dM 0'" : "\\l'%dM\\&\\(ru'"),
+ accent_width);
+ printf("\\v'%dM/2u+%dM'", 7*default_rule_thickness, x_height);
+ }
+ else if (output_format == mathml)
+ printf("<mo>&macr;</mo>");
+}
+
+void overline_char_box::debug_print()
+{
+ fprintf(stderr, "<overline char>");
+}
+
+class overline_box : public pointer_box {
+public:
+ overline_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+box *make_overline_box(box *p)
+{
+ if (p->is_char())
+ return new accent_box(p, new overline_char_box);
+ else
+ return new overline_box(p);
+}
+
+overline_box::overline_box(box *pp) : pointer_box(pp)
+{
+}
+
+int overline_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(cramped_style(style));
+ // 9
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+%dM\n",
+ uid, p->uid, default_rule_thickness*5);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void overline_box::output()
+{
+ if (output_format == troff) {
+ // 9
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'-\\n[" HEIGHT_FORMAT "]u-(%dM/2u)'",
+ p->uid, 7*default_rule_thickness);
+ if (draw_flag)
+ printf("\\D'l\\n[" WIDTH_FORMAT "]u 0'", p->uid);
+ else
+ printf("\\l'\\n[" WIDTH_FORMAT "]u\\&\\(ru'", p->uid);
+ printf(DELIMITER_CHAR);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<mover accent='false'>");
+ p->output();
+ printf("<mo>&macr;</mo></mover>");
+ }
+}
+
+void overline_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } bar");
+}
+
+class uaccent_box : public pointer_box {
+ box *ab;
+public:
+ uaccent_box(box *, box *);
+ ~uaccent_box();
+ int compute_metrics(int);
+ void output();
+ void compute_subscript_kern();
+ void check_tabs(int);
+ void debug_print();
+};
+
+box *make_uaccent_box(box *p, box *q)
+{
+ return new uaccent_box(p, q);
+}
+
+uaccent_box::uaccent_box(box *pp, box *qq)
+: pointer_box(pp), ab(qq)
+{
+}
+
+uaccent_box::~uaccent_box()
+{
+ delete ab;
+}
+
+int uaccent_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ ab->compute_metrics(style);
+ printf(".nr " LEFT_WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2)\n",
+ uid, p->uid, ab->uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2)"
+ "+\\n[" LEFT_WIDTH_FORMAT "]\n",
+ uid, p->uid, ab->uid, uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ "+\\n[" DEPTH_FORMAT "]\n",
+ uid, p->uid, ab->uid);
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" LEFT_WIDTH_FORMAT "]"
+ "-(\\n[" WIDTH_FORMAT "]/2)'\n",
+ uid, p->uid);
+ return r;
+}
+
+void uaccent_box::output()
+{
+ if (output_format == troff) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, ab->uid);
+ printf("\\v'\\n[" DEPTH_FORMAT "]u'", p->uid);
+ ab->output();
+ printf(DELIMITER_CHAR);
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<munder accent='true'>");
+ p->output();
+ ab->output();
+ printf("</munder>");
+ }
+}
+
+void uaccent_box::check_tabs(int level)
+{
+ ab->check_tabs(level + 1);
+ p->check_tabs(level + 1);
+}
+
+void uaccent_box::compute_subscript_kern()
+{
+ box::compute_subscript_kern(); // want 0 subscript kern
+}
+
+void uaccent_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } uaccent { ");
+ ab->debug_print();
+ fprintf(stderr, " }");
+}
+
+class underline_char_box : public simple_box {
+public:
+ underline_char_box();
+ void output();
+ void debug_print();
+};
+
+underline_char_box::underline_char_box()
+{
+}
+
+void underline_char_box::output()
+{
+ if (output_format == troff) {
+ printf("\\v'%dM/2u'", 7*default_rule_thickness);
+ printf((draw_flag ? "\\D'l%dM 0'" : "\\l'%dM\\&\\(ru'"),
+ accent_width);
+ printf("\\v'-%dM/2u'", 7*default_rule_thickness);
+ }
+ else if (output_format == mathml)
+ printf("<mo>&lowbar;</mo>");
+}
+
+void underline_char_box::debug_print()
+{
+ fprintf(stderr, "<underline char>");
+}
+
+
+class underline_box : public pointer_box {
+public:
+ underline_box(box *);
+ int compute_metrics(int);
+ void output();
+ void compute_subscript_kern();
+ void debug_print();
+};
+
+box *make_underline_box(box *p)
+{
+ if (p->is_char())
+ return new uaccent_box(p, new underline_char_box);
+ else
+ return new underline_box(p);
+}
+
+underline_box::underline_box(box *pp) : pointer_box(pp)
+{
+}
+
+int underline_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ // 10
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]+%dM\n",
+ uid, p->uid, default_rule_thickness*5);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void underline_box::output()
+{
+ if (output_format == troff) {
+ // 10
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'\\n[" DEPTH_FORMAT "]u+(%dM/2u)'",
+ p->uid, 7*default_rule_thickness);
+ if (draw_flag)
+ printf("\\D'l\\n[" WIDTH_FORMAT "]u 0'", p->uid);
+ else
+ printf("\\l'\\n[" WIDTH_FORMAT "]u\\&\\(ru'", p->uid);
+ printf(DELIMITER_CHAR);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<munder accent='true'>");
+ p->output();
+ printf("<mo>&macr;</mo></munder>");
+ }
+}
+
+// we want an underline box to have 0 subscript kern
+
+void underline_box::compute_subscript_kern()
+{
+ box::compute_subscript_kern();
+}
+
+void underline_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } under");
+}
+
+size_box::size_box(char *s, box *pp) : pointer_box(pp), size(s)
+{
+}
+
+int size_box::compute_metrics(int style)
+{
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ printf(".ps %s\n", size);
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ int r = p->compute_metrics(style);
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void size_box::output()
+{
+ if (output_format == troff) {
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ p->output();
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<mstyle mathsize='%s'>", size);
+ p->output();
+ printf("</mstyle>");
+ }
+}
+
+size_box::~size_box()
+{
+ free(size);
+}
+
+void size_box::debug_print()
+{
+ fprintf(stderr, "size %s { ", size);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+
+font_box::font_box(char *s, box *pp) : pointer_box(pp), f(s)
+{
+}
+
+font_box::~font_box()
+{
+ free(f);
+}
+
+int font_box::compute_metrics(int style)
+{
+ const char *old_roman_font = current_roman_font;
+ current_roman_font = f;
+ printf(".nr " FONT_FORMAT " \\n[.f]\n", uid);
+ printf(".ft %s\n", f);
+ int r = p->compute_metrics(style);
+ current_roman_font = old_roman_font;
+ printf(".ft \\n[" FONT_FORMAT "]\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void font_box::output()
+{
+ if (output_format == troff) {
+ printf("\\f[%s]", f);
+ const char *old_roman_font = current_roman_font;
+ current_roman_font = f;
+ p->output();
+ current_roman_font = old_roman_font;
+ printf("\\f[\\n[" FONT_FORMAT "]]", uid);
+ }
+ else if (output_format == mathml) {
+ const char *mlfont = f;
+ // bold and italic are already in MathML; translate eqn roman here
+ switch (f[0]) {
+ case 'I':
+ case 'i':
+ mlfont = "italic";
+ break;
+ case 'B':
+ case 'b':
+ mlfont = "bold";
+ break;
+ case 'R':
+ case 'r':
+ default:
+ mlfont = "normal";
+ break;
+ }
+ printf("<mstyle mathvariant='%s'>", mlfont);
+ p->output();
+ printf("</mstyle>");
+ }
+}
+
+void font_box::debug_print()
+{
+ fprintf(stderr, "font %s { ", f);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+fat_box::fat_box(box *pp) : pointer_box(pp)
+{
+}
+
+int fat_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]+%dM\n",
+ uid, p->uid, fat_offset);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void fat_box::output()
+{
+ if (output_format == troff) {
+ p->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", p->uid);
+ printf("\\h'%dM'", fat_offset);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<mstyle mathvariant='double-struck'>");
+ p->output();
+ printf("</mstyle>");
+ }
+}
+
+
+void fat_box::debug_print()
+{
+ fprintf(stderr, "fat { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+
+vmotion_box::vmotion_box(int i, box *pp) : pointer_box(pp), n(i)
+{
+}
+
+int vmotion_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ if (n > 0) {
+ printf(".nr " HEIGHT_FORMAT " %dM+\\n[" HEIGHT_FORMAT "]\n",
+ uid, n, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ }
+ else {
+ printf(".nr " DEPTH_FORMAT " %dM+\\n[" DEPTH_FORMAT "]>?0\n",
+ uid, -n, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n",
+ uid, p->uid);
+ }
+ return r;
+}
+
+void vmotion_box::output()
+{
+ if (output_format == troff) {
+ printf("\\v'%dM'", -n);
+ p->output();
+ printf("\\v'%dM'", n);
+ }
+ else if (output_format == mathml) {
+ printf("<merror>eqn vertical motion cannot be expressed "
+ "in MathML</merror>");
+ p->output();
+ }
+}
+
+void vmotion_box::debug_print()
+{
+ if (n >= 0)
+ fprintf(stderr, "up %d { ", n);
+ else
+ fprintf(stderr, "down %d { ", -n);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+hmotion_box::hmotion_box(int i, box *pp) : pointer_box(pp), n(i)
+{
+}
+
+int hmotion_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]+%dM\n",
+ uid, p->uid, n);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ if (r)
+ printf(".nr " MARK_REG " +%dM\n", n);
+ return r;
+}
+
+void hmotion_box::output()
+{
+ if (output_format == troff) {
+ printf("\\h'%dM'", n);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<merror>eqn horizontal motion cannot be expressed "
+ "in MathML</merror>");
+ p->output();
+ }
+}
+
+void hmotion_box::debug_print()
+{
+ if (n >= 0)
+ fprintf(stderr, "fwd %d { ", n);
+ else
+ fprintf(stderr, "back %d { ", -n);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+vcenter_box::vcenter_box(box *pp) : pointer_box(pp)
+{
+}
+
+int vcenter_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" DEPTH_FORMAT "]-\\n["
+ HEIGHT_FORMAT "]/2+%dM\n",
+ uid, p->uid, p->uid, axis_height);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+\\n["
+ SUP_RAISE_FORMAT "]>?0\n", uid, p->uid, uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]-\\n["
+ SUP_RAISE_FORMAT "]>?0\n", uid, p->uid, uid);
+
+ return r;
+}
+
+void vcenter_box::output()
+{
+ if (output_format == troff)
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ p->output();
+ if (output_format == troff)
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+}
+
+void vcenter_box::debug_print()
+{
+ fprintf(stderr, "vcenter { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
diff --git a/src/preproc/eqn/over.cpp b/src/preproc/eqn/over.cpp
new file mode 100644
index 0000000..6a9cd9b
--- /dev/null
+++ b/src/preproc/eqn/over.cpp
@@ -0,0 +1,204 @@
+// -*- C++ -*-
+/* 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 "pbox.h"
+
+class over_box : public box {
+private:
+ int reduce_size;
+ box *num;
+ box *den;
+public:
+ over_box(int small, box *, box *);
+ ~over_box();
+ void debug_print();
+ int compute_metrics(int);
+ void output();
+ void check_tabs(int);
+};
+
+box *make_over_box(box *pp, box *qq)
+{
+ return new over_box(0, pp, qq);
+}
+
+box *make_small_over_box(box *pp, box *qq)
+{
+ return new over_box(1, pp, qq);
+}
+
+over_box::over_box(int is_small, box *pp, box *qq)
+: reduce_size(is_small), num(pp), den(qq)
+{
+ spacing_type = INNER_TYPE;
+}
+
+over_box::~over_box()
+{
+ delete num;
+ delete den;
+}
+
+int over_box::compute_metrics(int style)
+{
+ if (reduce_size) {
+ style = script_style(style);
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ set_script_size();
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ }
+ int mark_uid = 0;
+ int res = num->compute_metrics(style);
+ if (res)
+ mark_uid = num->uid;
+ int r = den->compute_metrics(cramped_style(style));
+ if (r && res)
+ error("multiple marks and lineups");
+ else {
+ mark_uid = den->uid;
+ res = r;
+ }
+ if (reduce_size)
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ printf(".nr " WIDTH_FORMAT " (\\n[" WIDTH_FORMAT "]>?\\n[" WIDTH_FORMAT "]",
+ uid, num->uid, den->uid);
+ // allow for \(ru being wider than both the numerator and denominator
+ if (!draw_flag)
+ fputs(">?\\w" DELIMITER_CHAR "\\(ru" DELIMITER_CHAR, stdout);
+ printf(")+%dM\n", null_delimiter_space*2 + over_hang*2);
+ // 15b
+ printf(".nr " SUP_RAISE_FORMAT " %dM\n",
+ uid, (reduce_size ? num2 : num1));
+ printf(".nr " SUB_LOWER_FORMAT " %dM\n",
+ uid, (reduce_size ? denom2 : denom1));
+
+ // 15d
+ printf(".nr " SUP_RAISE_FORMAT " +(\\n[" DEPTH_FORMAT
+ "]-\\n[" SUP_RAISE_FORMAT "]+%dM+(%dM/2)+%dM)>?0\n",
+ uid, num->uid, uid, axis_height, default_rule_thickness,
+ default_rule_thickness*(reduce_size ? 1 : 3));
+ printf(".nr " SUB_LOWER_FORMAT " +(\\n[" HEIGHT_FORMAT
+ "]-\\n[" SUB_LOWER_FORMAT "]-%dM+(%dM/2)+%dM)>?0\n",
+ uid, den->uid, uid, axis_height, default_rule_thickness,
+ default_rule_thickness*(reduce_size ? 1 : 3));
+
+
+ printf(".nr " HEIGHT_FORMAT " \\n[" SUP_RAISE_FORMAT "]+\\n["
+ HEIGHT_FORMAT "]\n",
+ uid, uid, num->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" SUB_LOWER_FORMAT "]+\\n["
+ DEPTH_FORMAT "]\n",
+ uid, uid, den->uid);
+ if (res)
+ printf(".nr " MARK_REG " +(\\n[" WIDTH_FORMAT "]-\\n["
+ WIDTH_FORMAT "]/2)\n", uid, mark_uid);
+ return res;
+}
+
+#define USE_Z
+
+void over_box::output()
+{
+ if (output_format == troff) {
+ if (reduce_size)
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ #ifdef USE_Z
+ printf("\\Z" DELIMITER_CHAR);
+ #endif
+ // move up to the numerator baseline
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ // move across so that it's centered
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, num->uid);
+
+ // print the numerator
+ num->output();
+
+ #ifdef USE_Z
+ printf(DELIMITER_CHAR);
+ #else
+ // back again
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", num->uid);
+ printf("\\h'-(\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, num->uid);
+ // down again
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ #endif
+ #ifdef USE_Z
+ printf("\\Z" DELIMITER_CHAR);
+ #endif
+ // move down to the denominator baseline
+ printf("\\v'\\n[" SUB_LOWER_FORMAT "]u'", uid);
+
+ // move across so that it's centered
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, den->uid);
+
+ // print the denominator
+ den->output();
+
+ #ifdef USE_Z
+ printf(DELIMITER_CHAR);
+ #else
+ // back again
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", den->uid);
+ printf("\\h'-(\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, den->uid);
+ // up again
+ printf("\\v'-\\n[" SUB_LOWER_FORMAT "]u'", uid);
+ #endif
+ if (reduce_size)
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ // draw the line
+ printf("\\h'%dM'", null_delimiter_space);
+ printf("\\v'-%dM'", axis_height);
+ fputs(draw_flag ? "\\D'l" : "\\l'", stdout);
+ printf("\\n[" WIDTH_FORMAT "]u-%dM",
+ uid, 2*null_delimiter_space);
+ fputs(draw_flag ? " 0'" : "\\&\\(ru'", stdout);
+ printf("\\v'%dM'", axis_height);
+ printf("\\h'%dM'", null_delimiter_space);
+ }
+ else if (output_format == mathml) {
+ // FIXME: passing a displaystyle attribute doesn't validate.
+ printf("<mfrac>");
+ num->output();
+ den->output();
+ printf("</mfrac>");
+ }
+}
+
+void over_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ num->debug_print();
+ if (reduce_size)
+ fprintf(stderr, " } smallover { ");
+ else
+ fprintf(stderr, " } over { ");
+ den->debug_print();
+ fprintf(stderr, " }");
+}
+
+void over_box::check_tabs(int level)
+{
+ num->check_tabs(level + 1);
+ den->check_tabs(level + 1);
+}
diff --git a/src/preproc/eqn/pbox.h b/src/preproc/eqn/pbox.h
new file mode 100644
index 0000000..b185419
--- /dev/null
+++ b/src/preproc/eqn/pbox.h
@@ -0,0 +1,140 @@
+// -*- C++ -*-
+/* 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/>. */
+
+extern int fat_offset;
+
+extern int over_hang;
+extern int accent_width;
+
+extern int delimiter_factor;
+extern int delimiter_shortfall;
+
+extern int null_delimiter_space;
+extern int script_space;
+extern int thin_space;
+extern int medium_space;
+extern int thick_space;
+
+extern int num1;
+extern int num2;
+// we don't use num3, because we don't have \atop
+extern int denom1;
+extern int denom2;
+extern int axis_height;
+extern int sup1;
+extern int sup2;
+extern int sup3;
+extern int default_rule_thickness;
+extern int sub1;
+extern int sub2;
+extern int sup_drop;
+extern int sub_drop;
+extern int x_height;
+extern int big_op_spacing1;
+extern int big_op_spacing2;
+extern int big_op_spacing3;
+extern int big_op_spacing4;
+extern int big_op_spacing5;
+
+extern int baseline_sep;
+extern int shift_down;
+extern int column_sep;
+extern int matrix_side_sep;
+
+// ms.eqn relies on this!
+
+#define LINE_STRING "10"
+#define MARK_OR_LINEUP_FLAG_REG "MK"
+
+#define WIDTH_FORMAT PREFIX "w%d"
+#define HEIGHT_FORMAT PREFIX "h%d"
+#define DEPTH_FORMAT PREFIX "d%d"
+#define TOTAL_FORMAT PREFIX "t%d"
+#define SIZE_FORMAT PREFIX "z%d"
+#define SMALL_SIZE_FORMAT PREFIX "Z%d"
+#define SUP_RAISE_FORMAT PREFIX "p%d"
+#define SUB_LOWER_FORMAT PREFIX "b%d"
+#define SUB_KERN_FORMAT PREFIX "k%d"
+#define FONT_FORMAT PREFIX "f%d"
+#define SKEW_FORMAT PREFIX "s%d"
+#define LEFT_WIDTH_FORMAT PREFIX "lw%d"
+#define LEFT_DELIM_STRING_FORMAT PREFIX "l%d"
+#define RIGHT_DELIM_STRING_FORMAT PREFIX "r%d"
+#define SQRT_STRING_FORMAT PREFIX "sqr%d"
+#define SQRT_WIDTH_FORMAT PREFIX "sq%d"
+#define BASELINE_SEP_FORMAT PREFIX "bs%d"
+// this needs two parameters, the uid and the column index
+#define COLUMN_WIDTH_FORMAT PREFIX "cw%d,%d"
+
+#define BAR_STRING PREFIX "sqb"
+#define TEMP_REG PREFIX "temp"
+#define MARK_REG PREFIX "mark"
+#define MARK_WIDTH_REG PREFIX "mwidth"
+#define SAVED_MARK_REG PREFIX "smark"
+#define MAX_SIZE_REG PREFIX "mxsz"
+#define REPEAT_APPEND_STRING_MACRO PREFIX "ras"
+#define TOP_HEIGHT_REG PREFIX "th"
+#define TOP_DEPTH_REG PREFIX "td"
+#define MID_HEIGHT_REG PREFIX "mh"
+#define MID_DEPTH_REG PREFIX "md"
+#define BOT_HEIGHT_REG PREFIX "bh"
+#define BOT_DEPTH_REG PREFIX "bd"
+#define EXT_HEIGHT_REG PREFIX "eh"
+#define EXT_DEPTH_REG PREFIX "ed"
+#define TOTAL_HEIGHT_REG PREFIX "tot"
+#define DELTA_REG PREFIX "delta"
+#define DELIM_STRING PREFIX "delim"
+#define DELIM_WIDTH_REG PREFIX "dwidth"
+#define SAVED_FONT_REG PREFIX "sfont"
+#define SAVED_PREV_FONT_REG PREFIX "spfont"
+#define SAVED_INLINE_FONT_REG PREFIX "sifont"
+#define SAVED_INLINE_PREV_FONT_REG PREFIX "sipfont"
+#define SAVED_SIZE_REG PREFIX "ssize"
+#define SAVED_INLINE_SIZE_REG PREFIX "sisize"
+#define SAVED_INLINE_PREV_SIZE_REG PREFIX "sipsize"
+#define SAVE_FONT_STRING PREFIX "sfont"
+#define RESTORE_FONT_STRING PREFIX "rfont"
+#define INDEX_REG PREFIX "i"
+#define TEMP_MACRO PREFIX "tempmac"
+
+#define DELIMITER_CHAR "\\(EQ"
+
+const int CRAMPED_SCRIPT_STYLE = 0;
+const int SCRIPT_STYLE = 1;
+const int CRAMPED_DISPLAY_STYLE = 2;
+const int DISPLAY_STYLE = 3;
+
+extern int script_style(int);
+extern int cramped_style(int);
+
+const int ORDINARY_TYPE = 0;
+const int OPERATOR_TYPE = 1;
+const int BINARY_TYPE = 2;
+const int RELATION_TYPE = 3;
+const int OPENING_TYPE = 4;
+const int CLOSING_TYPE = 5;
+const int PUNCTUATION_TYPE = 6;
+const int INNER_TYPE = 7;
+const int SUPPRESS_TYPE = 8;
+
+void set_script_size();
+
+enum { HINT_PREV_IS_ITALIC = 01, HINT_NEXT_IS_ITALIC = 02 };
+
+extern const char *current_roman_font;
diff --git a/src/preproc/eqn/pile.cpp b/src/preproc/eqn/pile.cpp
new file mode 100644
index 0000000..fcc2e92
--- /dev/null
+++ b/src/preproc/eqn/pile.cpp
@@ -0,0 +1,354 @@
+/* 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/>. */
+
+// piles and matrices
+
+#include <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+// SUP_RAISE_FORMAT gives the first baseline
+// BASELINE_SEP_FORMAT gives the separation between baselines
+
+int pile_box::compute_metrics(int style)
+{
+ int i;
+ for (i = 0; i < col.len; i++)
+ col.p[i]->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0", uid);
+ for (i = 0; i < col.len; i++)
+ printf(">?\\n[" WIDTH_FORMAT "]", col.p[i]->uid);
+ printf("\n");
+ printf(".nr " BASELINE_SEP_FORMAT " %dM",
+ uid, baseline_sep+col.space);
+ for (i = 1; i < col.len; i++)
+ printf(">?(\\n[" DEPTH_FORMAT "]+\\n[" HEIGHT_FORMAT "]+%dM)",
+ col.p[i-1]->uid, col.p[i]->uid, default_rule_thickness*5);
+ // round it so that it's a multiple of the vertical motion quantum
+ printf("+(\\n(.V/2)/\\n(.V*\\n(.V\n");
+
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d/2"
+ "+%dM\n",
+ uid, uid, col.len-1, axis_height - shift_down);
+ printf(".nr " HEIGHT_FORMAT " \\n[" SUP_RAISE_FORMAT "]+\\n["
+ HEIGHT_FORMAT "]\n",
+ uid, uid, col.p[0]->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d+\\n["
+ DEPTH_FORMAT "]-\\n[" SUP_RAISE_FORMAT "]\n",
+ uid, uid, col.len-1, col.p[col.len-1]->uid, uid);
+ return FOUND_NOTHING;
+}
+
+void pile_box::output()
+{
+ if (output_format == troff) {
+ int i;
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ for (i = 0; i < col.len; i++) {
+ switch (col.align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, col.p[i]->uid);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ uid, col.p[i]->uid);
+ break;
+ default:
+ assert(0);
+ }
+ col.p[i]->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", col.p[i]->uid);
+ switch (col.align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ col.p[i]->uid, uid);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ col.p[i]->uid, uid);
+ break;
+ default:
+ assert(0);
+ }
+ if (i != col.len - 1)
+ printf("\\v'\\n[" BASELINE_SEP_FORMAT "]u'", uid);
+ }
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\v'-(%du*\\n[" BASELINE_SEP_FORMAT "]u)'", col.len - 1, uid);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ const char *av;
+ switch (col.align) {
+ case LEFT_ALIGN:
+ av = "left";
+ break;
+ case RIGHT_ALIGN:
+ av = "right";
+ break;
+ case CENTER_ALIGN:
+ av = "center";
+ break;
+ default:
+ assert(0);
+ }
+ printf("<mtable columnalign='%s'>", av);
+ for (int i = 0; i < col.len; i++) {
+ printf("<mtr><mtd>");
+ col.p[i]->output();
+ printf("</mtd></mtr>");
+ }
+ printf("</mtable>");
+ }
+}
+
+pile_box::pile_box(box *pp) : col(pp)
+{
+}
+
+void pile_box::check_tabs(int level)
+{
+ col.list_check_tabs(level);
+}
+
+void pile_box::debug_print()
+{
+ col.debug_print("pile");
+}
+
+int matrix_box::compute_metrics(int style)
+{
+ int i, j;
+ int max_len = 0;
+ int space = 0;
+ for (i = 0; i < len; i++) {
+ for (j = 0; j < p[i]->len; j++)
+ p[i]->p[j]->compute_metrics(style);
+ if (p[i]->len > max_len)
+ max_len = p[i]->len;
+ if (p[i]->space > space)
+ space = p[i]->space;
+ }
+ for (i = 0; i < len; i++) {
+ printf(".nr " COLUMN_WIDTH_FORMAT " 0", uid, i);
+ for (j = 0; j < p[i]->len; j++)
+ printf(">?\\n[" WIDTH_FORMAT "]", p[i]->p[j]->uid);
+ printf("\n");
+ }
+ printf(".nr " WIDTH_FORMAT " %dM",
+ uid, column_sep*(len-1)+2*matrix_side_sep);
+ for (i = 0; i < len; i++)
+ printf("+\\n[" COLUMN_WIDTH_FORMAT "]", uid, i);
+ printf("\n");
+ printf(".nr " BASELINE_SEP_FORMAT " %dM",
+ uid, baseline_sep+space);
+ for (i = 0; i < len; i++)
+ for (j = 1; j < p[i]->len; j++)
+ printf(">?(\\n[" DEPTH_FORMAT "]+\\n[" HEIGHT_FORMAT "]+%dM)",
+ p[i]->p[j-1]->uid, p[i]->p[j]->uid, default_rule_thickness*5);
+ // round it so that it's a multiple of the vertical motion quantum
+ printf("+(\\n(.V/2)/\\n(.V*\\n(.V\n");
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d/2"
+ "+%dM\n",
+ uid, uid, max_len-1, axis_height - shift_down);
+ printf(".nr " HEIGHT_FORMAT " 0\\n[" SUP_RAISE_FORMAT "]+(0",
+ uid, uid);
+ for (i = 0; i < len; i++)
+ printf(">?\\n[" HEIGHT_FORMAT "]", p[i]->p[0]->uid);
+ printf(")>?0\n");
+ printf(".nr " DEPTH_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d-\\n["
+ SUP_RAISE_FORMAT "]+(0",
+ uid, uid, max_len-1, uid);
+ for (i = 0; i < len; i++)
+ if (p[i]->len == max_len)
+ printf(">?\\n[" DEPTH_FORMAT "]", p[i]->p[max_len-1]->uid);
+ printf(")>?0\n");
+ return FOUND_NOTHING;
+}
+
+void matrix_box::output()
+{
+ if (output_format == troff) {
+ printf("\\h'%dM'", matrix_side_sep);
+ for (int i = 0; i < len; i++) {
+ int j;
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ for (j = 0; j < p[i]->len; j++) {
+ switch (p[i]->align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" COLUMN_WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, i, p[i]->p[j]->uid);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" COLUMN_WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ uid, i, p[i]->p[j]->uid);
+ break;
+ default:
+ assert(0);
+ }
+ p[i]->p[j]->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", p[i]->p[j]->uid);
+ switch (p[i]->align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" COLUMN_WIDTH_FORMAT "]u/2u'",
+ p[i]->p[j]->uid, uid, i);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" COLUMN_WIDTH_FORMAT "]u'",
+ p[i]->p[j]->uid, uid, i);
+ break;
+ default:
+ assert(0);
+ }
+ if (j != p[i]->len - 1)
+ printf("\\v'\\n[" BASELINE_SEP_FORMAT "]u'", uid);
+ }
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\v'-(%du*\\n[" BASELINE_SEP_FORMAT "]u)'", p[i]->len - 1, uid);
+ printf("\\h'\\n[" COLUMN_WIDTH_FORMAT "]u'", uid, i);
+ if (i != len - 1)
+ printf("\\h'%dM'", column_sep);
+ }
+ printf("\\h'%dM'", matrix_side_sep);
+ }
+ else if (output_format == mathml) {
+ int n = p[0]->len; // Each column must have the same number of rows in it
+ printf("<mtable>");
+ for (int i = 0; i < n; i++) {
+ printf("<mtr>");
+ for (int j = 0; j < len; j++) {
+ const char *av;
+ switch (p[j]->align) {
+ case LEFT_ALIGN:
+ av = "left";
+ break;
+ case RIGHT_ALIGN:
+ av = "right";
+ break;
+ case CENTER_ALIGN:
+ av = "center";
+ break;
+ default:
+ assert(0);
+ }
+ printf("<mtd columnalign='%s'>", av);
+ p[j]->p[i]->output();
+ printf("</mtd>");
+ }
+ printf("</mtr>");
+ }
+ printf("</mtable>");
+ }
+}
+
+matrix_box::matrix_box(column *pp)
+{
+ p = new column*[10];
+ for (int i = 0; i < 10; i++)
+ p[i] = 0;
+ maxlen = 10;
+ len = 1;
+ p[0] = pp;
+}
+
+matrix_box::~matrix_box()
+{
+ for (int i = 0; i < len; i++)
+ delete p[i];
+ delete[] p;
+}
+
+void matrix_box::append(column *pp)
+{
+ if (len + 1 > maxlen) {
+ column **oldp = p;
+ maxlen *= 2;
+ p = new column*[maxlen];
+ memcpy(p, oldp, sizeof(column*)*len);
+ delete[] oldp;
+ }
+ p[len++] = pp;
+}
+
+void matrix_box::check_tabs(int level)
+{
+ for (int i = 0; i < len; i++)
+ p[i]->list_check_tabs(level);
+}
+
+void matrix_box::debug_print()
+{
+ fprintf(stderr, "matrix { ");
+ p[0]->debug_print("col");
+ for (int i = 1; i < len; i++) {
+ fprintf(stderr, " ");
+ p[i]->debug_print("col");
+ }
+ fprintf(stderr, " }");
+}
+
+column::column(box *pp) : box_list(pp), align(CENTER_ALIGN), space(0)
+{
+}
+
+void column::set_alignment(alignment a)
+{
+ align = a;
+}
+
+void column::set_space(int n)
+{
+ space = n;
+}
+
+void column::debug_print(const char *s)
+{
+ char c = '\0'; // shut up -Wall
+ switch (align) {
+ case LEFT_ALIGN:
+ c = 'l';
+ break;
+ case RIGHT_ALIGN:
+ c = 'r';
+ break;
+ case CENTER_ALIGN:
+ c = 'c';
+ break;
+ default:
+ assert(0);
+ }
+ fprintf(stderr, "%c%s %d { ", c, s, space);
+ list_debug_print(" above ");
+ fprintf(stderr, " }");
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/script.cpp b/src/preproc/eqn/script.cpp
new file mode 100644
index 0000000..f485eb9
--- /dev/null
+++ b/src/preproc/eqn/script.cpp
@@ -0,0 +1,250 @@
+/* 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 <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+class script_box : public pointer_box {
+private:
+ box *sub;
+ box *sup;
+public:
+ script_box(box *, box *, box *);
+ ~script_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ int left_is_italic();
+ void hint(unsigned);
+ void check_tabs(int);
+};
+
+/* The idea is that the script should attach to the rightmost box
+of a list. For example, given '2x sup 3', the superscript should
+attach to 'x' rather than '2x'. */
+
+box *make_script_box(box *nuc, box *sub, box *sup)
+{
+ list_box *b = nuc->to_list_box();
+ if (b != 0) {
+ b->list.p[b->list.len-1] = make_script_box(b->list.p[b->list.len - 1],
+ sub,
+ sup);
+ return b;
+ }
+ else
+ return new script_box(nuc, sub, sup);
+}
+
+script_box::script_box(box *pp, box *qq, box *rr)
+: pointer_box(pp), sub(qq), sup(rr)
+{
+}
+
+script_box::~script_box()
+{
+ delete sub;
+ delete sup;
+}
+
+int script_box::left_is_italic()
+{
+ return p->left_is_italic();
+}
+
+int script_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ p->compute_subscript_kern();
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ if (!(style <= SCRIPT_STYLE && one_size_reduction_flag))
+ set_script_size();
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ if (sub != 0)
+ sub->compute_metrics(cramped_style(script_style(style)));
+ if (sup != 0)
+ sup->compute_metrics(script_style(style));
+ // 18a
+ if (p->is_char()) {
+ printf(".nr " SUP_RAISE_FORMAT " 0\n", uid);
+ printf(".nr " SUB_LOWER_FORMAT " 0\n", uid);
+ }
+ else {
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" HEIGHT_FORMAT "]-%dM>?0\n",
+ uid, p->uid, sup_drop);
+ printf(".nr " SUB_LOWER_FORMAT " \\n[" DEPTH_FORMAT "]+%dM\n",
+ uid, p->uid, sub_drop);
+ }
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ if (sup == 0) {
+ assert(sub != 0);
+ // 18b
+ printf(".nr " SUB_LOWER_FORMAT " \\n[" SUB_LOWER_FORMAT "]>?%dM>?(\\n["
+ HEIGHT_FORMAT "]-(%dM*4/5))\n",
+ uid, uid, sub1, sub->uid, x_height);
+ }
+ else {
+ // sup != 0
+ // 18c
+ int pos;
+ if (style == DISPLAY_STYLE)
+ pos = sup1;
+ else if (style & 1) // not cramped
+ pos = sup2;
+ else
+ pos = sup3;
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" SUP_RAISE_FORMAT
+ "]>?%dM>?(\\n[" DEPTH_FORMAT "]+(%dM/4))\n",
+ uid, uid, pos, sup->uid, x_height);
+ // 18d
+ if (sub != 0) {
+ printf(".nr " SUB_LOWER_FORMAT " \\n[" SUB_LOWER_FORMAT "]>?%dM\n",
+ uid, uid, sub2);
+ // 18e
+ printf(".nr " TEMP_REG " \\n[" DEPTH_FORMAT "]-\\n["
+ SUP_RAISE_FORMAT "]+\\n[" HEIGHT_FORMAT "]-\\n["
+ SUB_LOWER_FORMAT "]+(4*%dM)\n",
+ sup->uid, uid, sub->uid, uid, default_rule_thickness);
+ printf(".if \\n[" TEMP_REG "] \\{");
+ printf(".nr " SUB_LOWER_FORMAT " +\\n[" TEMP_REG "]\n", uid);
+ printf(".nr " TEMP_REG " (%dM*4/5)-\\n[" SUP_RAISE_FORMAT
+ "]+\\n[" DEPTH_FORMAT "]>?0\n",
+ x_height, uid, sup->uid);
+ printf(".nr " SUP_RAISE_FORMAT " +\\n[" TEMP_REG "]\n", uid);
+ printf(".nr " SUB_LOWER_FORMAT " -\\n[" TEMP_REG "]\n", uid);
+ printf(".\\}\n");
+ }
+ }
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]", uid, p->uid);
+ if (sub != 0 && sup != 0)
+ printf("+((\\n[" WIDTH_FORMAT "]-\\n[" SUB_KERN_FORMAT "]>?\\n["
+ WIDTH_FORMAT "])+%dM)>?0\n",
+ sub->uid, p->uid, sup->uid, script_space);
+ else if (sub != 0)
+ printf("+(\\n[" WIDTH_FORMAT "]-\\n[" SUB_KERN_FORMAT "]+%dM)>?0\n",
+ sub->uid, p->uid, script_space);
+ else if (sup != 0)
+ printf("+(\\n[" WIDTH_FORMAT "]+%dM)>?0\n", sup->uid, script_space);
+ else
+ printf("\n");
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]",
+ uid, p->uid);
+ if (sup != 0)
+ printf(">?(\\n[" SUP_RAISE_FORMAT "]+\\n[" HEIGHT_FORMAT "])",
+ uid, sup->uid);
+ if (sub != 0)
+ printf(">?(-\\n[" SUB_LOWER_FORMAT "]+\\n[" HEIGHT_FORMAT "])",
+ uid, sub->uid);
+ printf("\n");
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]",
+ uid, p->uid);
+ if (sub != 0)
+ printf(">?(\\n[" SUB_LOWER_FORMAT "]+\\n[" DEPTH_FORMAT "])",
+ uid, sub->uid);
+ if (sup != 0)
+ printf(">?(-\\n[" SUP_RAISE_FORMAT "]+\\n[" DEPTH_FORMAT "])",
+ uid, sup->uid);
+ printf("\n");
+ return res;
+}
+
+void script_box::output()
+{
+ if (output_format == troff) {
+ p->output();
+ if (sup != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ sup->output();
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf(DELIMITER_CHAR);
+ }
+ if (sub != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'\\n[" SUB_LOWER_FORMAT "]u'", uid);
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ printf("\\h'-\\n[" SUB_KERN_FORMAT "]u'", p->uid);
+ sub->output();
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf(DELIMITER_CHAR);
+ }
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ uid, p->uid);
+ }
+ else if (output_format == mathml) {
+ if (sup != 0 && sub != 0) {
+ printf("<msubsup>");
+ p->output();
+ sub->output();
+ sup->output();
+ printf("</msubsup>");
+ }
+ else if (sup != 0) {
+ printf("<msup>");
+ p->output();
+ sup->output();
+ printf("</msup>");
+ }
+ else if (sub != 0) {
+ printf("<msub>");
+ p->output();
+ sub->output();
+ printf("</msub>");
+ }
+ }
+}
+
+void script_box::hint(unsigned flags)
+{
+ p->hint(flags & ~HINT_NEXT_IS_ITALIC);
+}
+
+void script_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " }");
+ if (sub) {
+ fprintf(stderr, " sub { ");
+ sub->debug_print();
+ fprintf(stderr, " }");
+ }
+ if (sup) {
+ fprintf(stderr, " sup { ");
+ sup->debug_print();
+ fprintf(stderr, " }");
+ }
+}
+
+void script_box::check_tabs(int level)
+{
+ if (sup)
+ sup->check_tabs(level + 1);
+ if (sub)
+ sub->check_tabs(level + 1);
+ p->check_tabs(level);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/special.cpp b/src/preproc/eqn/special.cpp
new file mode 100644
index 0000000..8ad8238
--- /dev/null
+++ b/src/preproc/eqn/special.cpp
@@ -0,0 +1,117 @@
+// -*- C++ -*-
+/* 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 "pbox.h"
+
+#define STRING_FORMAT PREFIX "str%d"
+
+#define SPECIAL_STRING "0s"
+#define SPECIAL_WIDTH_REG "0w"
+#define SPECIAL_HEIGHT_REG "0h"
+#define SPECIAL_DEPTH_REG "0d"
+#define SPECIAL_SUB_KERN_REG "0skern"
+#define SPECIAL_SKEW_REG "0skew"
+
+/*
+For example:
+
+.de Cl
+.ds 0s \Z'\\*[0s]'\v'\\n(0du'\D'l \\n(0wu -\\n(0hu-\\n(0du'\v'\\n(0hu'
+..
+.EQ
+define cancel 'special Cl'
+.EN
+*/
+
+
+class special_box : public pointer_box {
+ char *macro_name;
+public:
+ special_box(char *, box *);
+ ~special_box();
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void compute_skew();
+ void output();
+ void debug_print();
+};
+
+box *make_special_box(char *s, box *p)
+{
+ return new special_box(s, p);
+}
+
+special_box::special_box(char *s, box *pp) : pointer_box(pp), macro_name(s)
+{
+}
+
+special_box::~special_box()
+{
+ delete[] macro_name;
+}
+
+int special_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ p->compute_subscript_kern();
+ p->compute_skew();
+ printf(".ds " SPECIAL_STRING " \"");
+ p->output();
+ printf("\n");
+ printf(".nr " SPECIAL_WIDTH_REG " 0\\n[" WIDTH_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_HEIGHT_REG " \\n[" HEIGHT_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_DEPTH_REG " \\n[" DEPTH_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_SUB_KERN_REG " \\n[" SUB_KERN_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_SKEW_REG " 0\\n[" SKEW_FORMAT "]\n", p->uid);
+ printf(".%s\n", macro_name);
+ printf(".rn " SPECIAL_STRING " " STRING_FORMAT "\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" SPECIAL_WIDTH_REG "]\n", uid);
+ printf(".nr " HEIGHT_FORMAT " 0>?\\n[" SPECIAL_HEIGHT_REG "]\n", uid);
+ printf(".nr " DEPTH_FORMAT " 0>?\\n[" SPECIAL_DEPTH_REG "]\n", uid);
+ printf(".nr " SUB_KERN_FORMAT " 0>?\\n[" SPECIAL_SUB_KERN_REG "]\n", uid);
+ printf(".nr " SKEW_FORMAT " 0\\n[" SPECIAL_SKEW_REG "]\n", uid);
+ // User will have to change MARK_REG if appropriate.
+ return r;
+}
+
+void special_box::compute_subscript_kern()
+{
+ // Already computed in compute_metrics(), so do nothing.
+}
+
+void special_box::compute_skew()
+{
+ // Already computed in compute_metrics(), so do nothing.
+}
+
+void special_box::output()
+{
+ if (output_format == troff)
+ printf("\\*[" STRING_FORMAT "]", uid);
+ else if (output_format == mathml)
+ printf("<merror>eqn specials cannot be expressed in MathML</merror>");
+}
+
+void special_box::debug_print()
+{
+ fprintf(stderr, "special %s { ", macro_name);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
diff --git a/src/preproc/eqn/sqrt.cpp b/src/preproc/eqn/sqrt.cpp
new file mode 100644
index 0000000..e8d7c77
--- /dev/null
+++ b/src/preproc/eqn/sqrt.cpp
@@ -0,0 +1,186 @@
+// -*- C++ -*-
+/* 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 "pbox.h"
+
+
+class sqrt_box : public pointer_box {
+public:
+ sqrt_box(box *);
+ int compute_metrics(int style);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+box *make_sqrt_box(box *pp)
+{
+ return new sqrt_box(pp);
+}
+
+sqrt_box::sqrt_box(box *pp) : pointer_box(pp)
+{
+}
+
+#define SQRT_CHAR "\\[sqrt]"
+#define RADICAL_EXTENSION_CHAR "\\[sqrtex]"
+
+#define SQRT_CHAIN "\\[sqrt\\\\n[" INDEX_REG "]]"
+#define BAR_CHAIN "\\[sqrtex\\\\n[" INDEX_REG "]]"
+
+int sqrt_box::compute_metrics(int style)
+{
+ // 11
+ int r = p->compute_metrics(cramped_style(style));
+ printf(".nr " TEMP_REG " \\n[" HEIGHT_FORMAT "]+\\n[" DEPTH_FORMAT
+ "]+%dM+(%dM/4)\n",
+ p->uid, p->uid, default_rule_thickness,
+ (style > SCRIPT_STYLE ? x_height : default_rule_thickness));
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ printf(".ds " SQRT_STRING_FORMAT " " SQRT_CHAR "\n", uid);
+ printf(".ds " BAR_STRING " " RADICAL_EXTENSION_CHAR "\n");
+ printf(".nr " SQRT_WIDTH_FORMAT
+ " 0\\w" DELIMITER_CHAR SQRT_CHAR DELIMITER_CHAR "\n",
+ uid);
+ printf(".if \\n[rst]-\\n[rsb]-%dM<\\n[" TEMP_REG "] \\{",
+ default_rule_thickness);
+
+ printf(".nr " INDEX_REG " 0\n"
+ ".de " TEMP_MACRO "\n"
+ ".ie c" SQRT_CHAIN " \\{"
+ ".ds " SQRT_STRING_FORMAT " " SQRT_CHAIN "\n"
+ ".ie c" BAR_CHAIN " .ds " BAR_STRING " " BAR_CHAIN "\n"
+ ".el .ds " BAR_STRING " " RADICAL_EXTENSION_CHAR "\n"
+ ".nr " SQRT_WIDTH_FORMAT
+ " 0\\w" DELIMITER_CHAR SQRT_CHAIN DELIMITER_CHAR "\n"
+ ".if \\\\n[rst]-\\\\n[rsb]-%dM<\\n[" TEMP_REG "] \\{"
+ ".nr " INDEX_REG " +1\n"
+ "." TEMP_MACRO "\n"
+ ".\\}\\}\n"
+ ".el .nr " INDEX_REG " 0-1\n"
+ "..\n"
+ "." TEMP_MACRO "\n",
+ uid, uid, default_rule_thickness);
+
+ printf(".if \\n[" INDEX_REG "]<0 \\{");
+
+ // Determine the maximum point size
+ printf(".ps 1000\n");
+ printf(".nr " MAX_SIZE_REG " \\n[.ps]\n");
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ // We define a macro that will increase the current point size
+ // until we get a radical sign that's tall enough or we reach
+ // the maximum point size.
+ printf(".de " TEMP_MACRO "\n"
+ ".nr " SQRT_WIDTH_FORMAT
+ " 0\\w" DELIMITER_CHAR "\\*[" SQRT_STRING_FORMAT "]" DELIMITER_CHAR "\n"
+ ".if \\\\n[rst]"
+ "&(\\\\n[rst]-\\\\n[rsb]-%dM<\\n[" TEMP_REG "])"
+ "&(\\\\n[.ps]<\\n[" MAX_SIZE_REG "]) \\{"
+ ".ps +1\n"
+ "." TEMP_MACRO "\n"
+ ".\\}\n"
+ "..\n"
+ "." TEMP_MACRO "\n",
+ uid, uid, default_rule_thickness);
+
+ printf(".\\}\\}\n");
+
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ // set TEMP_REG to the amount by which the radical sign is too big
+ printf(".nr " TEMP_REG " \\n[rst]-\\n[rsb]-%dM-\\n[" TEMP_REG "]\n",
+ default_rule_thickness);
+ // If TEMP_REG is negative, the bottom of the radical sign should
+ // be -TEMP_REG above the bottom of p. If it's positive, the bottom
+ // of the radical sign should be TEMP_REG/2 below the bottom of p.
+ // This calculates the amount by which the baseline of the radical
+ // should be raised.
+ printf(".nr " SUP_RAISE_FORMAT " (-\\n[" TEMP_REG "]>?(-\\n[" TEMP_REG "]/2))"
+ "-\\n[rsb]-\\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?(\\n[" SUP_RAISE_FORMAT "]+\\n[rst])\n",
+ uid, p->uid, uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ ">?(-\\n[" SUP_RAISE_FORMAT "]-\\n[rsb])\n",
+ uid, p->uid, uid);
+ // Do this last, so we don't lose height and depth information on
+ // the radical sign.
+ // Remember that the width of the bar might be greater than the width of p.
+
+ printf(".nr " TEMP_REG " "
+ "\\n[" WIDTH_FORMAT "]"
+ ">?\\w" DELIMITER_CHAR "\\*[" BAR_STRING "]" DELIMITER_CHAR "\n",
+ p->uid);
+ printf(".as " SQRT_STRING_FORMAT " "
+ "\\l'\\n[" TEMP_REG "]u\\&\\*[" BAR_STRING "]'\n",
+ uid);
+ printf(".nr " WIDTH_FORMAT " \\n[" TEMP_REG "]"
+ "+\\n[" SQRT_WIDTH_FORMAT "]\n",
+ uid, uid);
+
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" SQRT_WIDTH_FORMAT "]\n", uid);
+ // the top of the bar might be higher than the top of the radical sign
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?(\\n[" SUP_RAISE_FORMAT "]+\\n[rst])\n",
+ uid, p->uid, uid);
+ // put a bit of extra space above the bar
+ printf(".nr " HEIGHT_FORMAT " +%dM\n", uid, default_rule_thickness);
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ return r;
+}
+
+void sqrt_box::output()
+{
+ if (output_format == troff) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\*[" SQRT_STRING_FORMAT "]", uid);
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf(DELIMITER_CHAR);
+
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u"
+ "+\\n[" SQRT_WIDTH_FORMAT "]u/2u'",
+ uid, p->uid, uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<msqrt>");
+ p->output();
+ printf("</msqrt>");
+ }
+}
+
+void sqrt_box::debug_print()
+{
+ fprintf(stderr, "sqrt { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+void sqrt_box::check_tabs(int level)
+{
+ p->check_tabs(level + 1);
+}
diff --git a/src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh b/src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh
new file mode 100755
index 0000000..feb78c1
--- /dev/null
+++ b/src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# 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/>.
+#
+
+eqn="${abs_top_builddir:-.}/eqn"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$error"
+ fail=yes
+}
+
+# Verify that correct input file line numbers are reported.
+
+echo "checking for absent line number in early EOF diagnostic" >&2
+input='.EQ'
+error=$(printf "%s\n" "$input" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+: fatal' || wail
+
+echo "checking for correct line number in nested 'EQ' diagnostic" >&2
+input='.EQ
+.EQ'
+error=$(printf "%s\n" "$input" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+:2: fatal' || wail
+
+echo "checking for correct line number in invalid input character" \
+ "diagnostic" >&2
+error=$(printf ".EQ\nx\n.EN\n\200\n" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+:4: error' || wail
+
+echo "checking for correct line number in invalid input character" \
+ "diagnostic when 'lf' request used beforehand" >&2
+error=$(printf ".EQ\nx\n.EN\n.lf 99\n\200\n" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+:99: error' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/eqn/text.cpp b/src/preproc/eqn/text.cpp
new file mode 100644
index 0000000..272f3fe
--- /dev/null
+++ b/src/preproc/eqn/text.cpp
@@ -0,0 +1,957 @@
+// -*- C++ -*-
+/* 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/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdlib.h>
+#include "eqn.h"
+#include "pbox.h"
+#include "ptable.h"
+
+struct map {
+ const char *from;
+ const char *to;
+};
+
+struct map entity_table[] = {
+ // Classic troff special characters
+ {"%", "&shy;"}, // ISOnum
+ {"'", "&acute;"}, // ISOdia
+ {"!=", "&ne;"}, // ISOtech
+ {"**", "&lowast;"}, // ISOtech
+ {"*a", "&alpha;"}, // ISOgrk3
+ {"*A", "A"},
+ {"*b", "&beta;"}, // ISOgrk3
+ {"*B", "B"},
+ {"*d", "&delta;"}, // ISOgrk3
+ {"*D", "&Delta;"}, // ISOgrk3
+ {"*e", "&epsilon;"}, // ISOgrk3
+ {"*E", "E"},
+ {"*f", "&phi;"}, // ISOgrk3
+ {"*F", "&Phi;"}, // ISOgrk3
+ {"*g", "&gamma;"}, // ISOgrk3
+ {"*G", "&Gamma;"}, // ISOgrk3
+ {"*h", "&theta;"}, // ISOgrk3
+ {"*H", "&Theta;"}, // ISOgrk3
+ {"*i", "&iota;"}, // ISOgrk3
+ {"*I", "I"},
+ {"*k", "&kappa;"}, // ISOgrk3
+ {"*K", "K;"},
+ {"*l", "&lambda;"}, // ISOgrk3
+ {"*L", "&Lambda;"}, // ISOgrk3
+ {"*m", "&mu;"}, // ISOgrk3
+ {"*M", "M"},
+ {"*n", "&nu;"}, // ISOgrk3
+ {"*N", "N"},
+ {"*o", "o"},
+ {"*O", "O"},
+ {"*p", "&pi;"}, // ISOgrk3
+ {"*P", "&Pi;"}, // ISOgrk3
+ {"*q", "&psi;"}, // ISOgrk3
+ {"*Q", "&PSI;"}, // ISOgrk3
+ {"*r", "&rho;"}, // ISOgrk3
+ {"*R", "R"},
+ {"*s", "&sigma;"}, // ISOgrk3
+ {"*S", "&Sigma;"}, // ISOgrk3
+ {"*t", "&tau;"}, // ISOgrk3
+ {"*T", "&Tau;"}, // ISOgrk3
+ {"*u", "&upsilon;"}, // ISOgrk3
+ {"*U", "&Upsilon;"}, // ISOgrk3
+ {"*w", "&omega;"}, // ISOgrk3
+ {"*W", "&Omega;"}, // ISOgrk3
+ {"*x", "&chi;"}, // ISOgrk3
+ {"*X", "&Chi;"}, // ISOgrk3
+ {"*y", "&eta;"}, // ISOgrk3
+ {"*Y", "&Eta;"}, // ISOgrk3
+ {"*z", "&zeta;"}, // ISOgrk3
+ {"*Z", "&Zeta;"}, // ISOgrk3
+ {"+-", "&plusmn;"}, // ISOnum
+ {"->", "&rarr;"}, // ISOnum
+ {"12", "&frac12;"}, // ISOnum
+ {"14", "&frac14;"}, // ISOnum
+ {"34", "&frac34;"}, // ISOnum
+ {"<-", "&larr;"}, // ISOnum
+ {"==", "&equiv;"}, // ISOtech
+ {"Fi", "&ffilig;"}, // ISOpub
+ {"Fl", "&ffllig;"}, // ISOpub
+ {"aa", "&acute;"}, // ISOdia
+ {"ap", "&sim;"}, // ISOtech
+ {"bl", "&phonexb;"}, // ISOpub
+ {"br", "&boxv;"}, // ISObox
+ {"bs", "&phone;"}, // ISOpub (for the Bell logo)
+ {"bu", "&bull;"}, // ISOpub
+ {"bv", "&verbar;"}, // ISOnum
+ {"ca", "&cap;"}, // ISOtech
+ {"ci", "&cir;"}, // ISOpub
+ {"co", "&copy;"}, // ISOnum
+ {"ct", "&cent;"}, // ISOnum
+ {"cu", "&cup;"}, // ISOtech
+ {"da", "&darr;"}, // ISOnum
+ {"de", "&deg;"}, // ISOnum
+ {"dg", "&dagger;"}, // ISOpub
+ {"dd", "&Dagger;"}, // ISOpub
+ {"di", "&divide;"}, // ISOnum
+ {"em", "&mdash;"}, // ISOpub
+ {"eq", "&equals;"}, // ISOnum
+ {"es", "&empty;"}, // ISOamso
+ {"ff", "&fflig;"}, // ISOpub
+ {"fi", "&filig;"}, // ISOpub
+ {"fl", "&fllig;"}, // ISOpub
+ {"fm", "&prime;"}, // ISOtech
+ {"ge", "&ge;"}, // ISOtech
+ {"gr", "&nabla;"}, // ISOtech
+ {"hy", "&hyphen;"}, // ISOnum
+ {"ib", "&sube;"}, // ISOtech
+ {"if", "&infin;"}, // ISOtech
+ {"ip", "&supe;"}, // ISOtech
+ {"is", "&int;"}, // ISOtech
+ {"le", "&le;"}, // ISOtech
+ // Some pile characters go here
+ {"mi", "&minus;"}, // ISOtech
+ {"mo", "&isin;"}, // ISOtech
+ {"mu", "&times;"}, // ISOnum
+ {"no", "&not;"}, // ISOnum
+ {"or", "&verbar;"}, // ISOnum
+ {"pl", "&plus;"}, // ISOnum
+ {"pt", "&prop;"}, // ISOtech
+ {"rg", "&trade;"}, // ISOnum
+ // More pile characters go here
+ {"rn", "&macr;"}, // ISOdia
+ {"ru", "&lowbar;"}, // ISOnum
+ {"sb", "&sub;"}, // ISOtech
+ {"sc", "&sect;"}, // ISOnum
+ {"sl", "/"},
+ {"sp", "&sup;"}, // ISOtech
+ {"sq", "&squf;"}, // ISOpub
+ {"sr", "&radic;"}, // ISOtech
+ {"ts", "&sigmav;"}, // ISOgrk3
+ {"ua", "&uarr;"}, // ISOnum
+ {"ul", "_"},
+ {"~=", "&cong;"}, // ISOtech
+ // Extended specials supported by groff; see groff_char(7).
+ // These are listed in the order they occur on that man page.
+ {"-D", "&ETH;"}, // ISOlat: Icelandic uppercase eth
+ {"Sd", "&eth;"}, // ISOlat1: Icelandic lowercase eth
+ {"TP", "&THORN;"}, // ISOlat1: Icelandic uppercase thorn
+ {"Tp", "&thorn;"}, // ISOlat1: Icelandic lowercase thorn
+ {"ss", "&szlig;"}, // ISOlat1
+ // Ligatures
+ // ff, fi, fl, ffi, ffl from old troff go here
+ {"AE", "&AElig;"}, // ISOlat1
+ {"ae", "&aelig;"}, // ISOlat1
+ {"OE", "&OElig;"}, // ISOlat2
+ {"oe", "&oelig;"}, // ISOlat2
+ {"IJ", "&ijlig;"}, // ISOlat2: Dutch IJ ligature
+ {"ij", "&IJlig;"}, // ISOlat2: Dutch ij ligature
+ {".i", "&inodot;"}, // ISOlat2,ISOamso
+ {".j", "&jnodot;"}, // ISOamso (undocumented but in 1.19)
+ // Accented characters
+ {"'A", "&Aacute;"}, // ISOlat1
+ {"'C", "&Cacute;"}, // ISOlat2
+ {"'E", "&Eacute;"}, // ISOlat1
+ {"'I", "&Iacute;"}, // ISOlat1
+ {"'O", "&Oacute;"}, // ISOlat1
+ {"'U", "&Uacute;"}, // ISOlat1
+ {"'Y", "&Yacute;"}, // ISOlat1
+ {"'a", "&aacute;"}, // ISOlat1
+ {"'c", "&cacute;"}, // ISOlat2
+ {"'e", "&eacute;"}, // ISOlat1
+ {"'i", "&iacute;"}, // ISOlat1
+ {"'o", "&oacute;"}, // ISOlat1
+ {"'u", "&uacute;"}, // ISOlat1
+ {"'y", "&yacute;"}, // ISOlat1
+ {":A", "&Auml;"}, // ISOlat1
+ {":E", "&Euml;"}, // ISOlat1
+ {":I", "&Iuml;"}, // ISOlat1
+ {":O", "&Ouml;"}, // ISOlat1
+ {":U", "&Uuml;"}, // ISOlat1
+ {":Y", "&Yuml;"}, // ISOlat2
+ {":a", "&auml;"}, // ISOlat1
+ {":e", "&euml;"}, // ISOlat1
+ {":i", "&iuml;"}, // ISOlat1
+ {":o", "&ouml;"}, // ISOlat1
+ {":u", "&uuml;"}, // ISOlat1
+ {":y", "&yuml;"}, // ISOlat1
+ {"^A", "&Acirc;"}, // ISOlat1
+ {"^E", "&Ecirc;"}, // ISOlat1
+ {"^I", "&Icirc;"}, // ISOlat1
+ {"^O", "&Ocirc;"}, // ISOlat1
+ {"^U", "&Ucirc;"}, // ISOlat1
+ {"^a", "&acirc;"}, // ISOlat1
+ {"^e", "&ecirc;"}, // ISOlat1
+ {"^i", "&icirc;"}, // ISOlat1
+ {"^o", "&ocirc;"}, // ISOlat1
+ {"^u", "&ucirc;"}, // ISOlat1
+ {"`A", "&Agrave;"}, // ISOlat1
+ {"`E", "&Egrave;"}, // ISOlat1
+ {"`I", "&Igrave;"}, // ISOlat1
+ {"`O", "&Ograve;"}, // ISOlat1
+ {"`U", "&Ugrave;"}, // ISOlat1
+ {"`a", "&agrave;"}, // ISOlat1
+ {"`e", "&egrave;"}, // ISOlat1
+ {"`i", "&igrave;"}, // ISOlat1
+ {"`o", "&ograve;"}, // ISOlat1
+ {"`u", "&ugrave;"}, // ISOlat1
+ {"~A", "&Atilde;"}, // ISOlat1
+ {"~N", "&Ntilde;"}, // ISOlat1
+ {"~O", "&Otilde;"}, // ISOlat1
+ {"~a", "&atilde;"}, // ISOlat1
+ {"~n", "&ntilde;"}, // ISOlat1
+ {"~o", "&otilde;"}, // ISOlat1
+ {"vS", "&Scaron;"}, // ISOlat2
+ {"vs", "&scaron;"}, // ISOlat2
+ {"vZ", "&Zcaron;"}, // ISOlat2
+ {"vz", "&zcaron;"}, // ISOlat2
+ {",C", "&Ccedil;"}, // ISOlat1
+ {",c", "&ccedil;"}, // ISOlat1
+ {"/L", "&Lstrok;"}, // ISOlat2: Polish L with a slash
+ {"/l", "&lstrok;"}, // ISOlat2: Polish l with a slash
+ {"/O", "&Oslash;"}, // ISOlat1
+ {"/o", "&oslash;"}, // ISOlat1
+ {"oA", "&Aring;"}, // ISOlat1
+ {"oa", "&aring;"}, // ISOlat1
+ // Accents
+ {"a\"","&dblac;"}, // ISOdia: double acute accent (Hungarian umlaut)
+ {"a-", "&macr;"}, // ISOdia: macron or bar accent
+ {"a.", "&dot;"}, // ISOdia: dot above
+ {"a^", "&circ;"}, // ISOdia: circumflex accent
+ {"aa", "&acute;"}, // ISOdia: acute accent
+ {"ga", "&grave;"}, // ISOdia: grave accent
+ {"ab", "&breve;"}, // ISOdia: breve accent
+ {"ac", "&cedil;"}, // ISOdia: cedilla accent
+ {"ad", "&uml;"}, // ISOdia: umlaut or dieresis
+ {"ah", "&caron;"}, // ISOdia: caron (aka hacek accent)
+ {"ao", "&ring;"}, // ISOdia: ring or circle accent
+ {"a~", "&tilde;"}, // ISOdia: tilde accent
+ {"ho", "&ogon;"}, // ISOdia: hook or ogonek accent
+ {"ha", "^"}, // ASCII circumflex, hat, caret
+ {"ti", "~"}, // ASCII tilde, large tilde
+ // Quotes
+ {"Bq", "&lsquor;"}, // ISOpub: low double comma quote
+ {"bq", "&ldquor;"}, // ISOpub: low single comma quote
+ {"lq", "&ldquo;"}, // ISOnum
+ {"rq", "&rdquo;"}, // ISOpub
+ {"oq", "&lsquo;"}, // ISOnum: single open quote
+ {"cq", "&rsquo;"}, // ISOnum: single closing quote (ASCII 39)
+ {"aq", "&zerosp;'"}, // apostrophe quote
+ {"dq", "\""}, // double quote (ASCII 34)
+ {"Fo", "&laquo;"}, // ISOnum
+ {"Fc", "&raquo;"}, // ISOnum
+ //{"fo", "&fo;"},
+ //{"fc", "&fc;"},
+ // Punctuation
+ {"r!", "&iexcl;"}, // ISOnum
+ {"r?", "&iquest;"}, // ISOnum
+ // Old troff \(em goes here
+ {"en", "&ndash;"}, // ISOpub: en dash
+ // Old troff \(hy goes here
+ // Brackets
+ {"lB", "&lsqb;"}, // ISOnum: left (square) bracket
+ {"rB", "&rsqb;"}, // ISOnum: right (square) bracket
+ {"lC", "&lcub;"}, // ISOnum: left (curly) brace
+ {"rC", "&rcub;"}, // ISOnum: right (curly) brace
+ {"la", "&lang;"}, // ISOtech: left angle bracket
+ {"ra", "&rang;"}, // ISOtech: right angle bracket
+ // Old troff \(bv goes here
+ // Bracket-pile characters could go here.
+ // Arrows
+ // Old troff \(<- and \(-> go here
+ {"<>", "&harr;"}, // ISOamsa
+ {"da", "&darr;"}, // ISOnum
+ {"ua", "&uarr;"}, // ISOnum
+ {"lA", "&lArr;"}, // ISOtech
+ {"rA", "&rArr;"}, // ISOtech
+ {"hA", "&iff;"}, // ISOtech: horizontal double-headed arrow
+ {"dA", "&dArr;"}, // ISOamsa
+ {"uA", "&uArr;"}, // ISOamsa
+ {"vA", "&vArr;"}, // ISOamsa: vertical double-headed double arrow
+ //{"an", "&an;"},
+ // Lines
+ {"-h", "&planck;"}, // ISOamso: h-bar (Planck's constant)
+ // Old troff \(or goes here
+ {"ba", "&verbar;"}, // ISOnum
+ // Old troff \(br, \{u, \(ul, \(bv go here
+ {"bb", "&brvbar;"}, // ISOnum
+ {"sl", "/"},
+ {"rs", "&bsol;"}, // ISOnum
+ // Text markers
+ // Old troff \(ci, \(bu, \(dd, \(dg go here
+ {"lz", "&loz;"}, // ISOpub
+ // Old troff sq goes here
+ {"ps", "&para;"}, // ISOnum: paragraph or pilcrow sign
+ {"sc", "&sect;"}, // ISOnum (in old troff)
+ // Old troff \(lh, \{h go here
+ {"at", "&commat;"}, // ISOnum
+ {"sh", "&num;"}, // ISOnum
+ //{"CR", "&CR;"},
+ {"OK", "&check;"}, // ISOpub
+ // Legalize
+ // Old troff \(co, \{g go here
+ {"tm", "&trade;"}, // ISOnum
+ // Currency symbols
+ {"Do", "&dollar;"}, // ISOnum
+ {"ct", "&cent;"}, // ISOnum
+ {"eu", "&euro;"},
+ {"Eu", "&euro;"},
+ {"Ye", "&yen;"}, // ISOnum
+ {"Po", "&pound;"}, // ISOnum
+ {"Cs", "&curren;"}, // ISOnum: currency sign
+ {"Fn", "&fnof"}, // ISOtech
+ // Units
+ // Old troff de goes here
+ {"%0", "&permil;"}, // ISOtech: per thousand, per mille sign
+ // Old troff \(fm goes here
+ {"sd", "&Prime;"}, // ISOtech
+ {"mc", "&micro;"}, // ISOnum
+ {"Of", "&ordf;"}, // ISOnum
+ {"Om", "&ordm;"}, // ISOnum
+ // Logical symbols
+ {"AN", "&and;"}, // ISOtech
+ {"OR", "&or;"}, // ISOtech
+ // Old troff \(no goes here
+ {"te", "&exist;"}, // ISOtech: there exists, existential quantifier
+ {"fa", "&forall;"}, // ISOtech: for all, universal quantifier
+ {"st", "&bepsi"}, // ISOamsr: such that
+ {"3d", "&there4;"}, // ISOtech
+ {"tf", "&there4;"}, // ISOtech
+ // Mathematical symbols
+ // Old troff "12", "14", "34" goes here
+ {"S1", "&sup1;"}, // ISOnum
+ {"S2", "&sup2;"}, // ISOnum
+ {"S3", "&sup3;"}, // ISOnum
+ // Old troff \(pl", \-, \(+- go here
+ {"t+-", "&plusmn;"}, // ISOnum
+ {"-+", "&mnplus;"}, // ISOtech
+ {"pc", "&middot;"}, // ISOnum
+ {"md", "&middot;"}, // ISOnum
+ // Old troff \(mu goes here
+ {"tmu", "&times;"}, // ISOnum
+ {"c*", "&otimes;"}, // ISOamsb: multiply sign in a circle
+ {"c+", "&oplus;"}, // ISOamsb: plus sign in a circle
+ // Old troff \(di goes here
+ {"tdi", "&divide;"}, // ISOnum
+ {"f/", "&horbar;"}, // ISOnum: horizintal bar for fractions
+ // Old troff \(** goes here
+ {"<=", "&le;"}, // ISOtech
+ {">=", "&ge;"}, // ISOtech
+ {"<<", "&Lt;"}, // ISOamsr
+ {">>", "&Gt;"}, // ISOamsr
+ {"!=", "&ne;"}, // ISOtech
+ // Old troff \(eq and \(== go here
+ {"=~", "&cong;"}, // ISOamsr
+ // Old troff \(ap goes here
+ {"~~", "&ap;"}, // ISOtech
+ // This appears to be an error in the groff table.
+ // It clashes with the Bell Labs use of ~= for a congruence sign
+ // {"~=", "&ap;"}, // ISOamsr
+ // Old troff \(pt, \(es, \(mo go here
+ {"nm", "&notin;"}, // ISOtech
+ {"nb", "&nsub;"}, // ISOamsr
+ {"nc", "&nsup;"}, // ISOamsn
+ {"ne", "&nequiv;"}, // ISOamsn
+ // Old troff \(sb, \(sp, \(ib, \(ip, \(ca, \(cu go here
+ {"/_", "&ang;"}, // ISOamso
+ {"pp", "&perp;"}, // ISOtech
+ // Old troff \(is goes here
+ {"sum", "&sum;"}, // ISOamsb
+ {"product", "&prod;"}, // ISOamsb
+ {"gr", "&nabla;"}, // ISOtech
+ // Old troff \(sr. \{n, \(if go here
+ {"Ah", "&aleph;"}, // ISOtech
+ {"Im", "&image;"}, // ISOamso: Fraktur I, imaginary
+ {"Re", "&real;"}, // ISOamso: Fraktur R, real
+ {"wp", "&weierp;"}, // ISOamso
+ {"pd", "&part;"}, // ISOtech: partial differentiation sign
+ // Their table duplicates the Greek letters here.
+ // We list only the variant forms here, mapping them into
+ // the ISO Greek 4 variants (which may or may not be correct :-()
+ {"+f", "&b.phiv;"}, // ISOgrk4: variant phi
+ {"+h", "&b.thetas;"}, // ISOgrk4: variant theta
+ {"+p", "&b.omega;"}, // ISOgrk4: variant pi, looking like omega
+ // Card symbols
+ {"CL", "&clubs;"}, // ISOpub: club suit
+ {"SP", "&spades;"}, // ISOpub: spade suit
+ {"HE", "&hearts;"}, // ISOpub: heart suit
+ {"DI", "&diams;"}, // ISOpub: diamond suit
+};
+
+const char *special_to_entity(const char *sp)
+{
+ struct map *mp;
+ for (mp = entity_table;
+ mp < entity_table + sizeof(entity_table)/sizeof(entity_table[0]);
+ mp++) {
+ if (strcmp(mp->from, sp) == 0)
+ return mp->to;
+ }
+ return NULL;
+}
+
+class char_box : public simple_box {
+ unsigned char c;
+ char next_is_italic;
+ char prev_is_italic;
+public:
+ char_box(unsigned char);
+ void debug_print();
+ void output();
+ int is_char();
+ int left_is_italic();
+ int right_is_italic();
+ void hint(unsigned);
+ void handle_char_type(int, int);
+};
+
+class special_char_box : public simple_box {
+ char *s;
+public:
+ special_char_box(const char *);
+ ~special_char_box();
+ void output();
+ void debug_print();
+ int is_char();
+ void handle_char_type(int, int);
+};
+
+enum spacing_type {
+ s_ordinary,
+ s_operator,
+ s_binary,
+ s_relation,
+ s_opening,
+ s_closing,
+ s_punctuation,
+ s_inner,
+ s_suppress
+};
+
+const char *spacing_type_table[] = {
+ "ordinary",
+ "operator",
+ "binary",
+ "relation",
+ "opening",
+ "closing",
+ "punctuation",
+ "inner",
+ "suppress",
+ 0,
+};
+
+const int DIGIT_TYPE = 0;
+const int LETTER_TYPE = 1;
+
+const char *font_type_table[] = {
+ "digit",
+ "letter",
+ 0,
+};
+
+struct char_info {
+ int spacing_type;
+ int font_type;
+ char_info();
+};
+
+char_info::char_info()
+: spacing_type(ORDINARY_TYPE), font_type(DIGIT_TYPE)
+{
+}
+
+static char_info char_table[256];
+
+declare_ptable(char_info)
+implement_ptable(char_info)
+
+PTABLE(char_info) special_char_table;
+
+static int get_special_char_spacing_type(const char *ch)
+{
+ char_info *p = special_char_table.lookup(ch);
+ return p ? p->spacing_type : ORDINARY_TYPE;
+}
+
+static int get_special_char_font_type(const char *ch)
+{
+ char_info *p = special_char_table.lookup(ch);
+ return p ? p->font_type : DIGIT_TYPE;
+}
+
+static void set_special_char_type(const char *ch, int st, int ft)
+{
+ char_info *p = special_char_table.lookup(ch);
+ if (!p) {
+ p = new char_info[1];
+ special_char_table.define(ch, p);
+ }
+ if (st >= 0)
+ p->spacing_type = st;
+ if (ft >= 0)
+ p->font_type = ft;
+}
+
+void init_char_table()
+{
+ set_special_char_type("pl", s_binary, -1);
+ set_special_char_type("mi", s_binary, -1);
+ set_special_char_type("eq", s_relation, -1);
+ set_special_char_type("<=", s_relation, -1);
+ set_special_char_type(">=", s_relation, -1);
+ char_table['}'].spacing_type = s_closing;
+ char_table[')'].spacing_type = s_closing;
+ char_table[']'].spacing_type = s_closing;
+ char_table['{'].spacing_type = s_opening;
+ char_table['('].spacing_type = s_opening;
+ char_table['['].spacing_type = s_opening;
+ char_table[','].spacing_type = s_punctuation;
+ char_table[';'].spacing_type = s_punctuation;
+ char_table[':'].spacing_type = s_punctuation;
+ char_table['.'].spacing_type = s_punctuation;
+ char_table['>'].spacing_type = s_relation;
+ char_table['<'].spacing_type = s_relation;
+ char_table['*'].spacing_type = s_binary;
+ for (int i = 0; i < 256; i++)
+ if (csalpha(i))
+ char_table[i].font_type = LETTER_TYPE;
+}
+
+static int lookup_spacing_type(const char *type)
+{
+ for (int i = 0; spacing_type_table[i] != 0; i++)
+ if (strcmp(spacing_type_table[i], type) == 0)
+ return i;
+ return -1;
+}
+
+static int lookup_font_type(const char *type)
+{
+ for (int i = 0; font_type_table[i] != 0; i++)
+ if (strcmp(font_type_table[i], type) == 0)
+ return i;
+ return -1;
+}
+
+void box::set_spacing_type(char *type)
+{
+ int t = lookup_spacing_type(type);
+ if (t < 0)
+ error("unrecognised type '%1'", type);
+ else
+ spacing_type = t;
+ free(type);
+}
+
+char_box::char_box(unsigned char cc)
+: c(cc), next_is_italic(0), prev_is_italic(0)
+{
+ spacing_type = char_table[c].spacing_type;
+}
+
+void char_box::hint(unsigned flags)
+{
+ if (flags & HINT_PREV_IS_ITALIC)
+ prev_is_italic = 1;
+ if (flags & HINT_NEXT_IS_ITALIC)
+ next_is_italic = 1;
+}
+
+void char_box::output()
+{
+ if (output_format == troff) {
+ int font_type = char_table[c].font_type;
+ if (font_type != LETTER_TYPE)
+ printf("\\f[%s]", current_roman_font);
+ if (!prev_is_italic)
+ fputs("\\,", stdout);
+ if (c == '\\')
+ fputs("\\e", stdout);
+ else
+ putchar(c);
+ if (!next_is_italic)
+ fputs("\\/", stdout);
+ else
+ fputs("\\&", stdout); // suppress ligaturing and kerning
+ if (font_type != LETTER_TYPE)
+ fputs("\\fP", stdout);
+ }
+ else if (output_format == mathml) {
+ if (isdigit(c))
+ printf("<mn>");
+ else if (char_table[c].spacing_type)
+ printf("<mo>");
+ else
+ printf("<mi>");
+ if (c == '<')
+ printf("&lt;");
+ else if (c == '>')
+ printf("&gt;");
+ else if (c == '&')
+ printf("&amp;");
+ else
+ putchar(c);
+ if (isdigit(c))
+ printf("</mn>");
+ else if (char_table[c].spacing_type)
+ printf("</mo>");
+ else
+ printf("</mi>");
+ }
+}
+
+int char_box::left_is_italic()
+{
+ int font_type = char_table[c].font_type;
+ return font_type == LETTER_TYPE;
+}
+
+int char_box::right_is_italic()
+{
+ int font_type = char_table[c].font_type;
+ return font_type == LETTER_TYPE;
+}
+
+int char_box::is_char()
+{
+ return 1;
+}
+
+void char_box::debug_print()
+{
+ if (c == '\\') {
+ putc('\\', stderr);
+ putc('\\', stderr);
+ }
+ else
+ putc(c, stderr);
+}
+
+special_char_box::special_char_box(const char *t)
+{
+ s = strsave(t);
+ spacing_type = get_special_char_spacing_type(s);
+}
+
+special_char_box::~special_char_box()
+{
+ free(s);
+}
+
+void special_char_box::output()
+{
+ if (output_format == troff) {
+ int font_type = get_special_char_font_type(s);
+ if (font_type != LETTER_TYPE)
+ printf("\\f[%s]", current_roman_font);
+ printf("\\,\\[%s]\\/", s);
+ if (font_type != LETTER_TYPE)
+ printf("\\fP");
+ }
+ else if (output_format == mathml) {
+ const char *entity = special_to_entity(s);
+ if (entity != NULL)
+ printf("<mo>%s</mo>", entity);
+ else
+ printf("<merror>unknown eqn/troff special char %s</merror>", s);
+ }
+}
+
+int special_char_box::is_char()
+{
+ return 1;
+}
+
+void special_char_box::debug_print()
+{
+ fprintf(stderr, "\\[%s]", s);
+}
+
+
+void char_box::handle_char_type(int st, int ft)
+{
+ if (st >= 0)
+ char_table[c].spacing_type = st;
+ if (ft >= 0)
+ char_table[c].font_type = ft;
+}
+
+void special_char_box::handle_char_type(int st, int ft)
+{
+ set_special_char_type(s, st, ft);
+}
+
+void set_char_type(const char *type, char *ch)
+{
+ assert(ch != 0);
+ int st = lookup_spacing_type(type);
+ int ft = lookup_font_type(type);
+ if (st < 0 && ft < 0) {
+ error("bad character type '%1'", type);
+ delete[] ch;
+ return;
+ }
+ box *b = split_text(ch);
+ b->handle_char_type(st, ft);
+ delete b;
+}
+
+/* We give primes special treatment so that in "x' sub 2", the "2"
+will be tucked under the prime */
+
+class prime_box : public pointer_box {
+ box *pb;
+public:
+ prime_box(box *);
+ ~prime_box();
+ int compute_metrics(int style);
+ void output();
+ void compute_subscript_kern();
+ void debug_print();
+ void handle_char_type(int, int);
+};
+
+box *make_prime_box(box *pp)
+{
+ return new prime_box(pp);
+}
+
+prime_box::prime_box(box *pp) : pointer_box(pp)
+{
+ pb = new special_char_box("fm");
+}
+
+prime_box::~prime_box()
+{
+ delete pb;
+}
+
+int prime_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ pb->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]"
+ "+\\n[" WIDTH_FORMAT "]\n",
+ uid, p->uid, pb->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?\\n[" HEIGHT_FORMAT "]\n",
+ uid, p->uid, pb->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ ">?\\n[" DEPTH_FORMAT "]\n",
+ uid, p->uid, pb->uid);
+ return res;
+}
+
+void prime_box::compute_subscript_kern()
+{
+ p->compute_subscript_kern();
+ printf(".nr " SUB_KERN_FORMAT " 0\\n[" WIDTH_FORMAT "]"
+ "+\\n[" SUB_KERN_FORMAT "]>?0\n",
+ uid, pb->uid, p->uid);
+}
+
+void prime_box::output()
+{
+ p->output();
+ pb->output();
+}
+
+void prime_box::handle_char_type(int st, int ft)
+{
+ p->handle_char_type(st, ft);
+ pb->handle_char_type(st, ft);
+}
+
+void prime_box::debug_print()
+{
+ p->debug_print();
+ putc('\'', stderr);
+}
+
+box *split_text(char *text)
+{
+ list_box *lb = 0;
+ box *fb = 0;
+ char *s = text;
+ while (*s != '\0') {
+ char c = *s++;
+ box *b = 0;
+ switch (c) {
+ case '+':
+ b = new special_char_box("pl");
+ break;
+ case '-':
+ b = new special_char_box("mi");
+ break;
+ case '=':
+ b = new special_char_box("eq");
+ break;
+ case '\'':
+ b = new special_char_box("fm");
+ break;
+ case '<':
+ if (*s == '=') {
+ b = new special_char_box("<=");
+ s++;
+ break;
+ }
+ goto normal_char;
+ case '>':
+ if (*s == '=') {
+ b = new special_char_box(">=");
+ s++;
+ break;
+ }
+ goto normal_char;
+ case '\\':
+ if (*s == '\0') {
+ lex_error("bad escape");
+ break;
+ }
+ c = *s++;
+ switch (c) {
+ case '(':
+ {
+ char buf[3];
+ if (*s != '\0') {
+ buf[0] = *s++;
+ if (*s != '\0') {
+ buf[1] = *s++;
+ buf[2] = '\0';
+ b = new special_char_box(buf);
+ }
+ else {
+ lex_error("bad escape");
+ }
+ }
+ else {
+ lex_error("bad escape");
+ }
+ }
+ break;
+ case '[':
+ {
+ char *ch = s;
+ while (*s != ']' && *s != '\0')
+ s++;
+ if (*s == '\0')
+ lex_error("bad escape");
+ else {
+ *s++ = '\0';
+ b = new special_char_box(ch);
+ }
+ }
+ break;
+ case 'f':
+ case 'g':
+ case 'k':
+ case 'n':
+ case '*':
+ {
+ char *escape_start = s - 2;
+ switch (*s) {
+ case '(':
+ if (*++s != '\0')
+ ++s;
+ break;
+ case '[':
+ for (++s; *s != '\0' && *s != ']'; s++)
+ ;
+ break;
+ }
+ if (*s == '\0')
+ lex_error("bad escape");
+ else {
+ ++s;
+ char *buf = new char[s - escape_start + 1];
+ memcpy(buf, escape_start, s - escape_start);
+ buf[s - escape_start] = '\0';
+ b = new quoted_text_box(buf);
+ }
+ }
+ break;
+ case '-':
+ case '_':
+ {
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ b = new special_char_box(buf);
+ }
+ break;
+ case '`':
+ b = new special_char_box("ga");
+ break;
+ case '\'':
+ b = new special_char_box("aa");
+ break;
+ case 'e':
+ case '\\':
+ b = new char_box('\\');
+ break;
+ case '^':
+ case '|':
+ case '0':
+ {
+ char buf[3];
+ buf[0] = '\\';
+ buf[1] = c;
+ buf[2] = '\0';
+ b = new quoted_text_box(strsave(buf));
+ break;
+ }
+ default:
+ lex_error("unquoted escape");
+ b = new quoted_text_box(strsave(s - 2));
+ s = strchr(s, '\0');
+ break;
+ }
+ break;
+ default:
+ normal_char:
+ b = new char_box(c);
+ break;
+ }
+ while (*s == '\'') {
+ if (b == 0)
+ b = new quoted_text_box(0);
+ b = new prime_box(b);
+ s++;
+ }
+ if (b != 0) {
+ if (lb != 0)
+ lb->append(b);
+ else if (fb != 0) {
+ lb = new list_box(fb);
+ lb->append(b);
+ }
+ else
+ fb = b;
+ }
+ }
+ free(text);
+ if (lb != 0)
+ return lb;
+ else if (fb != 0)
+ return fb;
+ else
+ return new quoted_text_box(0);
+}
+