diff options
Diffstat (limited to '')
105 files changed, 55894 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", ¹ }, + { "sup2", ² }, + { "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) + //    doesn't display right under Firefox 1.5. + printf("<mtext> </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> </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>¯</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>¯</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>_</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>¯</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 + {"%", "­"}, // ISOnum + {"'", "´"}, // ISOdia + {"!=", "≠"}, // ISOtech + {"**", "∗"}, // ISOtech + {"*a", "α"}, // ISOgrk3 + {"*A", "A"}, + {"*b", "β"}, // ISOgrk3 + {"*B", "B"}, + {"*d", "δ"}, // ISOgrk3 + {"*D", "Δ"}, // ISOgrk3 + {"*e", "ε"}, // ISOgrk3 + {"*E", "E"}, + {"*f", "φ"}, // ISOgrk3 + {"*F", "Φ"}, // ISOgrk3 + {"*g", "γ"}, // ISOgrk3 + {"*G", "Γ"}, // ISOgrk3 + {"*h", "θ"}, // ISOgrk3 + {"*H", "Θ"}, // ISOgrk3 + {"*i", "ι"}, // ISOgrk3 + {"*I", "I"}, + {"*k", "κ"}, // ISOgrk3 + {"*K", "K;"}, + {"*l", "λ"}, // ISOgrk3 + {"*L", "Λ"}, // ISOgrk3 + {"*m", "μ"}, // ISOgrk3 + {"*M", "M"}, + {"*n", "ν"}, // ISOgrk3 + {"*N", "N"}, + {"*o", "o"}, + {"*O", "O"}, + {"*p", "π"}, // ISOgrk3 + {"*P", "Π"}, // ISOgrk3 + {"*q", "ψ"}, // ISOgrk3 + {"*Q", "&PSI;"}, // ISOgrk3 + {"*r", "ρ"}, // ISOgrk3 + {"*R", "R"}, + {"*s", "σ"}, // ISOgrk3 + {"*S", "Σ"}, // ISOgrk3 + {"*t", "τ"}, // ISOgrk3 + {"*T", "Τ"}, // ISOgrk3 + {"*u", "υ"}, // ISOgrk3 + {"*U", "Υ"}, // ISOgrk3 + {"*w", "ω"}, // ISOgrk3 + {"*W", "Ω"}, // ISOgrk3 + {"*x", "χ"}, // ISOgrk3 + {"*X", "Χ"}, // ISOgrk3 + {"*y", "η"}, // ISOgrk3 + {"*Y", "Η"}, // ISOgrk3 + {"*z", "ζ"}, // ISOgrk3 + {"*Z", "Ζ"}, // ISOgrk3 + {"+-", "±"}, // ISOnum + {"->", "→"}, // ISOnum + {"12", "½"}, // ISOnum + {"14", "¼"}, // ISOnum + {"34", "¾"}, // ISOnum + {"<-", "←"}, // ISOnum + {"==", "≡"}, // ISOtech + {"Fi", "ffi"}, // ISOpub + {"Fl", "ffl"}, // ISOpub + {"aa", "´"}, // ISOdia + {"ap", "∼"}, // ISOtech + {"bl", "&phonexb;"}, // ISOpub + {"br", "│"}, // ISObox + {"bs", "☎"}, // ISOpub (for the Bell logo) + {"bu", "•"}, // ISOpub + {"bv", "|"}, // ISOnum + {"ca", "∩"}, // ISOtech + {"ci", "○"}, // ISOpub + {"co", "©"}, // ISOnum + {"ct", "¢"}, // ISOnum + {"cu", "∪"}, // ISOtech + {"da", "↓"}, // ISOnum + {"de", "°"}, // ISOnum + {"dg", "†"}, // ISOpub + {"dd", "‡"}, // ISOpub + {"di", "÷"}, // ISOnum + {"em", "—"}, // ISOpub + {"eq", "="}, // ISOnum + {"es", "∅"}, // ISOamso + {"ff", "ff"}, // ISOpub + {"fi", "fi"}, // ISOpub + {"fl", "fl"}, // ISOpub + {"fm", "′"}, // ISOtech + {"ge", "≥"}, // ISOtech + {"gr", "∇"}, // ISOtech + {"hy", "‐"}, // ISOnum + {"ib", "⊆"}, // ISOtech + {"if", "∞"}, // ISOtech + {"ip", "⊇"}, // ISOtech + {"is", "∫"}, // ISOtech + {"le", "≤"}, // ISOtech + // Some pile characters go here + {"mi", "−"}, // ISOtech + {"mo", "∈"}, // ISOtech + {"mu", "×"}, // ISOnum + {"no", "¬"}, // ISOnum + {"or", "|"}, // ISOnum + {"pl", "+"}, // ISOnum + {"pt", "∝"}, // ISOtech + {"rg", "™"}, // ISOnum + // More pile characters go here + {"rn", "¯"}, // ISOdia + {"ru", "_"}, // ISOnum + {"sb", "⊂"}, // ISOtech + {"sc", "§"}, // ISOnum + {"sl", "/"}, + {"sp", "⊃"}, // ISOtech + {"sq", "▪"}, // ISOpub + {"sr", "√"}, // ISOtech + {"ts", "ς"}, // ISOgrk3 + {"ua", "↑"}, // ISOnum + {"ul", "_"}, + {"~=", "≅"}, // ISOtech + // Extended specials supported by groff; see groff_char(7). + // These are listed in the order they occur on that man page. + {"-D", "Ð"}, // ISOlat: Icelandic uppercase eth + {"Sd", "ð"}, // ISOlat1: Icelandic lowercase eth + {"TP", "Þ"}, // ISOlat1: Icelandic uppercase thorn + {"Tp", "þ"}, // ISOlat1: Icelandic lowercase thorn + {"ss", "ß"}, // ISOlat1 + // Ligatures + // ff, fi, fl, ffi, ffl from old troff go here + {"AE", "Æ"}, // ISOlat1 + {"ae", "æ"}, // ISOlat1 + {"OE", "Œ"}, // ISOlat2 + {"oe", "œ"}, // ISOlat2 + {"IJ", "ij"}, // ISOlat2: Dutch IJ ligature + {"ij", "IJ"}, // ISOlat2: Dutch ij ligature + {".i", "ı"}, // ISOlat2,ISOamso + {".j", "&jnodot;"}, // ISOamso (undocumented but in 1.19) + // Accented characters + {"'A", "Á"}, // ISOlat1 + {"'C", "Ć"}, // ISOlat2 + {"'E", "É"}, // ISOlat1 + {"'I", "Í"}, // ISOlat1 + {"'O", "Ó"}, // ISOlat1 + {"'U", "Ú"}, // ISOlat1 + {"'Y", "Ý"}, // ISOlat1 + {"'a", "á"}, // ISOlat1 + {"'c", "ć"}, // ISOlat2 + {"'e", "é"}, // ISOlat1 + {"'i", "í"}, // ISOlat1 + {"'o", "ó"}, // ISOlat1 + {"'u", "ú"}, // ISOlat1 + {"'y", "ý"}, // ISOlat1 + {":A", "Ä"}, // ISOlat1 + {":E", "Ë"}, // ISOlat1 + {":I", "Ï"}, // ISOlat1 + {":O", "Ö"}, // ISOlat1 + {":U", "Ü"}, // ISOlat1 + {":Y", "Ÿ"}, // ISOlat2 + {":a", "ä"}, // ISOlat1 + {":e", "ë"}, // ISOlat1 + {":i", "ï"}, // ISOlat1 + {":o", "ö"}, // ISOlat1 + {":u", "ü"}, // ISOlat1 + {":y", "ÿ"}, // ISOlat1 + {"^A", "Â"}, // ISOlat1 + {"^E", "Ê"}, // ISOlat1 + {"^I", "Î"}, // ISOlat1 + {"^O", "Ô"}, // ISOlat1 + {"^U", "Û"}, // ISOlat1 + {"^a", "â"}, // ISOlat1 + {"^e", "ê"}, // ISOlat1 + {"^i", "î"}, // ISOlat1 + {"^o", "ô"}, // ISOlat1 + {"^u", "û"}, // ISOlat1 + {"`A", "À"}, // ISOlat1 + {"`E", "È"}, // ISOlat1 + {"`I", "Ì"}, // ISOlat1 + {"`O", "Ò"}, // ISOlat1 + {"`U", "Ù"}, // ISOlat1 + {"`a", "à"}, // ISOlat1 + {"`e", "è"}, // ISOlat1 + {"`i", "ì"}, // ISOlat1 + {"`o", "ò"}, // ISOlat1 + {"`u", "ù"}, // ISOlat1 + {"~A", "Ã"}, // ISOlat1 + {"~N", "Ñ"}, // ISOlat1 + {"~O", "Õ"}, // ISOlat1 + {"~a", "ã"}, // ISOlat1 + {"~n", "ñ"}, // ISOlat1 + {"~o", "õ"}, // ISOlat1 + {"vS", "Š"}, // ISOlat2 + {"vs", "š"}, // ISOlat2 + {"vZ", "Ž"}, // ISOlat2 + {"vz", "ž"}, // ISOlat2 + {",C", "Ç"}, // ISOlat1 + {",c", "ç"}, // ISOlat1 + {"/L", "Ł"}, // ISOlat2: Polish L with a slash + {"/l", "ł"}, // ISOlat2: Polish l with a slash + {"/O", "Ø"}, // ISOlat1 + {"/o", "ø"}, // ISOlat1 + {"oA", "Å"}, // ISOlat1 + {"oa", "å"}, // ISOlat1 + // Accents + {"a\"","˝"}, // ISOdia: double acute accent (Hungarian umlaut) + {"a-", "¯"}, // ISOdia: macron or bar accent + {"a.", "˙"}, // ISOdia: dot above + {"a^", "ˆ"}, // ISOdia: circumflex accent + {"aa", "´"}, // ISOdia: acute accent + {"ga", "`"}, // ISOdia: grave accent + {"ab", "˘"}, // ISOdia: breve accent + {"ac", "¸"}, // ISOdia: cedilla accent + {"ad", "¨"}, // ISOdia: umlaut or dieresis + {"ah", "ˇ"}, // ISOdia: caron (aka hacek accent) + {"ao", "˚"}, // ISOdia: ring or circle accent + {"a~", "˜"}, // ISOdia: tilde accent + {"ho", "˛"}, // ISOdia: hook or ogonek accent + {"ha", "^"}, // ASCII circumflex, hat, caret + {"ti", "~"}, // ASCII tilde, large tilde + // Quotes + {"Bq", "‚"}, // ISOpub: low double comma quote + {"bq", "„"}, // ISOpub: low single comma quote + {"lq", "“"}, // ISOnum + {"rq", "”"}, // ISOpub + {"oq", "‘"}, // ISOnum: single open quote + {"cq", "’"}, // ISOnum: single closing quote (ASCII 39) + {"aq", "&zerosp;'"}, // apostrophe quote + {"dq", "\""}, // double quote (ASCII 34) + {"Fo", "«"}, // ISOnum + {"Fc", "»"}, // ISOnum + //{"fo", "&fo;"}, + //{"fc", "&fc;"}, + // Punctuation + {"r!", "¡"}, // ISOnum + {"r?", "¿"}, // ISOnum + // Old troff \(em goes here + {"en", "–"}, // ISOpub: en dash + // Old troff \(hy goes here + // Brackets + {"lB", "["}, // ISOnum: left (square) bracket + {"rB", "]"}, // ISOnum: right (square) bracket + {"lC", "{"}, // ISOnum: left (curly) brace + {"rC", "}"}, // ISOnum: right (curly) brace + {"la", "⟨"}, // ISOtech: left angle bracket + {"ra", "⟩"}, // ISOtech: right angle bracket + // Old troff \(bv goes here + // Bracket-pile characters could go here. + // Arrows + // Old troff \(<- and \(-> go here + {"<>", "↔"}, // ISOamsa + {"da", "↓"}, // ISOnum + {"ua", "↑"}, // ISOnum + {"lA", "⇐"}, // ISOtech + {"rA", "⇒"}, // ISOtech + {"hA", "⇔"}, // ISOtech: horizontal double-headed arrow + {"dA", "⇓"}, // ISOamsa + {"uA", "⇑"}, // ISOamsa + {"vA", "⇕"}, // ISOamsa: vertical double-headed double arrow + //{"an", "&an;"}, + // Lines + {"-h", "ℏ"}, // ISOamso: h-bar (Planck's constant) + // Old troff \(or goes here + {"ba", "|"}, // ISOnum + // Old troff \(br, \{u, \(ul, \(bv go here + {"bb", "¦"}, // ISOnum + {"sl", "/"}, + {"rs", "\"}, // ISOnum + // Text markers + // Old troff \(ci, \(bu, \(dd, \(dg go here + {"lz", "◊"}, // ISOpub + // Old troff sq goes here + {"ps", "¶"}, // ISOnum: paragraph or pilcrow sign + {"sc", "§"}, // ISOnum (in old troff) + // Old troff \(lh, \{h go here + {"at", "@"}, // ISOnum + {"sh", "#"}, // ISOnum + //{"CR", "&CR;"}, + {"OK", "✓"}, // ISOpub + // Legalize + // Old troff \(co, \{g go here + {"tm", "™"}, // ISOnum + // Currency symbols + {"Do", "$"}, // ISOnum + {"ct", "¢"}, // ISOnum + {"eu", "€"}, + {"Eu", "€"}, + {"Ye", "¥"}, // ISOnum + {"Po", "£"}, // ISOnum + {"Cs", "¤"}, // ISOnum: currency sign + {"Fn", "&fnof"}, // ISOtech + // Units + // Old troff de goes here + {"%0", "‰"}, // ISOtech: per thousand, per mille sign + // Old troff \(fm goes here + {"sd", "″"}, // ISOtech + {"mc", "µ"}, // ISOnum + {"Of", "ª"}, // ISOnum + {"Om", "º"}, // ISOnum + // Logical symbols + {"AN", "∧"}, // ISOtech + {"OR", "∨"}, // ISOtech + // Old troff \(no goes here + {"te", "∃"}, // ISOtech: there exists, existential quantifier + {"fa", "∀"}, // ISOtech: for all, universal quantifier + {"st", "&bepsi"}, // ISOamsr: such that + {"3d", "∴"}, // ISOtech + {"tf", "∴"}, // ISOtech + // Mathematical symbols + // Old troff "12", "14", "34" goes here + {"S1", "¹"}, // ISOnum + {"S2", "²"}, // ISOnum + {"S3", "³"}, // ISOnum + // Old troff \(pl", \-, \(+- go here + {"t+-", "±"}, // ISOnum + {"-+", "∓"}, // ISOtech + {"pc", "·"}, // ISOnum + {"md", "·"}, // ISOnum + // Old troff \(mu goes here + {"tmu", "×"}, // ISOnum + {"c*", "⊗"}, // ISOamsb: multiply sign in a circle + {"c+", "⊕"}, // ISOamsb: plus sign in a circle + // Old troff \(di goes here + {"tdi", "÷"}, // ISOnum + {"f/", "―"}, // ISOnum: horizintal bar for fractions + // Old troff \(** goes here + {"<=", "≤"}, // ISOtech + {">=", "≥"}, // ISOtech + {"<<", "≪"}, // ISOamsr + {">>", "≫"}, // ISOamsr + {"!=", "≠"}, // ISOtech + // Old troff \(eq and \(== go here + {"=~", "≅"}, // ISOamsr + // Old troff \(ap goes here + {"~~", "≈"}, // ISOtech + // This appears to be an error in the groff table. + // It clashes with the Bell Labs use of ~= for a congruence sign + // {"~=", "≈"}, // ISOamsr + // Old troff \(pt, \(es, \(mo go here + {"nm", "∉"}, // ISOtech + {"nb", "⊄"}, // ISOamsr + {"nc", "⊅"}, // ISOamsn + {"ne", "≢"}, // ISOamsn + // Old troff \(sb, \(sp, \(ib, \(ip, \(ca, \(cu go here + {"/_", "∠"}, // ISOamso + {"pp", "⊥"}, // ISOtech + // Old troff \(is goes here + {"sum", "∑"}, // ISOamsb + {"product", "∏"}, // ISOamsb + {"gr", "∇"}, // ISOtech + // Old troff \(sr. \{n, \(if go here + {"Ah", "ℵ"}, // ISOtech + {"Im", "ℑ"}, // ISOamso: Fraktur I, imaginary + {"Re", "ℜ"}, // ISOamso: Fraktur R, real + {"wp", "℘"}, // ISOamso + {"pd", "∂"}, // 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", "♣"}, // ISOpub: club suit + {"SP", "♠"}, // ISOpub: spade suit + {"HE", "♥"}, // ISOpub: heart suit + {"DI", "♦"}, // 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("<"); + else if (c == '>') + printf(">"); + else if (c == '&') + printf("&"); + 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); +} + diff --git a/src/preproc/grn/README b/src/preproc/grn/README new file mode 100644 index 0000000..73273f7 --- /dev/null +++ b/src/preproc/grn/README @@ -0,0 +1,68 @@ +This is grn from the Berkeley ditroff distribution. It has no +AT&T code and is therefore freely distributable. + +Tim Theisen <tim@cs.wisc.edu> + +===================================================================== + +This is the modified code for the groff. It uses the different +devxxx format that is ascii rather than binary as in the +Berkeley distribution. Since groff does not have the \Ds option +for line drawing (dotted, dashed, etc.), this version includes +the routines for drawing curves and arcs, so it does not use the +\D~, \Da nor \Dc. Although also included in here is a routine +for drawing the optional gremlin style curves, it is not used +because the gremlin editor uses the conventional spline +algorithm. The Berkeley grn has the choice of different +stipples. Here, only different shades of gray will be painted +depending on the gremlin file. It is possible to upgrade this at +a later time. (Daniel Senderowicz <daniel@synchrods.com> 12/28/99) + +===================================================================== + +Gremlin produces three types of curves: B-Splines, interpolated +curves and Bezier. As the original Berkeley grn, now groff grn +will honor B-Splines and interpolated curves. Bezier curves will +be printed as B-Splines. (Daniel Senderowicz <daniel@synchrods.com> +10/04/02) + +===================================================================== + +It has been further modified by Werner Lemberg <wl@gnu.org> to fit +better into the groff package. + + . Replaced Makefile with Makefile.sub. + + . Removed dev.h since it is unused. + + . Renamed grn.1 to grn.man; this man page has been extensively + revised. + + . Used error() and fatal() from libgroff for all source files. + + . Renamed *.c to *.cpp; updates as needed for C++ (prototypes, proper + casts, standard header files etc). Heavy formatting. + + . main.cpp: + + Using groff's default values instead of DEVDIR, DEFAULTDEV, + PRINTER, TYPESETTER, and GREMLIB. + + 'res' is now an integer. + + Added '-C' command flag (for compatibility mode) as with other + preprocessors. + + Added '-F' and '-v' option (similar to troff). + + Renamed '-L' option to '-M' for consistence. + + Removed '-P' option. + + Using font::load_desc() for scanning DESC files. + + Removed SYSV-specific code. + + Using macro_path.open_file() for getting gremlin graphic files. + + Added usage(). diff --git a/src/preproc/grn/gprint.h b/src/preproc/grn/gprint.h new file mode 100644 index 0000000..772d79a --- /dev/null +++ b/src/preproc/grn/gprint.h @@ -0,0 +1,90 @@ +/* Last non-groff version: gprint.h 1.1 84/10/08 + * + * This file contains standard definitions used by the gprint program. + */ + +#include <stdio.h> +#include <math.h> + + +#define xorn(x,y) (x) + /* was 512 */ +#define yorn(x,y) (511 - (y)) /* switch direction for */ + /* y-coordinates */ + +#define STYLES 6 +#define SIZES 4 +#define FONTS 4 +#define SOLID -1 +#define DOTTED 004 /* 014 */ +#define DASHED 020 /* 034 */ +#define DOTDASHED 024 /* 054 */ +#define LONGDASHED 074 + +#define DEFTHICK -1 /* default thickness */ +#define DEFSTYLE SOLID /* default line style */ + +#define TRUE 1 +#define FALSE 0 + +#define nullelt -1 +#define nullpt -1 +#define nullun NULL + +#define BOTLEFT 0 +#define BOTRIGHT 1 +#define CENTCENT 2 +#define VECTOR 3 +#define ARC 4 +#define CURVE 5 +#define POLYGON 6 +#define BSPLINE 7 +#define BEZIER 8 +#define TOPLEFT 10 +#define TOPCENT 11 +#define TOPRIGHT 12 +#define CENTLEFT 13 +#define CENTRIGHT 14 +#define BOTCENT 15 +#define TEXT(t) ( (t <= CENTCENT) || (t >= TOPLEFT) ) + +/* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING + * The above (TEXT) test is dependent on the relative values of the + * constants and will have to change if these values change or if new + * commands are added with value greater than BOTCENT + */ + +#define NUSER 4 +#define NFONTS 4 +#define NBRUSHES 6 +#define NSIZES 4 +#define NJUSTS 9 +#define NSTIPPLES 16 + +#define ADD 1 +#define DELETE 2 +#define MOD 3 + +typedef struct point { + double x, y; + struct point *nextpt; +} POINT; + +typedef struct elmt { + int type, brushf, size, textlength; + char *textpt; + POINT *ptlist; + struct elmt *nextelt, *setnext; +} ELT; + +#define DBNextElt(elt) (elt->nextelt) +#define DBNextofSet(elt) (elt->setnext) +#define DBNullelt(elt) (elt == NULL) +#define Nullpoint(pt) ((pt) == (POINT *) NULL) +#define PTNextPoint(pt) (pt->nextpt) + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/grn/grn.1.man b/src/preproc/grn/grn.1.man new file mode 100644 index 0000000..cbc15ae --- /dev/null +++ b/src/preproc/grn/grn.1.man @@ -0,0 +1,978 @@ +'\" t +.TH @g@grn @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +@g@grn \- embed Gremlin images in +.I groff +documents +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 2000-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_grn_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@grn +.RB [ \-C ] +.RB [ \-T\~\c +.IR dev ] +.RB [ \-M\~\c +.IR dir ] +.RB [ \-F\~\c +.IR dir ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY @g@grn +.B \-? +. +.SY @g@grn +.B \-\-help +.YS +. +. +.SY @g@grn +.B \-v +. +.SY @g@grn +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +.I @g@grn +is a preprocessor for including +.I gremlin +pictures in +.MR @g@troff @MAN1EXT@ +input. +. +.I @g@grn +writes to standard output, +processing only input lines between two that start with +.B .GS +and +.BR .GE . +. +Those lines must contain +.I @g@grn +commands +(see below). +. +These macros request a +.I gremlin +file; +the picture in that file is converted and placed in the +.I @g@troff +input stream. +. +.B .GS +may be called with a +.BR C , +.BR L , +or +.B R +argument to center, +left-, +or right-justify the whole +.I gremlin +picture +(the default is to center). +. +If no +.I file +is mentioned, +the standard input is read. +. +At the end of the picture, +the position on the page is the bottom of the +.I gremlin +picture. +. +If the +.I @g@grn +entry is ended with +.B .GF +instead of +.BR .GE , +the position is left at the top of the picture. +. +. +.PP +Currently only the +.I me +macro package has support for +.BR .GS , +.BR .GE , +and +.BR .GF . +. +. +.PP +.I @g@grn +produces drawing escape sequences that use +.IR groff 's +color scheme extension +.RB ( \[rs]D\[aq]F \~.\|.\|.\& \[aq] ), +and thus may not work with other +.IR troff s. +. +. +.\" ==================================================================== +.SS "\f[I]grn\f[] commands" +.\" ==================================================================== +. +Each input line between +.B .GS +and +.B .GE +may have one +.I @g@grn +command. +. +Commands consist of one or two strings separated by white space, +the first string being the command and the second its operand. +. +Commands may be upper- or lowercase and abbreviated down to one +character. +. +. +.PP +Commands that affect a picture's environment +(those listed before +.RB \%\[lq] default \[rq], +see below) +are only in effect for the current picture: +the environment is reinitialized to the defaults at the start of the +next picture. +. +The commands are as follows. +. +. +.TP +.BI 1\~ N +.TQ +.BI 2\~ N +.TQ +.BI 3\~ N +.TQ +.BI 4\~ N +. +Set +.IR gremlin 's +text size number 1 +(2, +3, +or 4) +to +.I N +points. +. +The default is 12 +(16, +24, +and 36, +respectively). +. +. +.TP +.BI roman\~ f +.TQ +.BI italics\~ f +.TQ +.BI bold\~ f +.TQ +.BI special\~ f +Set the roman +(italics, +bold, +or special) +font to +.IR @g@troff 's +font +.I f +(either a name or number). +. +The default is R +(I, +B, +and S, +respectively). +. +. +.TP +.BI l\~ f +.TQ +.BI stipple\~ f +Set the stipple font to +.IR @g@troff 's +stipple font +.I f +(name or number). +. +The command +.B \%stipple +may be abbreviated down as far as +.RB \[lq] st \[rq] +(to avoid confusion with +.RB \%\[lq] special \[rq]). +. +There is +.I no +default for stipples +(unless one is set by the +.RB \%\[lq] default \[rq] +command), +and it is invalid to include a +.I gremlin +picture with polygons without specifying a stipple font. +. +. +.TP +.BI x\~ N +.TQ +.BI scale\~ N +Magnify the picture +(in addition to any default magnification) +by +.IR N , +a floating-point number larger than zero. +. +The command +.B scale +may be abbreviated down to +.RB \[lq] sc \[rq]. +. +. +.TP +.BI narrow\~ N +.TQ +.BI medium\~ N +.TQ +.BI thick\~ N +. +Set the thickness of +.IR gremlin 's +narrow +(medium and thick, +respectively) +lines to +.I N +times 0.15pt +(this value can be changed at compile time). +. +The default is 1.0 +(3.0 and 5.0, +respectively), +which corresponds to 0.15pt +(0.45pt and 0.75pt, +respectively). +. +A thickness value of zero selects the smallest available line thickness. +. +Negative values cause the line thickness to be proportional to the +current point size. +. +. +.TP +.BR pointscale\~ [ off | on ] +Scale text to match the picture. +. +Gremlin text is usually printed in the point size specified with the +commands +.BR 1 , +.BR 2 , +.BR 3 , +.RB or\~ 4 , +regardless of any scaling factors in the picture. +. +Setting +.B pointscale +will cause the point sizes to scale with the picture +(within +.IR @g@troff 's +limitations, +of course). +. +An operand of anything but +.B off +will turn text scaling on. +. +. +.TP +.B default +Reset the picture environment defaults to the settings in the current +picture. +. +This is meant to be used as a global parameter setting mechanism at +the beginning of the +.I @g@troff +input file, +but can be used at any time to reset the default settings. +. +. +.TP +.BI width\~ N +Force the picture to be +.I N +inches wide. +. +This overrides any scaling factors present in the same picture. +. +.RB \[lq] "width 0" \[rq] +is ignored. +. +. +.TP +.BI height\~ N +Force the picture to be +.I N +inches high, +overriding other scaling factors. +. +If both +.B width +and +.B height +are specified, +the tighter constraint will determine the scale of the picture. +. +.B height +and +.B width +commands are not saved with a +.RB \%\[lq] default \[rq] +command. +. +They will, +however, +affect point size scaling if that option is set. +. +. +.TP +.BI file\~ name +Get picture from +.I gremlin +file +.I name +located the current directory +(or in the library directory; +see the +.B \-M +option above). +. +If multiple +.B file +commands are given, +the last one controls. +. +If +.I name +doesn't exist, +an error message is reported and processing continues from the +.B .GE +line. +. +. +.\" ==================================================================== +.SS "Usage with \f[I]groff\f[]" +.\" ==================================================================== +. +Since +.I @g@grn +is a preprocessor, +it has no access to elements of formatter state, +such as +indentation, +line length, +type size, +or +register values. +. +Consequently, +no +.I @g@troff +input can be placed between the +.B .GS +and +.B .GE +macros. +. +However, +.I gremlin +text elements are subsequently processed by +.IR @g@troff , +so anything valid in a single line of +.I @g@troff +input is valid in a line of +.I gremlin +text +(barring the dot control character \[lq].\[rq] at the beginning of a +line). +. +Thus, +it is possible to have equations within a +.I gremlin +figure by including in the +.I gremlin +file +.I eqn \" language +expressions enclosed by previously defined delimiters +(e.g., +\[lq]$$\[rq]). +. +. +.PP +When using +.I @g@grn +along with other preprocessors, +it is best to run +.MR @g@tbl @MAN1EXT@ +before +.IR @g@grn , +.MR @g@pic @MAN1EXT@ , +and/or +.I ideal \" no GNU version yet +to avoid overworking +.IR @g@tbl . +. +.MR @g@eqn @MAN1EXT@ +should always be run last. +. +.MR groff @MAN1EXT@ +will automatically run preprocessors in the correct order. +. +. +.PP +A picture is considered an entity, +but that doesn't stop +.I @g@troff +from trying to break it up if it falls off the end of a page. +. +Placing the picture between \[lq]keeps\[rq] in the +.I me +macros will ensure proper placement. +. +. +.PP +.I @g@grn +uses +.IR @g@troff 's +registers +.B g1 +through +.B g9 +and sets registers +.B g1 +and +.B g2 +to the width and height of the +.I gremlin +figure +(in device units) +before entering the +.B .GS +macro +(this is for those who want to rewrite these macros). +. +. +.\" ==================================================================== +.SS "Gremlin file format" +.\" ==================================================================== +. +There exist two distinct +.I gremlin +file formats: +the original format for AED graphic terminals, +and the Sun or X11 version. +. +An extension used by the Sun/X11 version allowing reference points with +negative coordinates is +.I not +compatible with the AED version. +. +As long as a +.I gremlin +file does not contain negative coordinates, +either format will be read correctly by either version of +.I gremlin +or +.IR @g@grn . +. +The other difference in +Sun/X11 format is the use of names for picture objects +(e.g., +.BR POLYGON , +.BR CURVE ) +instead of numbers. +. +Files representing the same picture are shown below. +. +. +.PP +.ie t .ne 18v +.el .ne 19v +.TS +center, tab(@); +l lw(0.1i) l. +sungremlinfile@@gremlinfile +0 240.00 128.00@@0 240.00 128.00 +CENTCENT@@2 +240.00 128.00@@240.00 128.00 +185.00 120.00@@185.00 120.00 +240.00 120.00@@240.00 120.00 +296.00 120.00@@296.00 120.00 +*@@\-1.00 \-1.00 +2 3@@2 3 +10 A Triangle@@10 A Triangle +POLYGON@@6 +224.00 416.00@@224.00 416.00 +96.00 160.00@@96.00 160.00 +384.00 160.00@@384.00 160.00 +*@@\-1.00 \-1.00 +5 1@@5 1 +0@@0 +\-1@@\-1 +.TE +. +. +.IP \[bu] 2n +The first line of each +.I gremlin +file contains either the string +.RB \[lq] gremlinfile \[rq] +(AED) +or +.RB \[lq] sungremlinfile \[rq] +(Sun/X11). +. +. +.IP \[bu] +The second line of the file contains an orientation and +.I x +and +.I y +values for a positioning point, +separated by spaces. +. +The orientation, +either +.B 0 +or +.BR 1 , +is ignored by the Sun/X11 version. +. +.B 0 +means that +.I gremlin +will display things in horizontal format +(a drawing area wider than it is tall, +with a menu across the top). +. +.B 1 +means that +.I gremlin +will display things in vertical format +(a drawing area taller than it is wide, +with a menu on the left side). +. +.I x +and +.I y +are floating-point values giving a positioning point to be used when +this file is read into another file. +. +The stuff on this line really isn't all that important; +a value of +.RB \[lq] "1 0.00 0.00" \[rq] +is suggested. +. +. +.IP \[bu] +The rest of the file consists of zero or more element specifications. +. +After the last element specification is a line containing the string +.RB \[lq] \-1 \[rq]. +. +. +.IP \[bu] +Lines longer than 127 characters are truncated to that length. +. +. +.\" ==================================================================== +.SS "Element specifications" +.\" ==================================================================== +. +.IP \[bu] 2n +The first line of each element contains a single decimal number giving +the type of the element (AED) or its name (Sun/X11). +. +. +.IP +.ie t .ne 18v +.el .ne 19v +.TS +center, tab(@); +css +ccc +nBlBl. +\f[I]gremlin\f[] File Format: Object Type Specification +_ +AED Number@Sun/X11 Name@Description +0@BOTLEFT@bottom-left-justified text +1@BOTRIGHT@bottom-right-justified text +2@CENTCENT@center-justified text +3@VECTOR@vector +4@ARC@arc +5@CURVE@curve +6@POLYGON@polygon +7@BSPLINE@b-spline +8@BEZIER@B\['e]zier +10@TOPLEFT@top-left-justified text +11@TOPCENT@top-center-justified text +12@TOPRIGHT@top-right-justified text +13@CENTLEFT@left-center-justified text +14@CENTRIGHT@right-center-justified text +15@BOTCENT@bottom-center-justified text +.TE +. +. +.IP \[bu] +After the object type comes a variable number of lines, +each specifying a point used to display the element. +. +Each line contains an x-coordinate and a y-coordinate in floating-point +format, +separated by spaces. +. +The list of points is terminated by a line containing the string +.RB \[lq] "\-1.0 \-1.0" \[rq] +(AED) or a single asterisk, +.RB \[lq] * \[rq] +(Sun/X11). +. +. +.IP \[bu] +After the points comes a line containing two decimal values, +giving the brush and size for the element. +. +The brush determines the style in which things are drawn. +. +For vectors, +arcs, +and curves there are six valid brush values. +. +. +.IP +.TS +center, tab(@); +nB l. +1@thin dotted lines +2@thin dot-dashed lines +3@thick solid lines +4@thin dashed lines +5@thin solid lines +6@medium solid lines +.TE +. +. +.IP +For polygons, +one more value, +0, +is valid. +. +It specifies a polygon with an invisible border. +. +For text, +the brush selects a font as follows. +. +. +.IP +.TS +center, tab(@); +nB l. +1@roman (R font in \f[I]@g@troff\f[]) +2@italics (I font in \f[I]@g@troff\f[]) +3@bold (B font in \f[I]@g@troff\f[]) +4@special (S font in \f[I]@g@troff\f[]) +.TE +. +. +.IP +If you're using +.I @g@grn +to run your pictures through +.IR groff , +the font is really just a starting font. +. +The text string can contain formatting sequences like +\[lq]\[rs]fI\[rq] +or +\[lq]\[rs]d\[rq] +which may change the font +(as well as do many other things). +. +For text, +the size field is a decimal value between 1 and 4. +. +It selects the size of the font in which the text will be drawn. +. +For polygons, +this size field is interpreted as a stipple number to fill the polygon +with. +. +The number is used to index into a stipple font at print time. +. +. +.IP \[bu] +The last line of each element contains a decimal number and a string of +characters, +separated by a single space. +. +The number is a count of the number of characters in the string. +. +This information is used only for text elements, +and contains the text string. +. +There can be spaces inside the text. +. +For arcs, +curves, +and vectors, +the character count is zero +.RB ( 0 ), +followed by exactly one space before the newline. +. +. +.\" ==================================================================== +.SS Coordinates +.\" ==================================================================== +. +.I gremlin +was designed for AED terminals, +and its coordinates reflect the AED coordinate space. +. +For vertical pictures, +.IR x \~values +range 116 to 511, +and +.IR y \~values +from 0 to 483. +. +For horizontal pictures, +.IR x \~values +range from 0 to 511, +and +.IR y \~values +from 0 to 367. +. +Although you needn't absolutely stick to this range, +you'll get better results if you at least stay in this vicinity. +. +Also, +point lists are terminated by a point of +(\-1, +\-1), +so you shouldn't ever use negative coordinates. +. +.I gremlin +writes out coordinates using the +.MR printf 3 +format \[lq]%f1.2\[rq]; +it's probably a good idea to use the same format if you want to modify +the +.I @g@grn +code. +. +. +.\" ==================================================================== +.SS "Sun/X11 coordinates" +.\" ==================================================================== +. +There is no restriction on the range of coordinates used to create +objects in the Sun/X11 version of +.IR gremlin . +. +However, +files with negative coordinates +.I will +cause problems if displayed on the AED. +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-?\& +and +.B \-\-help +display a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-C +Recognize +.B .GS +and +.B .GE +(and +.BR .GF ) +even when followed by a character other than space or newline. +. +. +.TP +.BI \-F\~ dir +Search +.I dir +for subdirectories +.IR dev name +.RI ( name +is the name of the output driver) +for the +.I DESC +file before the default font directories +.IR @LOCALFONTDIR@ , +.IR @FONTDIR@ , +and +.IR @LEGACYFONTDIR@ . +. +. +.TP +.BI \-M\~ dir +Prepend +.I dir +to the search path for +.I gremlin +files. +. +The default search path is the current directory, +the home directory, +.if !'@COMPATIBILITY_WRAPPERS@'no' .IR @SYSTEMMACRODIR@ , +.IR @LOCALMACRODIR@ , +and +.IR @MACRODIR@ , +in that order. +.\". +.\". +.\".TP +.\".B \-s +.\"This switch causes the picture to be traversed twice: +.\"The first time, +.\"only the interiors of filled polygons +.\"(as borderless polygons) +.\"are printed. +.\". +.\"The second time, +.\"the outline is printed as a series of line segments. +.\". +.\"This way, +.\"postprocessors that overwrite rather than merge picture elements +.\"(such as PostScript) +.\"can still have text and graphics on a shaded background. +. +. +.TP +.BI \-T\~ dev +Prepare device output using output driver +.IR dev . +. +The default is +.BR @DEVICE@ . +. +See +.MR groff @MAN1EXT@ +for a list of valid devices. +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.IR @FONTDIR@/\:\%dev name /\:DESC +describes the output device +.IR name . +. +. +.\" ==================================================================== +.SH Authors +.\" ==================================================================== +. +David Slattengren and Barry Roitblat wrote the original Berkeley +.IR grn . +. +Daniel Senderowicz and Werner Lemberg modified it for +.IR groff . +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.MR gremlin 1 , +.MR groff @MAN1EXT@ , +.MR @g@pic @MAN1EXT@ , +.MR ideal 1 +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grn_1_man_C] +.do rr *groff_grn_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/preproc/grn/grn.am b/src/preproc/grn/grn.am new file mode 100644 index 0000000..f45fa24 --- /dev/null +++ b/src/preproc/grn/grn.am @@ -0,0 +1,35 @@ +# 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 += grn +grn_SOURCES = \ + src/preproc/grn/hdb.cpp \ + src/preproc/grn/hpoint.cpp \ + src/preproc/grn/hgraph.cpp \ + src/preproc/grn/main.cpp \ + src/preproc/grn/gprint.h +src/preproc/grn/main.$(OBJEXT): defs.h +grn_LDADD = libgroff.a lib/libgnu.a $(LIBM) +PREFIXMAN1 += src/preproc/grn/grn.1 +EXTRA_DIST += src/preproc/grn/README src/preproc/grn/grn.1.man + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/grn/hdb.cpp b/src/preproc/grn/hdb.cpp new file mode 100644 index 0000000..9ba3eaa --- /dev/null +++ b/src/preproc/grn/hdb.cpp @@ -0,0 +1,441 @@ + /* Last non-groff version: hdb.c 1.8 (Berkeley) 84/10/20 + * + * Copyright -C- 1982 Barry S. Roitblat + * + * This file contains database routines for the hard copy programs of + * the gremlin picture editor. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include "gprint.h" +#include <string.h> +#include <ctype.h> + +#include "errarg.h" +#include "error.h" + +#define MAXSTRING 128 +#define MAXSTRING_S "127" + +/* imports from main.cpp */ + +extern char gremlinfile[]; /* name of file currently reading */ +extern int SUNFILE; /* TRUE if SUN gremlin file */ +extern int compatibility_flag; /* TRUE if in compatibility mode */ +extern void *grnmalloc(size_t size, const char *what); +extern void savebounds(double x, double y); + +/* imports from hpoint.cpp */ + +extern POINT *PTInit(); +extern POINT *PTMakePoint(double x, double y, POINT ** pplist); + + +int DBGetType(char *s); + +static long lineno = 0; /* line number of gremlin file */ + +/* + * This routine returns a pointer to an initialized database element + * which would be the only element in an empty list. + */ +ELT * +DBInit() +{ + return ((ELT *) NULL); +} /* end DBInit */ + + +/* + * This routine creates a new element with the specified attributes and + * links it into database. + */ +ELT * +DBCreateElt(int type, + POINT * pointlist, + int brush, + int size, + char *text, + ELT **db) +{ + ELT *temp = 0; + + temp = (ELT *) grnmalloc(sizeof(ELT), "picture element"); + temp->nextelt = *db; + temp->type = type; + temp->ptlist = pointlist; + temp->brushf = brush; + temp->size = size; + temp->textpt = text; + *db = temp; + return (temp); +} /* end CreateElt */ + + +/* + * This routine reads the specified file into a database and returns a + * pointer to that database. + */ +ELT * +DBRead(FILE *file) +{ + int i; + int done; /* flag for input exhausted */ + double nx; /* x holder so x is not set before orienting */ + int type; /* element type */ + ELT *elist; /* pointer to the file's elements */ + POINT *plist; /* pointer for reading in points */ + char string[MAXSTRING], *txt; + double x, y; /* x and y are read in point coords */ + int len, brush, size; + int lastpoint; + + SUNFILE = FALSE; + elist = DBInit(); + int nitems = fscanf(file, "%" MAXSTRING_S "s%*[^\n]\n", string); + if (nitems != 1) { + error_with_file_and_line(gremlinfile, lineno, + "malformed input; giving up on this" + " picture"); + return (elist); + } + lineno++; + if (strcmp(string, "gremlinfile")) { + if (strcmp(string, "sungremlinfile")) { + error_with_file_and_line(gremlinfile, lineno, + "not a gremlin file; giving up on this" + " picture"); + return (elist); + } + SUNFILE = TRUE; + } + + nitems = fscanf(file, "%d%lf%lf\n", &size, &x, &y); + if (nitems != 3) { + error_with_file_and_line(gremlinfile, lineno, + "malformed input; giving up on this" + " picture"); + return (elist); + } + lineno++; + /* ignore orientation and file positioning point */ + + done = FALSE; + while (!done) { + /* if (fscanf(file,"%" MAXSTRING_S "s\n", string) == EOF) */ + /* I changed the scanf format because the element */ + /* can have two words (e.g. CURVE SPLINE) */ + if (fscanf(file, "\n%" + MAXSTRING_S + "[^\n]%*[^\n]\n", string) == EOF) { + lineno++; + error_with_file_and_line(gremlinfile, lineno, "error in format;" + " giving up on this picture"); + return (elist); + } + lineno++; + + type = DBGetType(string); /* interpret element type */ + if (type < 0) { /* no more data */ + done = TRUE; + } else { + /* always one point */ +#ifdef UW_FASTSCAN + (void) xscanf(file, &x, &y); +#else + nitems = fscanf(file, "%lf%lf\n", &x, &y); + if (nitems != 2) { + error_with_file_and_line(gremlinfile, lineno, + "malformed input; giving up on this" + " picture"); + return (elist); + } + lineno++; +#endif /* UW_FASTSCAN */ + plist = PTInit(); /* NULL point list */ + + /* + * Files created on the SUN have point lists terminated by a line + * containing only an asterisk ('*'). Files created on the AED + * have point lists terminated by the coordinate pair (-1.00 + * -1.00). + */ + if (TEXT(type)) { /* read only first point for TEXT elements */ + nx = xorn(x, y); + y = yorn(x, y); + (void) PTMakePoint(nx, y, &plist); + savebounds(nx, y); + +#ifdef UW_FASTSCAN + while (xscanf(file, &x, &y)); +#else + lastpoint = FALSE; + do { + char *cp = fgets(string, MAXSTRING, file); + if (0 /* nullptr */ == cp) { + error_with_file_and_line(gremlinfile, lineno, + "premature end-of-file or error" + " reading input; giving up on this" + " picture"); + return(elist); + } + lineno++; + if (string[0] == '*') { /* SUN gremlin file */ + lastpoint = TRUE; + } else { + if (!sscanf(string, "%lf%lf", &x, &y)) { + error_with_file_and_line(gremlinfile, lineno, + "expected coordinate pair, got" + " '%1'; giving up on this" + " picture", string); + return(elist); + } + if ((x == -1.00 && y == -1.00) && (!SUNFILE)) + lastpoint = TRUE; + else { + if (compatibility_flag) + savebounds(xorn(x, y), yorn(x, y)); + } + } + } while (!lastpoint); +#endif /* UW_FASTSCAN */ + } else { /* not TEXT element */ +#ifdef UW_FASTSCAN + do { + nx = xorn(x, y); + y = yorn(x, y); + (void) PTMakePoint(nx, y, &plist); + savebounds(nx, y); + } while (xscanf(file, &x, &y)); +#else + lastpoint = FALSE; + while (!lastpoint) { + nx = xorn(x, y); + y = yorn(x, y); + (void) PTMakePoint(nx, y, &plist); + savebounds(nx, y); + + char *cp = fgets(string, MAXSTRING, file); + if (0 /* nullptr */ == cp) { + error_with_file_and_line(gremlinfile, lineno, + "premature end-of-file or error" + " reading input; giving up on this" + " picture"); + return(elist); + } + lineno++; + if (string[0] == '*') { /* SUN gremlin file */ + lastpoint = TRUE; + } else { + (void) sscanf(string, "%lf%lf", &x, &y); + if ((x == -1.00 && y == -1.00) && (!SUNFILE)) + lastpoint = TRUE; + } + } +#endif /* UW_FASTSCAN */ + } + nitems = fscanf(file, "%d%d\n", &brush, &size); + if (nitems != 2) { + error_with_file_and_line(gremlinfile, lineno, + "malformed input; giving up on this" + " picture"); + return (elist); + } + lineno++; + nitems = fscanf(file, "%d", &len); /* text length */ + if (nitems != 1) { + error_with_file_and_line(gremlinfile, lineno, + "malformed input; giving up on this" + " picture"); + return (elist); + } + (void) getc(file); /* eat blank */ + lineno++; /* advance line counter early */ + if (len < 0) { + error_with_file_and_line(gremlinfile, lineno, + "length claimed for text is nonsense:" + " '%1'; giving up on this picture", + len); + return (elist); + } + txt = (char *) grnmalloc((unsigned) len + 1, "element text"); + for (i = 0; i < len; ++i) { /* read text */ + int c = getc(file); + if (c == EOF) + break; + txt[i] = c; + } + if (feof(file)) { + error_with_file_and_line(gremlinfile, lineno, + "end of file while reading text of" + " length %1; giving up on this" + " picture", len); + return (elist); + } + txt[len] = '\0'; + (void) DBCreateElt(type, plist, brush, size, txt, &elist); + } /* end else */ + } /* end while not done */ ; + return (elist); +} /* end DBRead */ + + +/* + * Interpret element type in string s. + * Old file format consisted of integer element types. + * New file format has literal names for element types. + */ +int +DBGetType(char *s) +{ + if (isdigit(s[0]) || (s[0] == '-')) /* old element format or EOF */ + return (atoi(s)); + + switch (s[0]) { + case 'P': + return (POLYGON); + case 'V': + return (VECTOR); + case 'A': + return (ARC); + case 'C': + if (s[1] == 'U') { + if (s[5] == '\n') + return (CURVE); + switch (s[7]) { + case 'S': + return(BSPLINE); + case 'E': + warning_with_file_and_line(gremlinfile, lineno, + "using B-spline for Bezier curve"); + return(BSPLINE); + default: + return(CURVE); + } + } + switch (s[4]) { + case 'L': + return (CENTLEFT); + case 'C': + return (CENTCENT); + case 'R': + return (CENTRIGHT); + default: + error_with_file_and_line(gremlinfile, lineno, + "unknown element type '%1'", s); + return -1; + } + case 'B': + switch (s[3]) { + case 'L': + return (BOTLEFT); + case 'C': + return (BOTCENT); + case 'R': + return (BOTRIGHT); + default: + error_with_file_and_line(gremlinfile, lineno, + "unknown element type '%1'", s); + return -1; + } + case 'T': + switch (s[3]) { + case 'L': + return (TOPLEFT); + case 'C': + return (TOPCENT); + case 'R': + return (TOPRIGHT); + default: + error_with_file_and_line(gremlinfile, lineno, + "unknown element type '%1'", s); + return -1; + } + default: + error_with_file_and_line(gremlinfile, lineno, + "unknown element type '%1'", s); + return -1; + } +} + +#ifdef UW_FASTSCAN +/* + * Optimization hack added by solomon@crys.wisc.edu, 12/2/86. + * A huge fraction of the time was spent reading floating point numbers + * from the input file, but the numbers always have the format 'ddd.dd'. + * Thus the following special-purpose version of fscanf. + * + * xscanf(f,xp,yp) does roughly what fscanf(f,"%f%f",xp,yp) does except: + * -the next piece of input must be of the form + * <space>* <digit>*'.'<digit>* <space>* <digit>*'.'<digit>* + * -xscanf eats the character following the second number + * -xscanf returns 0 for "end-of-data" indication, 1 otherwise, where + * end-of-data is signalled by a '*' [in which case the rest of the + * line is gobbled], or by '-1.00 -1.00' [but only if !SUNFILE]. + */ +int +xscanf(FILE *f, + double *xp, + double *yp) +{ + int c, i, j, m, frac; + int iscale = 1, jscale = 1; /* x = i/scale, y=j/jscale */ + + while ((c = getc(f)) == ' '); + if (c == '*') { + while ((c = getc(f)) != '\n'); + return 0; + } + i = m = frac = 0; + while (isdigit(c) || c == '.' || c == '-') { + if (c == '-') { + m++; + c = getc(f); + continue; + } + if (c == '.') + frac = 1; + else { + if (frac) + iscale *= 10; + i = 10 * i + c - '0'; + } + c = getc(f); + } + if (m) + i = -i; + *xp = (double) i / (double) iscale; + + while ((c = getc(f)) == ' '); + j = m = frac = 0; + while (isdigit(c) || c == '.' || c == '-') { + if (c == '-') { + m++; + c = getc(f); + continue; + } + if (c == '.') + frac = 1; + else { + if (frac) + jscale *= 10; + j = 10 * j + c - '0'; + } + c = getc(f); + } + if (m) + j = -j; + *yp = (double) j / (double) jscale; + return (SUNFILE || i != -iscale || j != -jscale); +} +#endif /* UW_FASTSCAN */ + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/grn/hgraph.cpp b/src/preproc/grn/hgraph.cpp new file mode 100644 index 0000000..9ed81a4 --- /dev/null +++ b/src/preproc/grn/hgraph.cpp @@ -0,0 +1,1060 @@ +/* Last non-groff version: hgraph.c 1.14 (Berkeley) 84/11/27 + * + * This file contains the graphics routines for converting gremlin + * pictures to troff input. + */ + +#include "lib.h" + +#include "gprint.h" + +#define MAXVECT 40 +#define MAXPOINTS 200 +#define LINELENGTH 1 +#define PointsPerInterval 64 +#define pi 3.14159265358979324 +#define twopi (2.0 * pi) +#define len(a, b) groff_hypot((double)(b.x-a.x), \ + (double)(b.y-a.y)) + + +extern int dotshifter; /* for the length of dotted curves */ + +extern int style[]; /* line and character styles */ +extern double thick[]; +extern char *tfont[]; +extern int tsize[]; +extern int stipple_index[]; /* stipple font idx for stipples 0-16 */ +extern char *stipple; /* stipple type (cf or ug) */ + + +extern double troffscale; /* imports from main.c */ +extern double linethickness; +extern int linmod; +extern int lastx; +extern int lasty; +extern int lastyline; +extern int ytop; +extern int ybottom; +extern int xleft; +extern int xright; +extern enum E { + OUTLINE, FILL, BOTH +} polyfill; + +extern double adj1; +extern double adj2; +extern double adj3; +extern double adj4; +extern int res; + +void HGSetFont(int font, int size); +void HGPutText(int justify, POINT pnt, char *string); +void HGSetBrush(int mode); +void tmove2(int px, int py); +void doarc(POINT cp, POINT sp, int angle); +void tmove(POINT * ptr); +void cr(); +void drawwig(POINT * ptr, int type); +void HGtline(int x1, int y1); +void deltax(double x); +void deltay(double y); +void HGArc(int cx, int cy, int px, int py, int angle); +void picurve(int *x, int *y, int npts); +void HGCurve(int *x, int *y, int numpoints); +void Parameterize(int x[], int y[], double h[], int n); +void PeriodicSpline(double h[], int z[], + double dz[], double d2z[], double d3z[], + int npoints); +void NaturalEndSpline(double h[], int z[], + double dz[], double d2z[], double d3z[], + int npoints); + + + +/*--------------------------------------------------------------------* + | Routine: HGPrintElt (element_pointer, baseline) + | + | Results: Examines a picture element and calls the appropriate + | routine(s) to print them according to their type. After + | the picture is drawn, current position is (lastx,lasty). + *--------------------------------------------------------------------*/ + +void +HGPrintElt(ELT *element, + int /* baseline */) +{ + POINT *p1; + POINT *p2; + int length; + int graylevel; + + if (!DBNullelt(element) && !Nullpoint((p1 = element->ptlist))) { + /* p1 always has first point */ + if (TEXT(element->type)) { + HGSetFont(element->brushf, element->size); + switch (element->size) { + case 1: + p1->y += adj1; + break; + case 2: + p1->y += adj2; + break; + case 3: + p1->y += adj3; + break; + case 4: + p1->y += adj4; + break; + default: + break; + } + HGPutText(element->type, *p1, element->textpt); + } else { + if (element->brushf) /* if there is a brush, the */ + HGSetBrush(element->brushf); /* graphics need it set */ + + switch (element->type) { + + case ARC: + p2 = PTNextPoint(p1); + tmove(p2); + doarc(*p1, *p2, element->size); + cr(); + break; + + case CURVE: + length = 0; /* keep track of line length */ + drawwig(p1, CURVE); + cr(); + break; + + case BSPLINE: + length = 0; /* keep track of line length */ + drawwig(p1, BSPLINE); + cr(); + break; + + case VECTOR: + length = 0; /* keep track of line length so */ + tmove(p1); /* single lines don't get long */ + while (!Nullpoint((p1 = PTNextPoint(p1)))) { + HGtline((int) (p1->x * troffscale), + (int) (p1->y * troffscale)); + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } /* end while */ + cr(); + break; + + case POLYGON: + { + /* brushf = style of outline; size = color of fill: + * on first pass (polyfill=FILL), do the interior using 'P' + * unless size=0 + * on second pass (polyfill=OUTLINE), do the outline using a + * series of vectors. It might make more sense to use \D'p + * ...', but there is no uniform way to specify a 'fill + * character' that prints as 'no fill' on all output + * devices (and stipple fonts). + * If polyfill=BOTH, just use the \D'p ...' command. + */ + double firstx = p1->x; + double firsty = p1->y; + + length = 0; /* keep track of line length so */ + /* single lines don't get long */ + + if (polyfill == FILL || polyfill == BOTH) { + /* do the interior */ + char command = (polyfill == BOTH && element->brushf) + ? 'p' : 'P'; + + /* include outline, if there is one and */ + /* the -p flag was set */ + + /* switch based on what gremlin gives */ + switch (element->size) { + case 1: + graylevel = 1; + break; + case 3: + graylevel = 2; + break; + case 12: + graylevel = 3; + break; + case 14: + graylevel = 4; + break; + case 16: + graylevel = 5; + break; + case 19: + graylevel = 6; + break; + case 21: + graylevel = 7; + break; + case 23: + graylevel = 8; + break; + default: /* who's giving something else? */ + graylevel = NSTIPPLES; + break; + } + /* int graylevel = element->size; */ + + if (graylevel < 0) + break; + if (graylevel > NSTIPPLES) + graylevel = NSTIPPLES; + printf("\\D'Fg %.3f'", + double(1000 - stipple_index[graylevel]) / 1000.0); + cr(); + tmove(p1); + printf("\\D'%c", command); + + while (!Nullpoint((PTNextPoint(p1)))) { + p1 = PTNextPoint(p1); + deltax((double) p1->x); + deltay((double) p1->y); + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } /* end while */ + + /* close polygon if not done so by user */ + if ((firstx != p1->x) || (firsty != p1->y)) { + deltax((double) firstx); + deltay((double) firsty); + } + putchar('\''); + cr(); + break; + } + /* else polyfill == OUTLINE; only draw the outline */ + if (!(element->brushf)) + break; + length = 0; /* keep track of line length */ + tmove(p1); + + while (!Nullpoint((PTNextPoint(p1)))) { + p1 = PTNextPoint(p1); + HGtline((int) (p1->x * troffscale), + (int) (p1->y * troffscale)); + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } /* end while */ + + /* close polygon if not done so by user */ + if ((firstx != p1->x) || (firsty != p1->y)) { + HGtline((int) (firstx * troffscale), + (int) (firsty * troffscale)); + } + cr(); + break; + } /* end case POLYGON */ + } /* end switch */ + } /* end else Text */ + } /* end if */ +} /* end PrintElt */ + + +/*---------------------------------------------------------------------* + | Routine: HGPutText (justification, position_point, string) + | + | Results: Given the justification, a point to position with, and a + | string to put, HGPutText first sends the string into a + | diversion, moves to the positioning point, then outputs + | local vertical and horizontal motions as needed to + | justify the text. After all motions are done, the + | diversion is printed out. + *--------------------------------------------------------------------*/ + +void +HGPutText(int justify, + POINT pnt, + char *string) +{ + int savelasty = lasty; /* vertical motion for text is to be */ + /* ignored. Save current y here */ + + printf(".nr g8 \\n(.d\n"); /* save current vertical position. */ + printf(".ds g9 \""); /* define string containing the text. */ + while (*string) { /* put out the string */ + if (*string == '\\' && + *(string + 1) == '\\') { /* one character at a */ + printf("\\\\\\"); /* time replacing // */ + string++; /* by //// to prevent */ + } /* interpretation at */ + printf("%c", *(string++)); /* printout time */ + } + printf("\n"); + + tmove(&pnt); /* move to positioning point */ + + switch (justify) { + /* local vertical motions--the numbers here are used to be + somewhat compatible with gprint */ + case CENTLEFT: + case CENTCENT: + case CENTRIGHT: + printf("\\v'0.85n'"); /* down half */ + break; + + case TOPLEFT: + case TOPCENT: + case TOPRIGHT: + printf("\\v'1.7n'"); /* down whole */ + } + + switch (justify) { + /* local horizontal motions */ + case BOTCENT: + case CENTCENT: + case TOPCENT: + printf("\\h'-\\w'\\*(g9'u/2u'"); /* back half */ + break; + + case BOTRIGHT: + case CENTRIGHT: + case TOPRIGHT: + printf("\\h'-\\w'\\*(g9'u'"); /* back whole */ + } + + printf("\\&\\*(g9\n"); /* now print the text. */ + printf(".sp |\\n(g8u\n"); /* restore vertical position */ + lasty = savelasty; /* vertical position restored to */ + lastx = xleft; /* where it was before text, also */ + /* horizontal is at left */ +} /* end HGPutText */ + + +/*--------------------------------------------------------------------* + | Routine: doarc (center_point, start_point, angle) + | + | Results: Produces either drawarc command or a drawcircle command + | depending on the angle needed to draw through. + *--------------------------------------------------------------------*/ + +void +doarc(POINT cp, + POINT sp, + int angle) +{ + if (angle) /* arc with angle */ + HGArc((int) (cp.x * troffscale), (int) (cp.y * troffscale), + (int) (sp.x * troffscale), (int) (sp.y * troffscale), angle); + else /* a full circle (angle == 0) */ + HGArc((int) (cp.x * troffscale), (int) (cp.y * troffscale), + (int) (sp.x * troffscale), (int) (sp.y * troffscale), 0); +} + + +/*--------------------------------------------------------------------* + | Routine: HGSetFont (font_number, Point_size) + | + | Results: ALWAYS outputs a .ft and .ps directive to troff. This + | is done because someone may change stuff inside a text + | string. Changes thickness back to default thickness. + | Default thickness depends on font and point size. + *--------------------------------------------------------------------*/ + +void +HGSetFont(int font, + int size) +{ + printf(".ft %s\n" + ".ps %d\n", tfont[font - 1], tsize[size - 1]); + linethickness = DEFTHICK; +} + + +/*--------------------------------------------------------------------* + | Routine: HGSetBrush (line_mode) + | + | Results: Generates the troff commands to set up the line width + | and style of subsequent lines. Does nothing if no + | change is needed. + | + | Side Efct: Sets 'linmode' and 'linethickness'. + *--------------------------------------------------------------------*/ + +void +HGSetBrush(int mode) +{ + int printed = 0; + + if (linmod != style[--mode]) { + /* Groff doesn't understand \Ds, so we take it out */ + /* printf ("\\D's %du'", linmod = style[mode]); */ + linmod = style[mode]; + printed = 1; + } + if (linethickness != thick[mode]) { + linethickness = thick[mode]; + printf("\\h'-%.2fp'\\D't %.2fp'", linethickness, linethickness); + printed = 1; + } + if (printed) + cr(); +} + + +/*--------------------------------------------------------------------* + | Routine: deltax (x_destination) + | + | Results: Scales and outputs a number for delta x (with a leading + | space) given 'lastx' and x_destination. + | + | Side Efct: Resets 'lastx' to x_destination. + *--------------------------------------------------------------------*/ + +void +deltax(double x) +{ + int ix = (int) (x * troffscale); + + printf(" %du", ix - lastx); + lastx = ix; +} + + +/*--------------------------------------------------------------------* + | Routine: deltay (y_destination) + | + | Results: Scales and outputs a number for delta y (with a leading + | space) given 'lastyline' and y_destination. + | + | Side Efct: Resets 'lastyline' to y_destination. Since 'line' + | vertical motions don't affect 'page' ones, 'lasty' isn't + | updated. + *--------------------------------------------------------------------*/ + +void +deltay(double y) +{ + int iy = (int) (y * troffscale); + + printf(" %du", iy - lastyline); + lastyline = iy; +} + + +/*--------------------------------------------------------------------* + | Routine: tmove2 (px, py) + | + | Results: Produces horizontal and vertical moves for troff given + | the pair of points to move to and knowing the current + | position. Also puts out a horizontal move to start the + | line. This is a variation without the .sp command. + *--------------------------------------------------------------------*/ + +void +tmove2(int px, + int py) +{ + int dx; + int dy; + + if ((dy = py - lasty)) { + printf("\\v'%du'", dy); + } + lastyline = lasty = py; /* lasty is always set to current */ + if ((dx = px - lastx)) { + printf("\\h'%du'", dx); + lastx = px; + } +} + + +/*--------------------------------------------------------------------* + | Routine: tmove (point_pointer) + | + | Results: Produces horizontal and vertical moves for troff given + | the pointer of a point to move to and knowing the + | current position. Also puts out a horizontal move to + | start the line. + *--------------------------------------------------------------------*/ + +void +tmove(POINT * ptr) +{ + int ix = (int) (ptr->x * troffscale); + int iy = (int) (ptr->y * troffscale); + int dx; + int dy; + + if ((dy = iy - lasty)) { + printf(".sp %du\n", dy); + } + lastyline = lasty = iy; /* lasty is always set to current */ + if ((dx = ix - lastx)) { + printf("\\h'%du'", dx); + lastx = ix; + } +} + + +/*--------------------------------------------------------------------* + | Routine: cr ( ) + | + | Results: Ends off an input line. '.sp -1' is also added to + | counteract the vertical move done at the end of text + | lines. + | + | Side Efct: Sets 'lastx' to 'xleft' for troff's return to left + | margin. + *--------------------------------------------------------------------*/ + +void +cr() +{ + printf("\n.sp -1\n"); + lastx = xleft; +} + + +/*--------------------------------------------------------------------* + | Routine: line ( ) + | + | Results: Draws a single solid line to (x,y). + *--------------------------------------------------------------------*/ + +void +line(int px, + int py) +{ + printf("\\D'l"); + printf(" %du", px - lastx); + printf(" %du'", py - lastyline); + lastx = px; + lastyline = lasty = py; +} + + +/*--------------------------------------------------------------------* + | Routine: drawwig (ptr, type) + | + | Results: The point sequence found in the structure pointed by ptr + | is placed in integer arrays for further manipulation by + | the existing routing. With the corresponding type + | parameter, either picurve or HGCurve are called. + *--------------------------------------------------------------------*/ + +void +drawwig(POINT * ptr, + int type) +{ + int npts; /* point list index */ + int x[MAXPOINTS], y[MAXPOINTS]; /* point list */ + + for (npts = 1; !Nullpoint(ptr); ptr = PTNextPoint(ptr), npts++) { + x[npts] = (int) (ptr->x * troffscale); + y[npts] = (int) (ptr->y * troffscale); + } + if (--npts) { + if (type == CURVE) /* Use the 2 different types of curves */ + HGCurve(&x[0], &y[0], npts); + else + picurve(&x[0], &y[0], npts); + } +} + + +/*--------------------------------------------------------------------* + | Routine: HGArc (xcenter, ycenter, xstart, ystart, angle) + | + | Results: This routine plots an arc centered about (cx, cy) + | counter-clockwise starting from the point (px, py) + | through 'angle' degrees. If angle is 0, a full circle + | is drawn. It does so by creating a draw-path around the + | arc whose density of points depends on the size of the + | arc. + *--------------------------------------------------------------------*/ + +void +HGArc(int cx, + int cy, + int px, + int py, + int angle) +{ + double xs, ys, resolution, fullcircle; + int m; + int mask; + int extent; + int nx; + int ny; + int length; + double epsilon; + + xs = px - cx; + ys = py - cy; + + length = 0; + + resolution = (1.0 + groff_hypot(xs, ys) / res) * PointsPerInterval; + /* mask = (1 << (int) log10(resolution + 1.0)) - 1; */ + (void) frexp(resolution, &m); /* more elegant than log10 */ + for (mask = 1; mask < m; mask = mask << 1); + mask -= 1; + + epsilon = 1.0 / resolution; + fullcircle = (2.0 * pi) * resolution; + if (angle == 0) + extent = (int) fullcircle; + else + extent = (int) (angle * fullcircle / 360.0); + + HGtline(px, py); + while (--extent >= 0) { + xs += epsilon * ys; + nx = cx + (int) (xs + 0.5); + ys -= epsilon * xs; + ny = cy + (int) (ys + 0.5); + if (!(extent & mask)) { + HGtline(nx, ny); /* put out a point on circle */ + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } + } /* end for */ +} /* end HGArc */ + + +/*--------------------------------------------------------------------* + | Routine: picurve (xpoints, ypoints, num_of_points) + | + | Results: Draws a curve delimited by (not through) the line + | segments traced by (xpoints, ypoints) point list. This + | is the 'Pic'-style curve. + *--------------------------------------------------------------------*/ + +void +picurve(int *x, + int *y, + int npts) +{ + int nseg; /* effective resolution for each curve */ + int xp; /* current point (and temporary) */ + int yp; + int pxp, pyp; /* previous point (to make lines from) */ + int i; /* inner curve segment traverser */ + int length = 0; + double w; /* position factor */ + double t1, t2, t3; /* calculation temps */ + + if (x[1] == x[npts] && y[1] == y[npts]) { + x[0] = x[npts - 1]; /* if the lines' ends meet, make */ + y[0] = y[npts - 1]; /* sure the curve meets */ + x[npts + 1] = x[2]; + y[npts + 1] = y[2]; + } else { /* otherwise, make the ends of the */ + x[0] = x[1]; /* curve touch the ending points of */ + y[0] = y[1]; /* the line segments */ + x[npts + 1] = x[npts]; + y[npts + 1] = y[npts]; + } + + pxp = (x[0] + x[1]) / 2; /* make the last point pointers */ + pyp = (y[0] + y[1]) / 2; /* point to the start of the 1st line */ + tmove2(pxp, pyp); + + for (; npts--; x++, y++) { /* traverse the line segments */ + xp = x[0] - x[1]; + yp = y[0] - y[1]; + nseg = (int) groff_hypot((double) xp, (double) yp); + xp = x[1] - x[2]; + yp = y[1] - y[2]; + /* 'nseg' is the number of line */ + /* segments that will be drawn for */ + /* each curve segment. */ + nseg = (int) ((double) (nseg + (int) groff_hypot((double) xp, + (double) yp)) / + res * PointsPerInterval); + + for (i = 1; i < nseg; i++) { + w = (double) i / (double) nseg; + t1 = w * w; + t3 = t1 + 1.0 - (w + w); + t2 = 2.0 - (t3 + t1); + xp = (((int) (t1 * x[2] + t2 * x[1] + t3 * x[0])) + 1) / 2; + yp = (((int) (t1 * y[2] + t2 * y[1] + t3 * y[0])) + 1) / 2; + + HGtline(xp, yp); + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } + } +} + + +/*--------------------------------------------------------------------* + | Routine: HGCurve(xpoints, ypoints, num_points) + | + | Results: This routine generates a smooth curve through a set of + | points. The method used is the parametric spline curve + | on unit knot mesh described in 'Spline Curve Techniques' + | by Patrick Baudelaire, Robert Flegal, and Robert Sproull + | -- Xerox Parc. + *--------------------------------------------------------------------*/ + +void +HGCurve(int *x, + int *y, + int numpoints) +{ + double h[MAXPOINTS], dx[MAXPOINTS], dy[MAXPOINTS]; + double d2x[MAXPOINTS], d2y[MAXPOINTS], d3x[MAXPOINTS], d3y[MAXPOINTS]; + double t, t2, t3; + int j; + int k; + int nx; + int ny; + int lx, ly; + int length = 0; + + lx = x[1]; + ly = y[1]; + tmove2(lx, ly); + + /* + * Solve for derivatives of the curve at each point separately for x + * and y (parametric). + */ + Parameterize(x, y, h, numpoints); + + /* closed curve */ + if ((x[1] == x[numpoints]) && (y[1] == y[numpoints])) { + PeriodicSpline(h, x, dx, d2x, d3x, numpoints); + PeriodicSpline(h, y, dy, d2y, d3y, numpoints); + } else { + NaturalEndSpline(h, x, dx, d2x, d3x, numpoints); + NaturalEndSpline(h, y, dy, d2y, d3y, numpoints); + } + + /* + * Generate the curve using the above information and + * PointsPerInterval vectors between each specified knot. + */ + + for (j = 1; j < numpoints; ++j) { + if ((x[j] == x[j + 1]) && (y[j] == y[j + 1])) + continue; + for (k = 0; k <= PointsPerInterval; ++k) { + t = (double) k *h[j] / (double) PointsPerInterval; + t2 = t * t; + t3 = t * t * t; + nx = x[j] + (int) (t * dx[j] + t2 * d2x[j] / 2 + t3 * d3x[j] / 6); + ny = y[j] + (int) (t * dy[j] + t2 * d2y[j] / 2 + t3 * d3y[j] / 6); + HGtline(nx, ny); + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } /* end for k */ + } /* end for j */ +} /* end HGCurve */ + + +/*--------------------------------------------------------------------* + | Routine: Parameterize (xpoints, ypoints, hparams, num_points) + | + | Results: This routine calculates parametric values for use in + | calculating curves. The parametric values are returned + | in the array h. The values are an approximation of + | cumulative arc lengths of the curve (uses cord length). + | For additional information, see paper cited below. + *--------------------------------------------------------------------*/ + +void +Parameterize(int x[], + int y[], + double h[], + int n) +{ + int dx; + int dy; + int i; + int j; + double u[MAXPOINTS]; + + for (i = 1; i <= n; ++i) { + u[i] = 0; + for (j = 1; j < i; j++) { + dx = x[j + 1] - x[j]; + dy = y[j + 1] - y[j]; + /* Here was overflowing, so I changed it. */ + /* u[i] += sqrt ((double) (dx * dx + dy * dy)); */ + u[i] += groff_hypot((double) dx, (double) dy); + } + } + for (i = 1; i < n; ++i) + h[i] = u[i + 1] - u[i]; +} /* end Parameterize */ + + +/*--------------------------------------------------------------------* + | Routine: PeriodicSpline (h, z, dz, d2z, d3z, npoints) + | + | Results: This routine solves for the cubic polynomial to fit a + | spline curve to the points specified by the list of + | values. The curve generated is periodic. The + | algorithms for this curve are from the 'Spline Curve + | Techniques' paper cited above. + *--------------------------------------------------------------------*/ + +void +PeriodicSpline(double h[], /* parameterization */ + int z[], /* point list */ + double dz[], /* to return the 1st derivative */ + double d2z[], /* 2nd derivative */ + double d3z[], /* 3rd derivative */ + int npoints) /* number of valid points */ +{ + double d[MAXPOINTS]; + double deltaz[MAXPOINTS], a[MAXPOINTS], b[MAXPOINTS]; + double c[MAXPOINTS], r[MAXPOINTS], s[MAXPOINTS]; + int i; + + /* step 1 */ + for (i = 1; i < npoints; ++i) { + deltaz[i] = h[i] ? ((double) (z[i + 1] - z[i])) / h[i] : 0; + } + h[0] = h[npoints - 1]; + deltaz[0] = deltaz[npoints - 1]; + + /* step 2 */ + for (i = 1; i < npoints - 1; ++i) { + d[i] = deltaz[i + 1] - deltaz[i]; + } + d[0] = deltaz[1] - deltaz[0]; + + /* step 3a */ + a[1] = 2 * (h[0] + h[1]); + b[1] = d[0]; + c[1] = h[0]; + for (i = 2; i < npoints - 1; ++i) { + a[i] = 2 * (h[i - 1] + h[i]) - + pow((double) h[i - 1], (double) 2.0) / a[i - 1]; + b[i] = d[i - 1] - h[i - 1] * b[i - 1] / a[i - 1]; + c[i] = -h[i - 1] * c[i - 1] / a[i - 1]; + } + + /* step 3b */ + r[npoints - 1] = 1; + s[npoints - 1] = 0; + for (i = npoints - 2; i > 0; --i) { + r[i] = -(h[i] * r[i + 1] + c[i]) / a[i]; + s[i] = (6 * b[i] - h[i] * s[i + 1]) / a[i]; + } + + /* step 4 */ + d2z[npoints - 1] = (6 * d[npoints - 2] - h[0] * s[1] + - h[npoints - 1] * s[npoints - 2]) + / (h[0] * r[1] + h[npoints - 1] * r[npoints - 2] + + 2 * (h[npoints - 2] + h[0])); + for (i = 1; i < npoints - 1; ++i) { + d2z[i] = r[i] * d2z[npoints - 1] + s[i]; + } + d2z[npoints] = d2z[1]; + + /* step 5 */ + for (i = 1; i < npoints; ++i) { + dz[i] = deltaz[i] - h[i] * (2 * d2z[i] + d2z[i + 1]) / 6; + d3z[i] = h[i] ? (d2z[i + 1] - d2z[i]) / h[i] : 0; + } +} /* end PeriodicSpline */ + + +/*-------------------------------------------------------------------- + | Routine: NaturalEndSpline (h, z, dz, d2z, d3z, npoints) + | + | Results: This routine solves for the cubic polynomial to fit a + | spline curve the points specified by the list of values. + | The algorithms for this curve are from the 'Spline Curve + | Techniques' paper cited above. + *--------------------------------------------------------------------*/ + +void +NaturalEndSpline(double h[], /* parameterization */ + int z[], /* Point list */ + double dz[], /* to return the 1st derivative */ + double d2z[], /* 2nd derivative */ + double d3z[], /* 3rd derivative */ + int npoints) /* number of valid points */ +{ + double d[MAXPOINTS]; + double deltaz[MAXPOINTS], a[MAXPOINTS], b[MAXPOINTS]; + int i; + + /* step 1 */ + for (i = 1; i < npoints; ++i) { + deltaz[i] = h[i] ? ((double) (z[i + 1] - z[i])) / h[i] : 0; + } + deltaz[0] = deltaz[npoints - 1]; + + /* step 2 */ + for (i = 1; i < npoints - 1; ++i) { + d[i] = deltaz[i + 1] - deltaz[i]; + } + d[0] = deltaz[1] - deltaz[0]; + + /* step 3 */ + a[0] = 2 * (h[2] + h[1]); + b[0] = d[1]; + for (i = 1; i < npoints - 2; ++i) { + a[i] = 2 * (h[i + 1] + h[i + 2]) - + pow((double) h[i + 1], (double) 2.0) / a[i - 1]; + b[i] = d[i + 1] - h[i + 1] * b[i - 1] / a[i - 1]; + } + + /* step 4 */ + d2z[npoints] = d2z[1] = 0; + for (i = npoints - 1; i > 1; --i) { + d2z[i] = (6 * b[i - 2] - h[i] * d2z[i + 1]) / a[i - 2]; + } + + /* step 5 */ + for (i = 1; i < npoints; ++i) { + dz[i] = deltaz[i] - h[i] * (2 * d2z[i] + d2z[i + 1]) / 6; + d3z[i] = h[i] ? (d2z[i + 1] - d2z[i]) / h[i] : 0; + } +} /* end NaturalEndSpline */ + + +/*--------------------------------------------------------------------* + | Routine: change (x_position, y_position, visible_flag) + | + | Results: As HGtline passes from the invisible to visible (or vice + | versa) portion of a line, change is called to either + | draw the line, or initialize the beginning of the next + | one. Change calls line to draw segments if visible_flag + | is set (which means we're leaving a visible area). + *--------------------------------------------------------------------*/ + +void +change(int x, + int y, + int vis) +{ + static int length = 0; + + if (vis) { /* leaving a visible area, draw it. */ + line(x, y); + if (length++ > LINELENGTH) { + length = 0; + printf("\\\n"); + } + } else { /* otherwise entering one; remember */ + /* beginning */ + tmove2(x, y); + } +} + + +/*--------------------------------------------------------------------* + | Routine: HGtline (xstart, ystart, xend, yend) + | + | Results: Draws a line from current position to (x1,y1) using + | line(x1, y1) to place individual segments of dotted or + | dashed lines. + *--------------------------------------------------------------------*/ + +void +HGtline(int x_1, + int y_1) +{ + int x_0 = lastx; + int y_0 = lasty; + int dx; + int dy; + int oldcoord; + int res1; + int visible; + int res2; + int xinc; + int yinc; + int dotcounter; + + if (linmod == SOLID) { + line(x_1, y_1); + return; + } + + /* for handling different resolutions */ + dotcounter = linmod << dotshifter; + + xinc = 1; + yinc = 1; + if ((dx = x_1 - x_0) < 0) { + xinc = -xinc; + dx = -dx; + } + if ((dy = y_1 - y_0) < 0) { + yinc = -yinc; + dy = -dy; + } + res1 = 0; + res2 = 0; + visible = 0; + if (dx >= dy) { + oldcoord = y_0; + while (x_0 != x_1) { + if ((x_0 & dotcounter) && !visible) { + change(x_0, y_0, 0); + visible = 1; + } else if (visible && !(x_0 & dotcounter)) { + change(x_0 - xinc, oldcoord, 1); + visible = 0; + } + if (res1 > res2) { + oldcoord = y_0; + res2 += dx - res1; + res1 = 0; + y_0 += yinc; + } + res1 += dy; + x_0 += xinc; + } + } else { + oldcoord = x_0; + while (y_0 != y_1) { + if ((y_0 & dotcounter) && !visible) { + change(x_0, y_0, 0); + visible = 1; + } else if (visible && !(y_0 & dotcounter)) { + change(oldcoord, y_0 - yinc, 1); + visible = 0; + } + if (res1 > res2) { + oldcoord = x_0; + res2 += dy - res1; + res1 = 0; + x_0 += xinc; + } + res1 += dx; + y_0 += yinc; + } + } + if (visible) + change(x_1, y_1, 1); + else + change(x_1, y_1, 0); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/grn/hpoint.cpp b/src/preproc/grn/hpoint.cpp new file mode 100644 index 0000000..5ef0c0a --- /dev/null +++ b/src/preproc/grn/hpoint.cpp @@ -0,0 +1,59 @@ +/* Last non-groff version: hpoint.c 1.1 84/10/08 */ + +/* + * This file contains routines for manipulating the point data + * structures for the gremlin picture editor. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include "gprint.h" + +/* imports from main.cpp */ +extern void *grnmalloc(size_t size, const char *what); + +/* + * Return pointer to empty point list. + */ +POINT * +PTInit() +{ + return ((POINT *) NULL); +} + + +/* + * This routine creates a new point with coordinates x and y and links + * it into the point list. + */ +POINT * +PTMakePoint(double x, + double y, + POINT **pplist) +{ + POINT *pt; + + if (Nullpoint(pt = *pplist)) { /* empty list */ + *pplist = (POINT *) grnmalloc(sizeof(POINT), "initial point"); + pt = *pplist; + } else { + while (!Nullpoint(pt->nextpt)) + pt = pt->nextpt; + pt->nextpt = (POINT *) grnmalloc(sizeof(POINT), "subsequent point"); + pt = pt->nextpt; + } + + pt->x = x; + pt->y = y; + pt->nextpt = PTInit(); + return (pt); +} /* end PTMakePoint */ + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/grn/main.cpp b/src/preproc/grn/main.cpp new file mode 100644 index 0000000..6d6d586 --- /dev/null +++ b/src/preproc/grn/main.cpp @@ -0,0 +1,977 @@ +/* Last non-groff version: main.c 1.23 (Berkeley) 85/08/05 + * + * Adapted to GNU troff by Daniel Senderowicz 99/12/29. + * + * Further refinements by Werner Lemberg 00/02/20. + * + * + * This file contains the main and file system dependent routines for + * processing gremlin files into troff input. The program watches input + * go by to standard output, only interpreting things between .GS and + * .GE lines. Default values (font, size, scale, thickness) may be + * overridden with a 'default' command and are further overridden by + * commands in the input. + * + * Inside the GS and GE, commands are accepted to reconfigure the + * picture. At most one command may reside on each line, and each + * command is followed by a parameter separated by white space. The + * commands are as follows, and may be abbreviated down to one character + * (with exception of 'scale' and 'stipple' down to "sc" and "st") and + * may be upper or lower case. + * + * default - Make all settings in the current + * .GS/.GE the global defaults. + * Height, width and file are NOT + * saved. + * 1, 2, 3, 4 - Set size 1, 2, 3, or 4 (followed + * by an integer point size). + * roman, italics, bold, special - Set gremlin's fonts to any other + * troff font (1 or 2 characters). + * stipple, l - Use a stipple font for polygons. + * Arg is troff font name. No + * default. Can use only one stipple + * font per picture. (See below for + * stipple font index.) + * scale, x - Scale is IN ADDITION to the global + * scale factor from the default. + * pointscale - Turn on scaling point sizes to + * match 'scale' commands. (Optional + * operand 'off' to turn it off.) + * narrow, medium, thick - Set widths of lines. + * file - Set the file name to read the + * gremlin picture from. If the file + * isn't in the current directory, + * the gremlin library is tried. + * width, height - These two commands override any + * scaling factor that is in effect, + * and forces the picture to fit into + * either the height or width + * specified, whichever makes the + * picture smaller. The operand for + * these two commands is a + * floating-point number in units of + * inches. + * l<nn> (integer <nn>) - Set association between stipple + * <nn> and a stipple 'character'. + * <nn> must be in the range 0 to + * NSTIPPLES (16) inclusive. The + * integer operand is an index in the + * stipple font selected. Valid cf + * (cifplot) indices are 1-32 + * (although 24 is not defined), + * valid ug (unigrafix) indices are + * 1-14, and valid gs (gray scale) + * indices are 0-16. Nonetheless, + * any number between 0 and 255 is + * accepted since new stipple fonts + * may be added. An integer operand + * is required. + * + * Troff number registers used: g1 through g9. g1 is the width of the + * picture, and g2 is the height. g3, and g4, save information, g8 and + * g9 are used for text processing and g5-g7 are reserved. + */ + + +#include "lib.h" + +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> // errno +#include "gprint.h" + +#include "device.h" +#include "font.h" +#include "searchpath.h" +#include "macropath.h" + +#include "errarg.h" +#include "error.h" +#include "defs.h" + +extern "C" const char *Version_string; + +/* database imports */ + +extern void HGPrintElt(ELT *element, int baseline); +extern ELT *DBInit(); +extern ELT *DBRead(FILE *file); +extern POINT *PTInit(); +extern POINT *PTMakePoint(double x, double y, POINT **pplist); + +#define INIT_FILE_SIZE 50 /* Initial sz of file array from cmd line. */ +#define FILE_SIZE_INCR 50 /* Amount to increase array of files by. */ + +#define SUN_SCALEFACTOR 0.70 + +/* #define DEFSTIPPLE "gs" */ +#define DEFSTIPPLE "cf" +/* + * This grn implementation emits '.st' requests to control stipple + * effects, but groff does not (currently) support any such request. + * + * This hack disables the emission of such requests, without destroying + * the infrastructure necessary to support the feature in the future; to + * enable the emission of '.st' requests, at a future date when groff + * can support them, simply rewrite the following #define as: + * + * #define USE_ST_REQUEST stipple + * + * with accompanying comment: "emit '.st' requests as required". + */ +#define USE_ST_REQUEST 0 /* never emit '.st' requests */ + +#define MAXINLINE 100 /* input line length */ + +#define SCREENtoINCH 0.02 /* scaling factor, screen to inches */ + +#define BIG 999999999999.0 /* unwieldy large floating number */ + + +/* static char sccsid[] = "@(#) (Berkeley) 8/5/85, 12/28/99"; */ + +int res; /* the printer's resolution goes here */ + +int dotshifter; /* for the length of dotted curves */ + +double linethickness; /* brush styles */ +int linmod; +int lastx; /* point registers for printing */ +int lasty; /* elements */ +int lastyline; /* A line's vertical position is NOT */ + /* the same after that line is over, */ + /* so for a line of drawing commands, */ + /* vertical spacing is kept in */ + /* lastyline. */ + +/* These are the default fonts, sizes, line styles, */ +/* and thicknesses. They can be modified from a */ +/* 'default' command and are reset each time the */ +/* start of a picture (.GS) is found. */ + +const char *deffont[] = +{"R", "I", "B", "S"}; +int defsize[] = +{10, 16, 24, 36}; +/* #define BASE_THICKNESS 1.0 */ +#define BASE_THICKNESS 0.15 +double defthick[STYLES] = +{1 * BASE_THICKNESS, + 1 * BASE_THICKNESS, + 5 * BASE_THICKNESS, + 1 * BASE_THICKNESS, + 1 * BASE_THICKNESS, + 3 * BASE_THICKNESS}; + +/* int cf_stipple_index[NSTIPPLES + 1] = */ +/* {0, 1, 3, 12, 14, 16, 19, 21, 23}; */ +/* a logarithmic scale looks better than a linear one for gray shades */ +/* */ +/* int other_stipple_index[NSTIPPLES + 1] = */ +/* {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; */ + +int cf_stipple_index[NSTIPPLES + 1] = +{0, 18, 32, 56, 100, 178, 316, 562, 1000}; /* only 1-8 used */ +int other_stipple_index[NSTIPPLES + 1] = +{0, 62, 125, 187, 250, 312, 375, 437, 500, + 562, 625, 687, 750, 812, 875, 937, 1000}; + +/* int *defstipple_index = other_stipple_index; */ +int *defstipple_index = cf_stipple_index; + +int style[STYLES] = +{DOTTED, DOTDASHED, SOLID, DASHED, SOLID, SOLID}; +double scale = 1.0; /* no scaling, default */ +int defpoint = 0; /* flag for point size scaling */ +char *defstipple = (char *) 0; +enum E { + OUTLINE, FILL, BOTH +} polyfill; + +/* flag to control filling of polygons */ + +double adj1 = 0.0; +double adj2 = 0.0; +double adj3 = 0.0; +double adj4 = 0.0; + +double thick[STYLES]; /* thicknesses set by defaults, then */ + /* by commands */ +char *tfont[FONTS]; /* fonts originally set to deffont */ + /* values, then optionally changed by */ +int tsize[SIZES]; /* commands inside grn */ +int stipple_index[NSTIPPLES + 1]; /* stipple font file indices */ +char *stipple; + +double xscale; /* scaling factor from individual pictures */ +double troffscale; /* scaling factor at output time */ + +double width; /* user-request maximum width for picture */ + /* (in inches) */ +double height; /* user-request height */ +int pointscale; /* flag for point size scaling */ +int setdefault; /* flag for a .GS/.GE to remember all */ + /* settings */ +int sflag; /* -s flag: sort order (do polyfill first) */ + +double toppoint; /* remember the picture */ +double bottompoint; /* bounds in these variables */ +double leftpoint; +double rightpoint; + +int ytop; /* these are integer versions of the above */ +int ybottom; /* so not to convert each time they're used */ +int xleft; +int xright; + +static int linenum = 0; /* line number of troff input file */ +char inputline[MAXINLINE]; /* spot to filter through the file */ +char *c1 = inputline; /* c1, c2, and c3 will be used to */ +char *c2 = inputline + 1; /* hunt for lines that begin with */ +char *c3 = inputline + 2; /* '.GS' by looking individually */ +char *c4 = inputline + 3; /* needed for compatibility mode */ +char GScommand[MAXINLINE]; /* put user's '.GS' command line here */ +char gremlinfile[MAXINLINE]; /* filename to use for a picture */ +int SUNFILE = FALSE; /* TRUE if SUN gremlin file */ +int compatibility_flag = FALSE; /* TRUE if in compatibility mode */ + + +void getres(); +int doinput(FILE *fp); +void conv(FILE *fp, int baseline); +void savestate(); +int has_polygon(ELT *elist); +void interpret(char *line); + +void * +grnmalloc(size_t size, + const char *what) +{ + void *ptr = 0; + ptr = malloc(size); + if (!ptr) { + fatal("memory allocation failed for %1: %2", what, strerror(errno)); + } + return ptr; +} + +void +usage(FILE *stream) +{ + fprintf(stream, + "usage: %s [-Cs] [-M dir] [-F dir] [-T dev] [file ...]\n" + "usage: %s {-v | --version}\n" + "usage: %s --help\n", + program_name, program_name, program_name); +} + + +/* Add a new file entry in the array, expanding array if needs be. */ + +char ** +add_file(char **file, + char *new_file, + int *count, + int *cur_size) +{ + if (*count >= *cur_size) { + *cur_size += FILE_SIZE_INCR; + file = (char **) realloc(file, *cur_size * sizeof(char *)); + if (file == NULL) { + fatal("unable to extend file array"); + } + } + file[*count] = new_file; + *count += 1; + + return file; +} + + +/*--------------------------------------------------------------------* + | Routine: main (argument_count, argument_pointer) + | + | Results: Parses the command line, accumulating input file names, + | then reads the inputs, passing it directly to output + | until a '.GS' line is read. Main then passes control to + | 'conv' to do the gremlin file conversions. + *--------------------------------------------------------------------*/ + +int +main(int argc, + char **argv) +{ + setlocale(LC_NUMERIC, "C"); + program_name = argv[0]; + FILE *fp; + int k; + char c; + int gfil = 0; + char **file = NULL; + int file_cur_size = INIT_FILE_SIZE; + char *operand(int *argcp, char ***argvp); + + file = (char **) grnmalloc(file_cur_size * sizeof(char *), + "file array"); + while (--argc) { + if (**++argv != '-') + file = add_file(file, *argv, &gfil, &file_cur_size); + else + switch (c = (*argv)[1]) { + + case 0: + file = add_file(file, NULL, &gfil, &file_cur_size); + break; + + case 'C': /* compatibility mode */ + compatibility_flag = TRUE; + break; + + case 'F': /* font path to find DESC */ + font::command_line_font_dir(operand(&argc, &argv)); + break; + + case 'T': /* final output typesetter name */ + device = operand(&argc, &argv); + break; + + case 'M': /* set library directory */ + macro_path.command_line_dir(operand(&argc, &argv)); + break; + + case 's': /* preserve order of elements */ + sflag = 1; + break; + + case '-': + if (strcmp(*argv,"--version")==0) { + case 'v': + printf("GNU grn (groff) version %s\n", Version_string); + exit(0); + break; + } + if (strcmp(*argv,"--help")==0) { + case '?': + usage(stdout); + exit(0); + break; + } + // fallthrough + default: + error("unknown switch: %1", c); + usage(stderr); + exit(1); + } + } + + getres(); /* set the resolution for an output device */ + + if (gfil == 0) { /* no filename, use standard input */ + file[0] = NULL; + gfil++; + } + + for (k = 0; k < gfil; k++) { + if (file[k] != NULL) { + if ((fp = fopen(file[k], "r")) == NULL) + fatal("can't open %1", file[k]); + } else + fp = stdin; + + while (doinput(fp)) { + if (*c1 == '.' && *c2 == 'G' && *c3 == 'S') { + if (compatibility_flag || + *c4 == '\n' || *c4 == ' ' || *c4 == '\0') + conv(fp, linenum); + else + fputs(inputline, stdout); + } else + fputs(inputline, stdout); + } + } + + return 0; +} + + +/*--------------------------------------------------------------------* + | Routine: char * operand (& argc, & argv) + | + | Results: Returns address of the operand given with a command-line + | option. It uses either '-Xoperand' or '-X operand', + | whichever is present. The program is terminated if no + | option is present. + | + | Side Efct: argc and argv are updated as necessary. + *--------------------------------------------------------------------*/ + +char * +operand(int *argcp, + char ***argvp) +{ + if ((**argvp)[2]) + return (**argvp + 2); /* operand immediately follows */ + if ((--*argcp) <= 0) { /* no operand */ + error("command-line option operand missing."); + exit(8); + } + return (*(++(*argvp))); /* operand is next word */ +} + + +/*--------------------------------------------------------------------* + | Routine: getres () + | + | Results: Sets 'res' to the resolution of the output device. + *--------------------------------------------------------------------*/ + +void +getres() +{ + int linepiece; + + if (0 /* nullptr */ == font::load_desc()) + fatal("cannot load 'DESC' description file for device '%1'", + device); + + res = font::res; + + /* Correct the brush thicknesses based on res */ + /* if (res >= 256) { + defthick[0] = res >> 8; + defthick[1] = res >> 8; + defthick[2] = res >> 4; + defthick[3] = res >> 8; + defthick[4] = res >> 8; + defthick[5] = res >> 6; + } */ + + linepiece = res >> 9; + for (dotshifter = 0; linepiece; dotshifter++) + linepiece = linepiece >> 1; +} + + +/*--------------------------------------------------------------------* + | Routine: int doinput (file_pointer) + | + | Results: A line of input is read into 'inputline'. + | + | Side Efct: "linenum" is incremented. + | + | Bugs: Lines longer than MAXINLINE are NOT checked, except for + | updating 'linenum'. + *--------------------------------------------------------------------*/ + +int +doinput(FILE *fp) +{ + if (fgets(inputline, MAXINLINE, fp) == NULL) + return 0; + if (strchr(inputline, '\n')) /* ++ only if it's a complete line */ + linenum++; + return 1; +} + + +/*--------------------------------------------------------------------* + | Routine: initpic ( ) + | + | Results: Sets all parameters to the normal defaults, possibly + | overridden by a setdefault command. Initialize the + | picture variables, and output the startup commands to + | troff to begin the picture. + *--------------------------------------------------------------------*/ + +void +initpic() +{ + int i; + + for (i = 0; i < STYLES; i++) { /* line thickness defaults */ + thick[i] = defthick[i]; + } + for (i = 0; i < FONTS; i++) { /* font name defaults */ + tfont[i] = (char *)deffont[i]; + } + for (i = 0; i < SIZES; i++) { /* font size defaults */ + tsize[i] = defsize[i]; + } + for (i = 0; i <= NSTIPPLES; i++) { /* stipple font file default */ + /* indices */ + stipple_index[i] = defstipple_index[i]; + } + stipple = defstipple; + + gremlinfile[0] = 0; /* filename is 'null' */ + setdefault = 0; /* not the default settings (yet) */ + + toppoint = BIG; /* set the picture bounds out */ + bottompoint = -BIG; /* of range so they'll be set */ + leftpoint = BIG; /* by 'savebounds' on input */ + rightpoint = -BIG; + + pointscale = defpoint;/* flag for scaling point sizes default */ + xscale = scale; /* default scale of individual pictures */ + width = 0.0; /* size specifications input by user */ + height = 0.0; + + linethickness = DEFTHICK; /* brush styles */ + linmod = DEFSTYLE; +} + + +/*--------------------------------------------------------------------* + | Routine: conv (file_pointer, starting_line) + | + | Results: At this point, we just passed a '.GS' line in the input + | file. conv reads the input and calls 'interpret' to + | process commands, gathering up information until a '.GE' + | line is found. It then calls 'HGPrint' to do the + | translation of the gremlin file to troff commands. + *--------------------------------------------------------------------*/ + +void +conv(FILE *fp, + int baseline) +{ + FILE *gfp = NULL; /* input file pointer */ + int done = 0; /* flag to remember if finished */ + ELT *e; /* current element pointer */ + ELT *PICTURE; /* whole picture data base pointer */ + double temp; /* temporary calculating area */ + /* POINT ptr; */ /* coordinates of a point to pass to 'mov' */ + /* routine */ + int flyback; /* flag 'want to end up at the top of the */ + /* picture?' */ + int compat; /* test character after .GE or .GF */ + + + initpic(); /* set defaults, ranges, etc. */ + strcpy(GScommand, inputline); /* save '.GS' line for later */ + + do { + done = !doinput(fp); /* test for EOF */ + flyback = (*c3 == 'F'); /* and .GE or .GF */ + compat = (compatibility_flag || + *c4 == '\n' || *c4 == ' ' || *c4 == '\0'); + done |= (*c1 == '.' && *c2 == 'G' && (*c3 == 'E' || flyback) && + compat); + + if (done) { + if (setdefault) + savestate(); + + if (!gremlinfile[0]) { + if (!setdefault) + error("no picture file name at line %1", baseline); + return; + } + char *path; + gfp = macro_path.open_file(gremlinfile, &path); + if (0 /* nullptr */ == gfp) { + error("cannot open picture file '%1'", gremlinfile); + return; + } + PICTURE = DBRead(gfp); /* read picture file */ + fclose(gfp); + free(path); + if (DBNullelt(PICTURE)) + return; /* If a request is made to make the */ + /* picture fit into a specific area, */ + /* set the scale to do that. */ + + if (stipple == (char *) NULL) /* if user forgot stipple */ + if (has_polygon(PICTURE)) /* and picture has a polygon */ + stipple = (char *)DEFSTIPPLE; /* then set the default */ + + if ((temp = bottompoint - toppoint) < 0.1) + temp = 0.1; + temp = (height != 0.0) ? height / (temp * SCREENtoINCH) : BIG; + if ((troffscale = rightpoint - leftpoint) < 0.1) + troffscale = 0.1; + troffscale = (width != 0.0) ? + width / (troffscale * SCREENtoINCH) : BIG; + if (temp == BIG && troffscale == BIG) + troffscale = xscale; + else { + if (temp < troffscale) + troffscale = temp; + } /* here, troffscale is the */ + /* picture's scaling factor */ + if (pointscale) { + int i; /* do point scaling here, when */ + /* scale is known, before output */ + for (i = 0; i < SIZES; i++) + tsize[i] = (int) (troffscale * (double) tsize[i] + 0.5); + } + + /* change to device units */ + troffscale *= SCREENtoINCH * res; /* from screen units */ + + /* Calculate integer versions of the picture limits. */ + ytop = (int) (toppoint * troffscale); + ybottom = (int) (bottompoint * troffscale); + xleft = (int) (leftpoint * troffscale); + xright = (int) (rightpoint * troffscale); + + /* save stuff in number registers, */ + /* register g1 = picture width and */ + /* register g2 = picture height, */ + /* set vertical spacing, no fill, */ + /* and break (to make sure picture */ + /* starts on left), and put out the */ + /* user's '.GS' line. */ + printf(".br\n" + ".nr g1 %du\n" + ".nr g2 %du\n" + "%s" + ".nr g3 \\n(.f\n" + ".nr g4 \\n(.s\n" + "\\0\n" + ".sp -1\n", + xright - xleft, ybottom - ytop, GScommand); + + if (USE_ST_REQUEST) /* stipple requested for this picture */ + printf(".st %s\n", stipple); + lastx = xleft; /* note where we are (upper left */ + lastyline = lasty = ytop; /* corner of the picture) */ + + /* Just dump everything in the order it appears. + * + * If -s command-line option, traverse picture twice: First time, + * print only the interiors of filled polygons (as borderless + * polygons). Second time, print the outline as series of line + * segments. This way, postprocessors that overwrite rather than + * merge picture elements (such as Postscript) can still have text + * and graphics on a shaded background. + */ + /* if (sflag) */ + if (!sflag) { /* changing the default for filled polygons */ + e = PICTURE; + polyfill = FILL; + while (!DBNullelt(e)) { + printf(".mk\n"); + if (e->type == POLYGON) + HGPrintElt(e, baseline); + printf(".rt\n"); + lastx = xleft; + lastyline = lasty = ytop; + e = DBNextElt(e); + } + } + e = PICTURE; + + /* polyfill = !sflag ? BOTH : OUTLINE; */ + polyfill = sflag ? BOTH : OUTLINE; /* changing default */ + while (!DBNullelt(e)) { + printf(".mk\n"); + HGPrintElt(e, baseline); + printf(".rt\n"); + lastx = xleft; + lastyline = lasty = ytop; + e = DBNextElt(e); + } + + /* decide where to end picture */ + + /* I [Senderowicz?] changed everything here. I always use the */ + /* combination .mk and .rt, so once finished I just space down */ + /* height of the picture that is \n(g2u. */ + if (flyback) { /* end picture at upper left */ + /* ptr.x = leftpoint; + ptr.y = toppoint; */ + } else { /* end picture at lower left */ + /* ptr.x = leftpoint; + ptr.y = bottompoint; */ + printf(".sp \\n(g2u\n"); + } + + /* tmove(&ptr); */ /* restore default line parameters */ + + /* Restore everything to the way it was before the .GS, then */ + /* put out the '.GE' line from user */ + + /* printf("\\D't %du'\\D's %du'\n", DEFTHICK, DEFSTYLE); */ + /* groff doesn't understand the \Ds command */ + + printf("\\D't %du'\n", DEFTHICK); + if (flyback) /* make sure we end up at top of */ + printf(".sp -1\n"); /* picture if 'flying back' */ + if (USE_ST_REQUEST) /* restore stipple to previous */ + printf(".st\n"); + printf(".br\n" + ".ft \\n(g3\n" + ".ps \\n(g4\n" + "%s", inputline); + } else + interpret(inputline); /* take commands from the input file */ + } while (!done); +} + + +/*--------------------------------------------------------------------* + | Routine: savestate ( ) + | + | Results: All the current scaling/font size/font name/thickness/ + | pointscale settings are made the defaults. Scaled + | point sizes are NOT saved. The scaling is done each + | time a new picture is started. + | + | Side Efct: scale, and def* are modified. + *--------------------------------------------------------------------*/ + +void +savestate() +{ + int i; + + for (i = 0; i < STYLES; i++) /* line thickness defaults */ + defthick[i] = thick[i]; + for (i = 0; i < FONTS; i++) /* font name defaults */ + deffont[i] = tfont[i]; + for (i = 0; i < SIZES; i++) /* font size defaults */ + defsize[i] = tsize[i]; + /* stipple font file default indices */ + for (i = 0; i <= NSTIPPLES; i++) + defstipple_index[i] = stipple_index[i]; + + defstipple = stipple; /* if stipple has been set, it's remembered */ + scale *= xscale; /* default scale of individual pictures */ + defpoint = pointscale;/* flag to scale point sizes from x factors */ +} + + +/*--------------------------------------------------------------------* + | Routine: savebounds (x_coordinate, y_coordinate) + | + | Results: Keeps track of the maximum and minimum extent of a + | picture in the global variables: left-, right-, top- and + | bottompoint. 'savebounds' assumes that the points have + | been oriented to the correct direction. No scaling has + | taken place, though. + *--------------------------------------------------------------------*/ + +void +savebounds(double x, + double y) +{ + if (x < leftpoint) + leftpoint = x; + if (x > rightpoint) + rightpoint = x; + if (y < toppoint) + toppoint = y; + if (y > bottompoint) + bottompoint = y; +} + + +/*--------------------------------------------------------------------* + | Routine: interpret (character_string) + | + | Results: Commands are taken from the input string and performed. + | Commands are separated by newlines, and are of the + | format: + | string1 string2 + | where string1 is the command, string2 the argument. + | + | Side Efct: Font and size strings, plus the gremlin file name and + | the width and height variables are set by this routine. + *--------------------------------------------------------------------*/ + +void +interpret(char *line) +{ + char str1[MAXINLINE]; + char str2[MAXINLINE]; + char *chr; + int i; + double par; + + str2[0] = '\0'; + sscanf(line, "%80s%80s", &str1[0], &str2[0]); + for (chr = &str1[0]; *chr; chr++) /* convert command to */ + if (isupper(*chr)) + *chr = tolower(*chr); /* lower case */ + + switch (str1[0]) { + + case '1': + case '2': /* font sizes */ + case '3': + case '4': + i = atoi(str2); + if (i > 0 && i < 1000) + tsize[str1[0] - '1'] = i; + else + error("bad font size value at line %1", linenum); + break; + + case 'r': /* roman */ + if (str2[0] < '0') + goto nofont; + tfont[0] = (char *) grnmalloc(strlen(str2) + 1, "roman command"); + strcpy(tfont[0], str2); + break; + + case 'i': /* italics */ + if (str2[0] < '0') + goto nofont; + tfont[1] = (char *) grnmalloc(strlen(str2) + 1, "italics command"); + strcpy(tfont[1], str2); + break; + + case 'b': /* bold */ + if (str2[0] < '0') + goto nofont; + tfont[2] = (char *) grnmalloc(strlen(str2) + 1, "bold command"); + strcpy(tfont[2], str2); + break; + + case 's': /* special */ + if (str1[1] == 'c') + goto scalecommand; /* or scale */ + + if (str2[0] < '0') { + nofont: + error("no font name specified in line %1", linenum); + break; + } + if (str1[1] == 't') + goto stipplecommand; /* or stipple */ + + tfont[3] = (char *) grnmalloc(strlen(str2) + 1, "special command"); + strcpy(tfont[3], str2); + break; + + case 'l': /* l */ + if (isdigit(str1[1])) { /* set stipple index */ + int idx = atoi(str1 + 1), val; + + if (idx < 0 || idx > NSTIPPLES) { + error("bad stipple number %1 at line %2", idx, linenum); + break; + } + if (!defstipple_index) + defstipple_index = other_stipple_index; + val = atoi(str2); + if (val >= 0 && val < 256) + stipple_index[idx] = val; + else + error("bad stipple index value at line %1", linenum); + break; + } + + stipplecommand: /* set stipple name */ + stipple = (char *) grnmalloc(strlen(str2) + 1, "stipple command"); + strcpy(stipple, str2); + /* if it's a 'known' font (currently only 'cf'), set indices */ + if (strcmp(stipple, "cf") == 0) + defstipple_index = cf_stipple_index; + else + defstipple_index = other_stipple_index; + for (i = 0; i <= NSTIPPLES; i++) + stipple_index[i] = defstipple_index[i]; + break; + + case 'a': /* text adjust */ + par = atof(str2); + switch (str1[1]) { + case '1': + adj1 = par; + break; + case '2': + adj2 = par; + break; + case '3': + adj3 = par; + break; + case '4': + adj4 = par; + break; + default: + error("bad adjust command at line %1", linenum); + break; + } + break; + + case 't': /* thick */ + thick[2] = defthick[0] * atof(str2); + break; + + case 'm': /* medium */ + thick[5] = defthick[0] * atof(str2); + break; + + case 'n': /* narrow */ + thick[0] = thick[1] = thick[3] = thick[4] = + defthick[0] * atof(str2); + break; + + case 'x': /* x */ + scalecommand: /* scale */ + par = atof(str2); + if (par > 0.0) + xscale *= par; + else + error("invalid scale value on line %1", linenum); + break; + + case 'f': /* file */ + strcpy(gremlinfile, str2); + break; + + case 'w': /* width */ + width = atof(str2); + if (width < 0.0) + width = -width; + break; + + case 'h': /* height */ + height = atof(str2); + if (height < 0.0) + height = -height; + break; + + case 'd': /* defaults */ + setdefault = 1; + break; + + case 'p': /* pointscale */ + if (strcmp("off", str2)) + pointscale = 1; + else + pointscale = 0; + break; + + default: + error("unknown command '%1' on line %2", str1, linenum); + exit(8); + break; + }; +} + + +/* + * return TRUE if picture contains a polygon + * otherwise FALSE + */ + +int +has_polygon(ELT *elist) +{ + while (!DBNullelt(elist)) { + if (elist->type == POLYGON) + return (1); + elist = DBNextElt(elist); + } + + return (0); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/html/html.am b/src/preproc/html/html.am new file mode 100644 index 0000000..ac5ca9d --- /dev/null +++ b/src/preproc/html/html.am @@ -0,0 +1,32 @@ +# 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/>. + +bin_PROGRAMS += pre-grohtml +pre_grohtml_LDADD = libgroff.a lib/libgnu.a $(LIBM) +pre_grohtml_SOURCES = \ + src/preproc/html/pre-html.cpp \ + src/preproc/html/pushback.cpp \ + src/preproc/html/pre-html.h \ + src/preproc/html/pushback.h +src/preproc/html/pre-html.$(OBJEXT): defs.h + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/html/pre-html.cpp b/src/preproc/html/pre-html.cpp new file mode 100644 index 0000000..fecfb05 --- /dev/null +++ b/src/preproc/html/pre-html.cpp @@ -0,0 +1,1819 @@ +/* Copyright (C) 2000-2021 Free Software Foundation, Inc. + * Written by Gaius Mulley (gaius@glam.ac.uk). + * + * 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 groff; see the file COPYING. If not, write to the Free + * Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#define PREHTMLC + +#include "lib.h" + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> + +#include "errarg.h" +#include "error.h" +#include "stringclass.h" +#include "posix.h" +#include "defs.h" +#include "searchpath.h" +#include "paper.h" +#include "device.h" +#include "font.h" + +#include <errno.h> +#include <sys/types.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef _POSIX_VERSION +# include <sys/wait.h> +# define PID_T pid_t +#else /* not _POSIX_VERSION */ +# define PID_T int +#endif /* not _POSIX_VERSION */ + +#include <stdarg.h> + +#include "nonposix.h" + +#if 0 +# define DEBUGGING +#endif + +/* Establish some definitions to facilitate discrimination between + differing runtime environments. */ + +#undef MAY_FORK_CHILD_PROCESS +#undef MAY_SPAWN_ASYNCHRONOUS_CHILD + +#if defined(__MSDOS__) || defined(_WIN32) + +// Most MS-DOS and Win32 environments will be missing the 'fork' +// capability (some, like Cygwin, have it, but it is better avoided). + +# define MAY_FORK_CHILD_PROCESS 0 + +// On these systems, we use 'spawn...', instead of 'fork' ... 'exec...'. +# include <process.h> // for 'spawn...' +# include <fcntl.h> // for attributes of pipes + +# if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32) + +// These Win32 implementations allow parent and 'spawn...'ed child to +// multitask asynchronously. + +# define MAY_SPAWN_ASYNCHRONOUS_CHILD 1 + +# else + +// Others may adopt MS-DOS behaviour where parent must sleep, +// from 'spawn...' until child terminates. + +# define MAY_SPAWN_ASYNCHRONOUS_CHILD 0 + +# endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */ + +# if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR) +/* When we are building a DEBUGGING version we need to tell pre-grohtml + where to put intermediate files (the DEBUGGING version will preserve + these on exit). + + On a Unix host, we might simply use '/tmp', but MS-DOS and Win32 will + probably not have this on all disk drives, so default to using + 'c:/temp' instead. (Note that user may choose to override this by + supplying a definition such as + + -DDEBUG_FILE_DIR=d:/path/to/debug/files + + in the CPPFLAGS to 'make'.) */ + +# define DEBUG_FILE_DIR c:/temp +# endif + +#else /* not __MSDOS__ or _WIN32 */ + +// For non-Microsoft environments assume Unix conventions, +// so 'fork' is required and child processes are asynchronous. +# define MAY_FORK_CHILD_PROCESS 1 +# define MAY_SPAWN_ASYNCHRONOUS_CHILD 1 + +# if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR) +/* For a DEBUGGING version, on the Unix host, we can also usually rely + on being able to use '/tmp' for temporary file storage. (Note that, + as in the __MSDOS__ or _WIN32 case above, the user may override this + by defining + + -DDEBUG_FILE_DIR=/path/to/debug/files + + in the CPPFLAGS.) */ + +# define DEBUG_FILE_DIR /tmp +# endif + +#endif /* not __MSDOS__ or _WIN32 */ + +#ifdef DEBUGGING +// For a DEBUGGING version, we need some additional macros, +// to direct the captured debugging mode output to appropriately named +// files in the specified DEBUG_FILE_DIR. + +# define DEBUG_TEXT(text) #text +# define DEBUG_NAME(text) DEBUG_TEXT(text) +# define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name +#endif + +extern "C" const char *Version_string; + +#include "pre-html.h" +#include "pushback.h" +#include "html-strings.h" + +#define DEFAULT_LINE_LENGTH 7 // inches wide +#define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution +#define IMAGE_BORDER_PIXELS 0 +#define INLINE_LEADER_CHAR '\\' + +// Don't use colour names here! Otherwise there is a dependency on +// a file called 'rgb.txt' which maps names to colours. +#define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f" +#define MIN_ALPHA_BITS 0 +#define MAX_ALPHA_BITS 4 + +#define PAGE_TEMPLATE_SHORT "pg" +#define PAGE_TEMPLATE_LONG "-page-" +#define PS_TEMPLATE_SHORT "ps" +#define PS_TEMPLATE_LONG "-ps-" +#define REGION_TEMPLATE_SHORT "rg" +#define REGION_TEMPLATE_LONG "-regions-" + +typedef enum { + CENTERED, LEFT, RIGHT, INLINE +} IMAGE_ALIGNMENT; + +typedef enum {xhtml, html4} html_dialect; + +static int postscriptRes = -1; // PostScript resolution, + // dots per inch +static int stdoutfd = 1; // output file descriptor - + // normally 1 but might move + // -1 means closed +static char *psFileName = 0 /* nullptr */; // PostScript file name +static char *psPageName = 0 /* nullptr */; // name of file + // containing current + // PostScript page +static char *regionFileName = 0 /* nullptr */; // name of file + // containing all image + // regions +static char *imagePageName = 0 /* nullptr */; // name of bitmap image + // file containing + // current page +static const char *image_device = "pnmraw"; +static int image_res = DEFAULT_IMAGE_RES; +static int vertical_offset = 0; +static char *image_template = 0 /* nullptr */; // image file name + // template +static char *macroset_template= 0 /* nullptr */; // image file + // name template + // passed to + // troff by -D +static int troff_arg = 0; // troff arg index +static char *image_dir = 0 /* nullptr */; // user-specified image + // directory +static int textAlphaBits = MAX_ALPHA_BITS; +static int graphicAlphaBits = MAX_ALPHA_BITS; +static char *antiAlias = 0 /* nullptr */; // anti-alias arguments + // to be passed to gs +static bool want_progress_report = false; // display page numbers + // as they are processed +static int currentPageNo = -1; // current image page number +#if defined(DEBUGGING) +static bool debugging = false; +static char *troffFileName = 0 /* nullptr */; // pre-html output sent + // to troff -Tps +static char *htmlFileName = 0 /* nullptr */; // pre-html output sent + // to troff -Thtml +#endif +static bool need_eqn = false; // must we preprocess via eqn? + +static char *linebuf = 0 /* nullptr */; // for scanning devps/DESC +static int linebufsize = 0; +static const char *image_gen = 0 /* nullptr */; // the 'gs' program + +static const char devhtml_desc[] = "devhtml/DESC"; +static const char devps_desc[] = "devps/DESC"; + +const char *const FONT_ENV_VAR = "GROFF_FONT_PATH"; +static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0); +static html_dialect dialect = html4; + + +/* + * Images are generated via PostScript, gs, and the pnm utilities. + */ +#define IMAGE_DEVICE "-Tps" + + +/* + * sys_fatal - Write a fatal error message. + * Taken from src/roff/groff/pipeline.c. + */ + +void sys_fatal(const char *s) +{ + fatal("%1: %2", s, strerror(errno)); +} + +/* + * get_line - Copy a line (w/o newline) from a file to the + * global line buffer. + */ + +int get_line(FILE *f) +{ + if (f == 0) + return 0; + if (linebuf == 0) { + linebuf = new char[128]; + linebufsize = 128; + } + int i = 0; + // skip leading whitespace + for (;;) { + int c = getc(f); + if (c == EOF) + return 0; + if (c != ' ' && c != '\t') { + ungetc(c, f); + break; + } + } + for (;;) { + int c = getc(f); + if (c == EOF) + break; + if (i + 1 >= linebufsize) { + char *old_linebuf = linebuf; + linebuf = new char[linebufsize * 2]; + memcpy(linebuf, old_linebuf, linebufsize); + delete[] old_linebuf; + linebufsize *= 2; + } + linebuf[i++] = c; + if (c == '\n') { + i--; + break; + } + } + linebuf[i] = '\0'; + return 1; +} + +/* + * get_resolution - Return the PostScript device resolution. + */ + +static unsigned int get_resolution(void) +{ + char *pathp; + FILE *f; + unsigned int res = 0; + f = font_path.open_file(devps_desc, &pathp); + if (0 == f) + fatal("cannot open file '%1'", devps_desc); + free(pathp); + // XXX: We should break out of this loop if we hit a "charset" line. + // "This line and everything following it in the file are ignored." + // (groff_font(5)) + while (get_line(f)) + (void) sscanf(linebuf, "res %u", &res); + fclose(f); + return res; +} + + +/* + * get_image_generator - Return the declared program from the HTML + * device description. + */ + +static char *get_image_generator(void) +{ + char *pathp; + FILE *f; + char *generator = 0; + const char keyword[] = "image_generator"; + const size_t keyword_len = strlen(keyword); + f = font_path.open_file(devhtml_desc, &pathp); + if (0 == f) + fatal("cannot open file '%1'", devhtml_desc); + free(pathp); + // XXX: We should break out of this loop if we hit a "charset" line. + // "This line and everything following it in the file are ignored." + // (groff_font(5)) + while (get_line(f)) { + char *cursor = linebuf; + size_t limit = strlen(linebuf); + char *end = linebuf + limit; + if (0 == (strncmp(linebuf, keyword, keyword_len))) { + cursor += keyword_len; + // At least one space or tab is required. + if(!(' ' == *cursor) || ('\t' == *cursor)) + continue; + cursor++; + while((cursor < end) && ((' ' == *cursor) || ('\t' == *cursor))) + cursor++; + if (cursor == end) + continue; + generator = cursor; + } + } + fclose(f); + return generator; +} + +/* + * html_system - A wrapper for system(). + */ + +void html_system(const char *s, int redirect_stdout) +{ +#if defined(DEBUGGING) + if (debugging) { + fprintf(stderr, "executing: "); + fwrite(s, sizeof(char), strlen(s), stderr); + fflush(stderr); + } +#endif + { + int saved_stdout = dup(1); + int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666); + if (redirect_stdout && saved_stdout > 1 && fdnull > 1) + dup2(fdnull, 1); + if (fdnull >= 0) + close(fdnull); + int status = system(s); + if (redirect_stdout) + dup2(saved_stdout, 1); + if (status == -1) + fprintf(stderr, "Calling '%s' failed\n", s); + else if (status) + fprintf(stderr, "Calling '%s' returned status %d\n", s, status); + close(saved_stdout); + } +} + +/* + * make_string - Create a string via `malloc()`, place the variadic + * arguments as formatted by `fmt` into it, and return + * it. Adapted from Linux man-pages' printf(3) example. + * We never return a null pointer, instead treating + * failure as invariably fatal. + */ + +char *make_string(const char *fmt, ...) +{ + size_t size = 0; + char *p = 0 /* nullptr */; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(p, size, fmt, ap); + va_end(ap); + if (n < 0) + sys_fatal("vsnprintf"); + size = static_cast<size_t>(n) + 1 /* '\0' */; + p = static_cast<char *>(malloc(size)); + if (0 /* nullptr */ == p) + sys_fatal("vsnprintf"); + va_start(ap, fmt); + n = vsnprintf(p, size, fmt, ap); + va_end(ap); + if (n < 0) + sys_fatal("vsnprintf"); + assert(p != 0 /* nullptr */); + return p; +} + +/* + * classes and methods for retaining ascii text + */ + +struct char_block { + enum { SIZE = 256 }; + char buffer[SIZE]; + int used; + char_block *next; + + char_block(); +}; + +char_block::char_block() +: used(0), next(0) +{ + for (int i = 0; i < SIZE; i++) + buffer[i] = 0; +} + +class char_buffer { +public: + char_buffer(); + ~char_buffer(); + void read_file(FILE *fp); + int do_html(int argc, char *argv[]); + int do_image(int argc, char *argv[]); + void emit_troff_output(int device_format_selector); + void write_upto_newline(char_block **t, int *i, int is_html); + bool can_see(char_block **t, int *i, const char *string); + void skip_until_newline(char_block **t, int *i); +private: + char_block *head; + char_block *tail; + int run_output_filter(int device_format_selector, int argc, + char *argv[]); +}; + +char_buffer::char_buffer() +: head(0), tail(0) +{ +} + +char_buffer::~char_buffer() +{ + while (head != 0 /* nullptr */) { + char_block *temp = head; + head = head->next; + delete temp; + } +} + +/* + * read_file - Read file `fp` into char_blocks. + */ + +void char_buffer::read_file(FILE *fp) +{ + int n; + while (!feof(fp)) { + if (0 /* nullptr */ == tail) { + tail = new char_block; + head = tail; + } + else { + if (tail->used == char_block::SIZE) { + tail->next = new char_block; + tail = tail->next; + } + } + // We now have a tail ready for the next `SIZE` bytes of the file. + n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used, + fp); + if ((n < 0) || ((0 == n) && !feof(fp))) + sys_fatal("fread"); + tail->used += n * sizeof(char); + } +} + +/* + * writeNbytes - Write n bytes to stdout. + */ + +static void writeNbytes(const char *s, int l) +{ + int n = 0; + int r; + + while (n < l) { + r = write(stdoutfd, s, l - n); + if (r < 0) + sys_fatal("write"); + n += r; + s += r; + } +} + +/* + * writeString - Write a string to stdout. + */ + +static void writeString(const char *s) +{ + writeNbytes(s, strlen(s)); +} + +/* + * makeFileName - Create the image filename template + * and the macroset image template. + */ + +static void makeFileName(void) +{ + if ((image_dir != 0 /* nullptr */) + && (strchr(image_dir, '%') != 0 /* nullptr */)) + fatal("'%%' is prohibited within the image directory name"); + if ((image_template != 0 /* nullptr */) + && (strchr(image_template, '%') != 0 /* nullptr */)) + fatal("'%%' is prohibited within the image template"); + if (0 /* nullptr */ == image_dir) + image_dir = (char *)""; + else if (strlen(image_dir) > 0 + && image_dir[strlen(image_dir) - 1] != '/') + image_dir = make_string("%s/", image_dir); + if (0 /* nullptr */ == image_template) + macroset_template = make_string("%sgrohtml-%d-", image_dir, + int(getpid())); + else + macroset_template = make_string("%s%s-", image_dir, + image_template); + size_t mtlen = strlen(macroset_template); + image_template = (char *)malloc(strlen("%d") + mtlen + 1); + if (0 /* nullptr */ == image_template) + sys_fatal("malloc"); + char *s = strcpy(image_template, macroset_template); + s += mtlen; + // Keep this format string synced with troff:suppress_node::tprint(). + strcpy(s, "%d"); +} + +/* + * setupAntiAlias - Set up the antialias string, used when we call gs. + */ + +static void setupAntiAlias(void) +{ + if (textAlphaBits == 0 && graphicAlphaBits == 0) + antiAlias = make_string(" "); + else if (textAlphaBits == 0) + antiAlias = make_string("-dGraphicsAlphaBits=%d ", + graphicAlphaBits); + else if (graphicAlphaBits == 0) + antiAlias = make_string("-dTextAlphaBits=%d ", textAlphaBits); + else + antiAlias = make_string("-dTextAlphaBits=%d" + " -dGraphicsAlphaBits=%d ", textAlphaBits, + graphicAlphaBits); +} + +/* + * checkImageDir - Check whether the image directory is available. + */ + +static void checkImageDir(void) +{ + if (image_dir != 0 /* nullptr */ && strcmp(image_dir, "") != 0) + if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST)) + fatal("cannot create directory '%1': %2", image_dir, + strerror(errno)); +} + +/* + * write_end_image - End the image. Write out the image extents if we + * are using -Tps. + */ + +static void write_end_image(int is_html) +{ + /* + * if we are producing html then these + * emit image name and enable output + * else + * we are producing images + * in which case these generate image + * boundaries + */ + writeString("\\O[4]\\O[2]"); + if (is_html) + writeString("\\O[1]"); + else + writeString("\\O[0]"); +} + +/* + * write_start_image - Write troff code which will: + * + * (i) disable html output for the following image + * (ii) reset the max/min x/y registers during + * Postscript Rendering. + */ + +static void write_start_image(IMAGE_ALIGNMENT pos, int is_html) +{ + writeString("\\O[5"); + switch (pos) { + case INLINE: + writeString("i"); + break; + case LEFT: + writeString("l"); + break; + case RIGHT: + writeString("r"); + break; + case CENTERED: + default: + writeString("c"); + break; + } + writeString(image_template); + writeString(".png]"); + if (is_html) + writeString("\\O[0]\\O[3]"); + else + // reset min/max registers + writeString("\\O[1]\\O[3]"); +} + +/* + * write_upto_newline - Write the contents of the buffer until a + * newline is seen. Check for + * HTML_IMAGE_INLINE_BEGIN and + * HTML_IMAGE_INLINE_END; process them if they are + * present. + */ + +void char_buffer::write_upto_newline(char_block **t, int *i, + int is_html) +{ + int j = *i; + + if (*t) { + while (j < (*t)->used + && (*t)->buffer[j] != '\n' + && (*t)->buffer[j] != INLINE_LEADER_CHAR) + j++; + if (j < (*t)->used + && (*t)->buffer[j] == '\n') + j++; + writeNbytes((*t)->buffer + (*i), j - (*i)); + if (j < char_block::SIZE && (*t)->buffer[j] == INLINE_LEADER_CHAR) { + if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN)) + write_start_image(INLINE, is_html); + else if (can_see(t, &j, HTML_IMAGE_INLINE_END)) + write_end_image(is_html); + else { + if (j < (*t)->used) { + *i = j; + j++; + writeNbytes((*t)->buffer + (*i), j - (*i)); + } + } + } + if (j == (*t)->used) { + *i = 0; + *t = (*t)->next; + if (*t && (*t)->buffer[j - 1] != '\n') + write_upto_newline(t, i, is_html); + } + else + // newline was seen + *i = j; + } +} + +/* + * can_see - Return true if we can see string in t->buffer[i] onwards. + */ + +bool char_buffer::can_see(char_block **t, int *i, const char *str) +{ + int j = 0; + int l = strlen(str); + int k = *i; + char_block *s = *t; + + while (s) { + while (k < s->used && j < l && s->buffer[k] == str[j]) { + j++; + k++; + } + if (j == l) { + *i = k; + *t = s; + return true; + } + else if (k < s->used && s->buffer[k] != str[j]) + return false; + s = s->next; + k = 0; + } + return false; +} + +/* + * skip_until_newline - Skip all characters until a newline is seen. + * The newline is not consumed. + */ + +void char_buffer::skip_until_newline(char_block **t, int *i) +{ + int j = *i; + + if (*t) { + while (j < (*t)->used && (*t)->buffer[j] != '\n') + j++; + if (j == (*t)->used) { + *i = 0; + *t = (*t)->next; + skip_until_newline(t, i); + } + else + // newline was seen + *i = j; + } +} + +#define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER) +#define HTML_OUTPUT_FILTER 0 +#define IMAGE_OUTPUT_FILTER 1 +#define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR) +#define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName) +#define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName) + +/* + * emit_troff_output - Write formatted buffer content to the troff + * post-processor data pipeline. + */ + +void char_buffer::emit_troff_output(int device_format_selector) +{ + // Handle output for BOTH html and image device formats + // if 'device_format_selector' is passed as + // + // HTML_FORMAT(HTML_OUTPUT_FILTER) + // Buffer data is written to the output stream + // with template image names translated to actual image names. + // + // HTML_FORMAT(IMAGE_OUTPUT_FILTER) + // Buffer data is written to the output stream + // with no translation, for image file creation in the + // post-processor. + + int idx = 0; + char_block *element = head; + + while (element != 0 /* nullptr */) + write_upto_newline(&element, &idx, device_format_selector); + +#if 0 + if (close(stdoutfd) < 0) + sys_fatal ("close"); + + // now we grab fd=1 so that the next pipe cannot use fd=1 + if (stdoutfd == 1) { + if (dup(2) != stdoutfd) + sys_fatal ("dup failed to use fd=1"); + } +#endif /* 0 */ +} + +/* + * The image class remembers the position of all images in the + * PostScript file and assigns names for each image. + */ + +struct imageItem { + imageItem *next; + int X1; + int Y1; + int X2; + int Y2; + char *imageName; + int resolution; + int maxx; + int pageNo; + + imageItem(int x1, int y1, int x2, int y2, + int page, int res, int max_width, char *name); + ~imageItem(); +}; + +/* + * imageItem - Constructor. + */ + +imageItem::imageItem(int x1, int y1, int x2, int y2, + int page, int res, int max_width, char *name) +{ + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + pageNo = page; + resolution = res; + maxx = max_width; + imageName = name; + next = 0 /* nullptr */; +} + +/* + * imageItem - Destructor. + */ + +imageItem::~imageItem() +{ + if (imageName) + free(imageName); +} + +/* + * imageList - A class containing a list of imageItems. + */ + +class imageList { +private: + imageItem *head; + imageItem *tail; + int count; +public: + imageList(); + ~imageList(); + void add(int x1, int y1, int x2, int y2, + int page, int res, int maxx, char *name); + void createImages(void); + int createPage(int pageno); + void createImage(imageItem *i); + int getMaxX(int pageno); +}; + +/* + * imageList - Constructor. + */ + +imageList::imageList() +: head(0), tail(0), count(0) +{ +} + +/* + * imageList - Destructor. + */ + +imageList::~imageList() +{ + while (head != 0 /* nullptr */) { + imageItem *i = head; + head = head->next; + delete i; + } +} + +/* + * createPage - Create image of page `pageno` from PostScript file. + */ + +int imageList::createPage(int pageno) +{ + char *s; + + if (currentPageNo == pageno) + return 0; + + if (currentPageNo >= 1) { + /* + * We need to unlink the files which change each time a new page is + * processed. The final unlink is done by xtmpfile when + * pre-grohtml exits. + */ + unlink(imagePageName); + unlink(psPageName); + } + + if (want_progress_report) { + fprintf(stderr, "[%d] ", pageno); + fflush(stderr); + } + +#if defined(DEBUGGING) + if (debugging) + fprintf(stderr, "creating page %d\n", pageno); +#endif + + s = make_string("psselect -q -p%d %s %s\n", + pageno, psFileName, psPageName); + html_system(s, 1); + assert(strlen(image_gen) > 0); + s = make_string("echo showpage | " + "%s%s -q -dBATCH -dSAFER " + "-dDEVICEHEIGHTPOINTS=792 " + "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true " + "-sDEVICE=%s -r%d %s " + "-sOutputFile=%s %s -\n", + image_gen, + EXE_EXT, + (getMaxX(pageno) * image_res) / postscriptRes, + image_device, + image_res, + antiAlias, + imagePageName, + psPageName); + html_system(s, 1); + free(s); + currentPageNo = pageno; + return 0; +} + +/* + * min - Return the minimum of two numbers. + */ + +int min(int x, int y) +{ + if (x < y) + return x; + else + return y; +} + +/* + * max - Return the maximum of two numbers. + */ + +int max(int x, int y) +{ + if (x > y) + return x; + else + return y; +} + +/* + * getMaxX - Return the largest right-hand position for any image + * on `pageno`. + */ + +int imageList::getMaxX(int pageno) +{ + imageItem *h = head; + int x = postscriptRes * DEFAULT_LINE_LENGTH; + + while (h != 0 /* nullptr */) { + if (h->pageNo == pageno) + x = max(h->X2, x); + h = h->next; + } + return x; +} + +/* + * createImage - Generate a minimal PNG file from the set of page + * images. + */ + +void imageList::createImage(imageItem *i) +{ + if (i->X1 != -1) { + char *s; + int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes + - IMAGE_BORDER_PIXELS, + 0); + int y1 = max(image_res * vertical_offset / 72 + + min(i->Y1, i->Y2) * image_res / postscriptRes + - IMAGE_BORDER_PIXELS, + 0); + int x2 = max(i->X1, i->X2) * image_res / postscriptRes + + IMAGE_BORDER_PIXELS; + int y2 = image_res * vertical_offset / 72 + + max(i->Y1, i->Y2) * image_res / postscriptRes + + 1 + IMAGE_BORDER_PIXELS; + if (createPage(i->pageNo) == 0) { + s = make_string("pnmcut%s %d %d %d %d < %s " + "| pnmcrop%s -quiet | pnmtopng%s -quiet %s" + "> %s\n", + EXE_EXT, + x1, y1, x2 - x1 + 1, y2 - y1 + 1, + imagePageName, + EXE_EXT, + EXE_EXT, + TRANSPARENT, + i->imageName); + html_system(s, 0); + free(s); + } + else { + fprintf(stderr, "failed to generate image of page %d\n", + i->pageNo); + fflush(stderr); + } +#if defined(DEBUGGING) + } + else { + if (debugging) { + fprintf(stderr, "ignoring image as x1 coord is -1\n"); + fflush(stderr); + } +#endif + } +} + +/* + * add - Add an image description to the imageList. + */ + +void imageList::add(int x1, int y1, int x2, int y2, + int page, int res, int maxx, char *name) +{ + imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name); + + if (0 /* nullptr */ == head) { + head = i; + tail = i; + } + else { + tail->next = i; + tail = i; + } +} + +/* + * createImages - For each image descriptor on the imageList, + * create the actual image. + */ + +void imageList::createImages(void) +{ + imageItem *h = head; + + while (h != 0 /* nullptr */) { + createImage(h); + h = h->next; + } +} + +static imageList listOfImages; // list of images defined by region file + +/* + * generateImages - Parse the region file and generate images from the + * PostScript file. The region file contains the + * x1,y1--x2,y2 extents of each image. + */ + +static void generateImages(char *region_file_name) +{ + pushBackBuffer *f=new pushBackBuffer(region_file_name); + + while (f->putPB(f->getPB()) != eof) { + if (f->isString("grohtml-info:page")) { + int page = f->readInt(); + int x1 = f->readInt(); + int y1 = f->readInt(); + int x2 = f->readInt(); + int y2 = f->readInt(); + int maxx = f->readInt(); + char *name = f->readString(); + int res = postscriptRes; + listOfImages.add(x1, y1, x2, y2, page, res, maxx, name); + while (f->putPB(f->getPB()) != '\n' + && f->putPB(f->getPB()) != eof) + (void)f->getPB(); + if (f->putPB(f->getPB()) == '\n') + (void)f->getPB(); + } + else { + /* Write any error messages out to the user. */ + fputc(f->getPB(), stderr); + } + } + fflush(stderr); + + listOfImages.createImages(); + if (want_progress_report) { + fprintf(stderr, "done\n"); + fflush(stderr); + } + delete f; +} + +/* + * set_redirection - Redirect file descriptor `was` to file descriptor + * `willbe`. + */ + +static void set_redirection(int was, int willbe) +{ + // Nothing to do if 'was' and 'willbe' already have same handle. + if (was != willbe) { + // Otherwise attempt the specified redirection. + if (dup2(willbe, was) < 0) { + // Redirection failed, so issue diagnostic and bail out. + fprintf(stderr, "failed to replace fd=%d with %d\n", was, willbe); + if (willbe == STDOUT_FILENO) + fprintf(stderr, + "likely that stdout should be opened before %d\n", was); + sys_fatal("dup2"); + } + + // When redirection has been successfully completed assume redundant + // handle 'willbe' is no longer required, so close it. + if (close(willbe) < 0) + // Issue diagnostic if 'close' fails. + sys_fatal("close"); + } +} + +/* + * save_and_redirect - Duplicate file descriptor for `was` on file + * descriptor `willbe`. + */ + +static int save_and_redirect(int was, int willbe) +{ + if (was == willbe) + // No redirection specified; silently bail out. + return (was); + + // Proceeding with redirection so first save and verify our duplicate + // handle for 'was'. + int saved = dup(was); + if (saved < 0) { + fprintf(stderr, "unable to get duplicate handle for %d\n", was); + sys_fatal("dup"); + } + + // Duplicate handle safely established so complete redirection. + set_redirection(was, willbe); + + // Finally return the saved duplicate descriptor for the original + // 'was' descriptor. + return saved; +} + +/* + * alterDeviceTo - If toImage is set + * the argument list is altered to include + * IMAGE_DEVICE; we invoke groff rather than troff. + * Else + * set -Thtml and groff. + */ + +static void alterDeviceTo(int argc, char *argv[], int toImage) +{ + int i = 0; + + if (toImage) { + while (i < argc) { + if ((strcmp(argv[i], "-Thtml") == 0) || + (strcmp(argv[i], "-Txhtml") == 0)) + argv[i] = (char *)IMAGE_DEVICE; + i++; + } + argv[troff_arg] = (char *)"groff"; /* rather than troff */ + } + else { + while (i < argc) { + if (strcmp(argv[i], IMAGE_DEVICE) == 0) { + if (dialect == xhtml) + argv[i] = (char *)"-Txhtml"; + else + argv[i] = (char *)"-Thtml"; + } + i++; + } + argv[troff_arg] = (char *)"groff"; /* use groff -Z */ + } +} + +/* + * addArg - Append newarg onto the command list for groff. + */ + +char **addArg(int argc, char *argv[], char *newarg) +{ + char **new_argv = (char **)malloc((argc + 2) * sizeof(char *)); + int i = 0; + + if (0 /* nullptr */ == new_argv) + sys_fatal("malloc"); + + if (argc > 0) { + new_argv[i] = argv[i]; + i++; + } + new_argv[i] = newarg; + while (i < argc) { + new_argv[i + 1] = argv[i]; + i++; + } + argc++; + new_argv[argc] = 0 /* nullptr */; + return new_argv; +} + +/* + * addRegDef - Append a defined register or string onto the command + * list for troff. + */ + +char **addRegDef(int argc, char *argv[], const char *numReg) +{ + char **new_argv = (char **)malloc((argc + 2) * sizeof(char *)); + int i = 0; + + if (0 /* nullptr */ == new_argv) + sys_fatal("malloc"); + + while (i < argc) { + new_argv[i] = argv[i]; + i++; + } + new_argv[argc] = strsave(numReg); + argc++; + new_argv[argc] = 0 /* nullptr */; + return new_argv; +} + +/* + * dump_args - Display the argument list. + */ + +void dump_args(int argc, char *argv[]) +{ + fprintf(stderr, " %d arguments:", argc); + for (int i = 0; i < argc; i++) + fprintf(stderr, " %s", argv[i]); + fprintf(stderr, "\n"); +} + +/* + * print_args - Print arguments as if issued on the command line. + */ + +#if defined(DEBUGGING) + +void print_args(int argc, char *argv[]) +{ + if (debugging) { + fprintf(stderr, "executing: "); + for (int i = 0; i < argc; i++) + fprintf(stderr, "%s ", argv[i]); + fprintf(stderr, "\n"); + } +} + +#else + +void print_args(int, char **) +{ +} + +#endif + +int char_buffer::run_output_filter(int filter, int argc, char **argv) +{ + int pipedes[2]; + PID_T child_pid; + int wstatus; + + print_args(argc, argv); + if (pipe(pipedes) < 0) + sys_fatal("pipe"); + +#if MAY_FORK_CHILD_PROCESS + // This is the Unix process model. To invoke our post-processor, + // we must 'fork' the current process. + + if ((child_pid = fork()) < 0) + sys_fatal("fork"); + + else if (child_pid == 0) { + // This is the child process. We redirect its input file descriptor + // to read data emerging from our pipe. There is no point in + // saving, since we won't be able to restore later! + + set_redirection(STDIN_FILENO, pipedes[0]); + + // The parent process will be writing this data; release the child's + // writeable handle on the pipe since we have no use for it. + + if (close(pipedes[1]) < 0) + sys_fatal("close"); + + // The IMAGE_OUTPUT_FILTER needs special output redirection... + + if (filter == IMAGE_OUTPUT_FILTER) { + // ...with BOTH 'stdout' AND 'stderr' diverted to files, the + // latter so that `generateImages()` can scrape "grohtml-info" + // from it. + + set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM); + set_redirection(STDERR_FILENO, REGION_OUTPUT_STREAM); + } + + // Now we are ready to launch the output filter. + + execvp(argv[0], argv); // does not return unless it fails + fatal("cannot execute '%1': %2", argv[0], strerror(errno)); + } + + else { + // This is the parent process. We write data to the filter pipeline + // where the child will read it. We have no need to read from the + // input side ourselves, so close it. + + if (close(pipedes[0]) < 0) + sys_fatal("close"); + + // Now redirect the standard output file descriptor to the inlet end + // of the pipe, and push the formatted data to the filter. + + pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]); + emit_troff_output(DEVICE_FORMAT(filter)); + + // After emitting all the data we close our connection to the inlet + // end of the pipe so the child process will detect end of data. + + set_redirection(STDOUT_FILENO, pipedes[1]); + + // Finally, we must wait for the child process to complete. + + if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid) + sys_fatal("wait"); + } + +#elif MAY_SPAWN_ASYNCHRONOUS_CHILD + + // We do not have `fork` (or we prefer not to use it), but + // asynchronous processes are allowed, passing data through pipes. + // This should be okay for most Win32 systems and is preferred to + // `fork` for starting child processes under Cygwin. + + // Before we start the post-processor we bind its inherited standard + // input file descriptor to the readable end of our pipe, saving our + // own standard input file descriptor in `pipedes[0]`. + + pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]); + + // For the Win32 model, + // we need special provision for saving BOTH 'stdout' and 'stderr'. + + int saved_stdout = dup(STDOUT_FILENO); + int saved_stderr = STDERR_FILENO; + + // The IMAGE_OUTPUT_FILTER needs special output redirection... + + if (filter == IMAGE_OUTPUT_FILTER) { + // with BOTH 'stdout' AND 'stderr' diverted to files while saving a + // duplicate handle for 'stderr'. + + set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM); + saved_stderr = save_and_redirect(STDERR_FILENO, + REGION_OUTPUT_STREAM); + } + + // Use an asynchronous spawn request to start the post-processor. + + if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) { + fatal("cannot spawn %1: %2", argv[0], strerror(errno)); + } + + // Once the post-processor has been started we revert our 'stdin' + // to its original saved source, which also closes the readable handle + // for the pipe. + + set_redirection(STDIN_FILENO, pipedes[0]); + + // if we redirected 'stderr', for use by the image post-processor, + // then we also need to reinstate its original assignment. + + if (filter == IMAGE_OUTPUT_FILTER) + set_redirection(STDERR_FILENO, saved_stderr); + + // Now we redirect the standard output to the inlet end of the pipe, + // and push out the appropriately formatted data to the filter. + + set_redirection(STDOUT_FILENO, pipedes[1]); + emit_troff_output(DEVICE_FORMAT(filter)); + + // After emitting all the data we close our connection to the inlet + // end of the pipe so the child process will detect end of data. + + set_redirection(STDOUT_FILENO, saved_stdout); + + // And finally, we must wait for the child process to complete. + + if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid) + sys_fatal("wait"); + +#else /* can't do asynchronous pipes! */ + + // TODO: code to support an MS-DOS style process model should go here + fatal("output filtering not supported on this platform"); + +#endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */ + + return wstatus; +} + +/* + * do_html - Set the troff number htmlflip and + * write out the buffer to troff -Thtml. + */ + +int char_buffer::do_html(int argc, char *argv[]) +{ + string s; + + alterDeviceTo(argc, argv, 0); + argv += troff_arg; // skip all arguments up to groff + argc -= troff_arg; + argv = addArg(argc, argv, (char *)"-Z"); + argc++; + + s = (char *)"-dwww-image-template="; + s += macroset_template; // Do not combine these statements, + // otherwise they will not work. + s += '\0'; // The trailing '\0' is ignored. + argv = addRegDef(argc, argv, s.contents()); + argc++; + + if (dialect == xhtml) { + argv = addRegDef(argc, argv, "-rxhtml=1"); + argc++; + if (need_eqn) { + argv = addRegDef(argc, argv, "-e"); + argc++; + } + } + +#if defined(DEBUGGING) +# define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName) + // slight security risk: only enabled if defined(DEBUGGING) + if (debugging) { + int saved_stdout = save_and_redirect(STDOUT_FILENO, + HTML_DEBUG_STREAM); + emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER)); + set_redirection(STDOUT_FILENO, saved_stdout); + } +#endif + + return run_output_filter(HTML_OUTPUT_FILTER, argc, argv); +} + +/* + * do_image - Write out the buffer to troff -Tps. + */ + +int char_buffer::do_image(int argc, char *argv[]) +{ + string s; + + alterDeviceTo(argc, argv, 1); + argv += troff_arg; // skip all arguments up to troff/groff + argc -= troff_arg; + argv = addRegDef(argc, argv, "-rps4html=1"); + argc++; + + s = "-dwww-image-template="; + s += macroset_template; + s += '\0'; + argv = addRegDef(argc, argv, s.contents()); + argc++; + + // Override local settings and produce a letter-size PostScript page + // file. + argv = addRegDef(argc, argv, "-P-pletter"); + argc++; + + if (dialect == xhtml) { + if (need_eqn) { + argv = addRegDef(argc, argv, "-rxhtml=1"); + argc++; + } + argv = addRegDef(argc, argv, "-e"); + argc++; + } + +#if defined(DEBUGGING) +# define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName) + // slight security risk: only enabled if defined(DEBUGGING) + if (debugging) { + int saved_stdout = save_and_redirect(STDOUT_FILENO, + IMAGE_DEBUG_STREAM); + emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER)); + set_redirection(STDOUT_FILENO, saved_stdout); + } +#endif + + return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv); +} + +static char_buffer inputFile; + +/* + * usage - Emit usage message. + */ + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-epV] [-a anti-aliasing-text-bits] [-D image-directory]" +" [-F font-directory] [-g anti-aliasing-graphics-bits] [-i resolution]" +" [-I image-stem] [-o image-vertical-offset] [-x html-dialect]" +" troff-command troff-argument ...\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); + if (stdout == stream) { + fputs( +"\n" +"Prepare a troff(1) document for HTML formatting.\n" +"\n" +"This program is not intended to be executed standalone; it is\n" +"normally part of a groff pipeline. If your need to run it manually\n" +"(e.g., for debugging purposes), give the 'groff' program the\n" +"command-line option '-V' to inspect the arguments with which\n", + stream); + fprintf(stream, +"'%s' is called. See the grohtml(1) manual page.\n", + program_name); + exit(EXIT_SUCCESS); + } +} + +/* + * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D, + * and -P-I. Return the argument index of the first + * non-option. + */ + +static int scanArguments(int argc, char **argv) +{ + const char *cmdprefix = getenv("GROFF_COMMAND_PREFIX"); + if (!cmdprefix) + cmdprefix = PROG_PREFIX; + size_t pfxlen = strlen(cmdprefix); + char *troff_name = new char[pfxlen + strlen("troff") + 1]; + char *s = strcpy(troff_name, cmdprefix); + s += pfxlen; + strcpy(s, "troff"); + int c, i; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { 0 /* nullptr */, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, + "+a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options, + 0 /* nullptr */)) + != EOF) + switch(c) { + case 'a': + textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)), + MAX_ALPHA_BITS); + if (textAlphaBits == 3) + fatal("cannot use 3 bits of antialiasing information"); + break; + case 'b': + // handled by post-grohtml (set background color to white) + break; + case 'C': + // handled by post-grohtml (don't write Creator HTML comment) + break; + case 'd': +#if defined(DEBUGGING) + debugging = true; +#endif + break; + case 'D': + image_dir = optarg; + break; + case 'e': + need_eqn = true; + break; + case 'F': + font_path.command_line_dir(optarg); + break; + case 'g': + graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)), + MAX_ALPHA_BITS); + if (graphicAlphaBits == 3) + fatal("cannot use 3 bits of antialiasing information"); + break; + case 'G': + // handled by post-grohtml (don't write CreationDate HTML comment) + break; + case 'h': + // handled by post-grohtml (write headings with font size changes) + break; + case 'i': + image_res = atoi(optarg); + break; + case 'I': + image_template = optarg; + break; + case 'j': + // handled by post-grohtml (set job name for multiple file output) + break; + case 'l': + // handled by post-grohtml (no automatic section links) + break; + case 'n': + // handled by post-grohtml (generate simple heading anchors) + break; + case 'o': + vertical_offset = atoi(optarg); + break; + case 'p': + want_progress_report = true; + break; + case 'r': + // handled by post-grohtml (no header and footer lines) + break; + case 's': + // handled by post-grohtml (use font size n as the HTML base size) + break; + case 'S': + // handled by post-grohtml (set file split level) + break; + case 'v': + printf("GNU pre-grohtml (groff) version %s\n", Version_string); + exit(EXIT_SUCCESS); + case 'V': + // handled by post-grohtml (create validator button) + break; + case 'x': + // html dialect + if (strcmp(optarg, "x") == 0) + dialect = xhtml; + else if (strcmp(optarg, "4") == 0) + dialect = html4; + else + warning("unsupported HTML dialect: '%1'", optarg); + break; + case 'y': + // handled by post-grohtml (create groff signature) + break; + case CHAR_MAX + 1: // --help + usage(stdout); + break; + case '?': + usage(stderr); + exit(EXIT_FAILURE); + break; + default: + break; + } + + i = optind; + while (i < argc) { + if (strcmp(argv[i], troff_name) == 0) + troff_arg = i; + else if (argv[i][0] != '-') + return i; + i++; + } + delete[] troff_name; + + return argc; +} + +/* + * makeTempFiles - Name the temporary files. + */ + +static void makeTempFiles(void) +{ +#if defined(DEBUGGING) + psFileName = DEBUG_FILE("prehtml-ps"); + regionFileName = DEBUG_FILE("prehtml-region"); + imagePageName = DEBUG_FILE("prehtml-page"); + psPageName = DEBUG_FILE("prehtml-psn"); + troffFileName = DEBUG_FILE("prehtml-troff"); + htmlFileName = DEBUG_FILE("prehtml-html"); +#else /* not DEBUGGING */ + FILE *f; + + // psPageName contains a single page of PostScript. + f = xtmpfile(&psPageName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT, true); + if (0 /* nullptr */ == f) + sys_fatal("xtmpfile"); + fclose(f); + + // imagePageName contains a bitmap image of a single PostScript page. + f = xtmpfile(&imagePageName, PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT, + true); + if (0 /* nullptr */ == f) + sys_fatal("xtmpfile"); + fclose(f); + + // psFileName contains a PostScript file of the complete document. + f = xtmpfile(&psFileName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT, true); + if (0 /* nullptr */ == f) + sys_fatal("xtmpfile"); + fclose(f); + + // regionFileName contains a list of the images and their boxed + // coordinates. + f = xtmpfile(®ionFileName, + REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT, true); + if (0 /* nullptr */ == f) + sys_fatal("xtmpfile"); + fclose(f); +#endif /* not DEBUGGING */ +} + +static bool do_file(const char *filename) +{ + FILE *fp; + + current_filename = filename; + if (strcmp(filename, "-") == 0) + fp = stdin; + else { + fp = fopen(filename, "r"); + if (0 /* nullptr*/ == fp) { + error("unable to open '%1': %2", filename, strerror(errno)); + return false; + } + } + inputFile.read_file(fp); + if (fp != stdin) + if (fclose(fp) != 0) + sys_fatal("fclose"); + current_filename = 0 /* nullptr */; + return true; +} + +static void cleanup(void) +{ + free(const_cast<char *>(image_gen)); +} + +int main(int argc, char **argv) +{ +#ifdef CAPTURE_MODE + fprintf(stderr, "%s: invoked with %d arguments ...\n", argv[0], argc); + for (int i = 0; i < argc; i++) + fprintf(stderr, "%2d: %s\n", i, argv[i]); + FILE *dump = fopen(DEBUG_FILE("pre-html-data"), "wb"); + if (dump != 0 /* nullptr */) { + while((int ch = fgetc(stdin)) >= 0) + fputc(ch, dump); + fclose(dump); + } + exit(EXIT_FAILURE); +#endif /* CAPTURE_MODE */ + program_name = argv[0]; + if (atexit(&cleanup) != 0) + sys_fatal("atexit"); + int operand_index = scanArguments(argc, argv); + image_gen = strsave(get_image_generator()); + if (0 == image_gen) + fatal("'image_generator' directive not found in file '%1'", + devhtml_desc); + postscriptRes = get_resolution(); + if (postscriptRes < 1) // TODO: what's a more sane minimum value? + fatal("'res' directive missing or invalid in file '%1'", + devps_desc); + setupAntiAlias(); + checkImageDir(); + makeFileName(); + bool have_file_operand = false; + while (operand_index < argc) { + if (argv[operand_index][0] != '-') { + if(!do_file(argv[operand_index])) + exit(EXIT_FAILURE); + have_file_operand = true; + } + operand_index++; + } + + if (!have_file_operand) + do_file("-"); + makeTempFiles(); + int wstatus = inputFile.do_image(argc, argv); + if (wstatus == 0) { + generateImages(regionFileName); + wstatus = inputFile.do_html(argc, argv); + } + else + if (WEXITSTATUS(wstatus) != 0) + // XXX: This is a crappy suggestion. See Savannah #62673. + fatal("'%1' exited with status %2; re-run with a different output" + " driver to see diagnostic messages", argv[0], + WEXITSTATUS(wstatus)); + exit(EXIT_SUCCESS); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/html/pre-html.h b/src/preproc/html/pre-html.h new file mode 100644 index 0000000..f257854 --- /dev/null +++ b/src/preproc/html/pre-html.h @@ -0,0 +1,38 @@ +// -*- C++ -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + * Written by Gaius Mulley (gaius@glam.ac.uk). + * + * 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 groff; see the file COPYING. If not, write to the Free Software + * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * defines functions implemented within pre-html.cpp + */ + +#if !defined(PREHTMLH) +# define PREHTMLH +# if defined(PREHTMLC) +# define EXTERN +# else +# define EXTERN extern +# endif + + +extern void sys_fatal (const char *s); + +#undef EXTERN +#endif diff --git a/src/preproc/html/pushback.cpp b/src/preproc/html/pushback.cpp new file mode 100644 index 0000000..100ac0b --- /dev/null +++ b/src/preproc/html/pushback.cpp @@ -0,0 +1,336 @@ +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + Written by Gaius Mulley (gaius@glam.ac.uk). + +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 <signal.h> +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> +#include "errarg.h" +#include "error.h" +#include "stringclass.h" +#include "posix.h" +#include "nonposix.h" + +#include <errno.h> +#include <sys/types.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "pushback.h" +#include "pre-html.h" + +#if !defined(TRUE) +# define TRUE (1==1) +#endif + +#if !defined(FALSE) +# define FALSE (1==0) +#endif + +# define ERROR(X) (void)(fprintf(stderr, "%s:%d error %s\n", __FILE__, __LINE__, X) && \ + (fflush(stderr)) && localexit(1)) + + +#define MAXPUSHBACKSTACK 4096 /* maximum number of character that can be pushed back */ + + +/* + * constructor for pushBackBuffer + */ + +pushBackBuffer::pushBackBuffer (char *filename) +{ + charStack = (char *)malloc(MAXPUSHBACKSTACK); + if (charStack == 0) { + sys_fatal("malloc"); + } + stackPtr = 0; /* index to push back stack */ + verbose = 0; + eofFound = FALSE; + lineNo = 1; + if (strcmp(filename, "") != 0) { + stdIn = dup(0); + if (stdIn < 0) { + sys_fatal("dup stdin"); + } + close(0); + if (open(filename, O_RDONLY) != 0) { + sys_fatal("when trying to open file"); + } else { + fileName = filename; + } + } +} + +pushBackBuffer::~pushBackBuffer () +{ + if (charStack != 0) { + free(charStack); + } + close(0); + /* restore stdin in file descriptor 0 */ + if (dup(stdIn) < 0) { + sys_fatal("restore stdin"); + } + close(stdIn); +} + +/* + * localexit - wraps exit with a return code to aid the ERROR macro. + */ + +int localexit (int i) +{ + exit(i); + return( 1 ); +} + +/* + * getPB - returns a character, possibly a pushed back character. + */ + +char pushBackBuffer::getPB (void) +{ + if (stackPtr>0) { + stackPtr--; + return( charStack[stackPtr] ); + } else { + char ch; + + if (read(0, &ch, 1) == 1) { + if (verbose) { + printf("%c", ch); + } + if (ch == '\n') { + lineNo++; + } + return( ch ); + } else { + eofFound = TRUE; + return( eof ); + } + } +} + +/* + * putPB - pushes a character onto the push back stack. + * The same character is returned. + */ + +char pushBackBuffer::putPB (char ch) +{ + if (stackPtr<MAXPUSHBACKSTACK) { + charStack[stackPtr] = ch ; + stackPtr++; + } else { + ERROR("max push back stack exceeded, increase MAXPUSHBACKSTACK constant"); + } + return( ch ); +} + +/* + * isWhite - returns TRUE if a white character is found. This character is NOT consumed. + */ + +static int isWhite (char ch) +{ + return( (ch==' ') || (ch == '\t') || (ch == '\n') ); +} + +/* + * skipToNewline - skips characters until a newline is seen. + */ + +void pushBackBuffer::skipToNewline (void) +{ + while ((putPB(getPB()) != '\n') && (! eofFound)) { + getPB(); + } +} + +/* + * skipUntilToken - skips until a token is seen + */ + +void pushBackBuffer::skipUntilToken (void) +{ + char ch; + + while ((isWhite(putPB(getPB())) || (putPB(getPB()) == '#')) && (! eofFound)) { + ch = getPB(); + if (ch == '#') { + skipToNewline(); + } + } +} + +/* + * isString - returns TRUE if the string, s, matches the pushed back string. + * if TRUE is returned then this string is consumed, otherwise it is + * left alone. + */ + +int pushBackBuffer::isString (const char *s) +{ + int length=strlen(s); + int i=0; + + while ((i<length) && (putPB(getPB())==s[i])) { + if (getPB() != s[i]) { + ERROR("assert failed"); + } + i++; + } + if (i==length) { + return( TRUE ); + } else { + i--; + while (i>=0) { + if (putPB(s[i]) != s[i]) { + ERROR("assert failed"); + } + i--; + } + } + return( FALSE ); +} + +/* + * isDigit - returns TRUE if the character, ch, is a digit. + */ + +static int isDigit (char ch) +{ + return( ((ch>='0') && (ch<='9')) ); +} + +/* + * isHexDigit - returns TRUE if the character, ch, is a hex digit. + */ + +#if 0 +static int isHexDigit (char ch) +{ + return( (isDigit(ch)) || ((ch>='a') && (ch<='f')) ); +} +#endif + +/* + * readInt - returns an integer from the input stream. + */ + +int pushBackBuffer::readInt (void) +{ + int c =0; + int i =0; + int s =1; + char ch=getPB(); + + while (isWhite(ch)) { + ch=getPB(); + } + // now read integer + + if (ch == '-') { + s = -1; + ch = getPB(); + } + while (isDigit(ch)) { + i *= 10; + if ((ch>='0') && (ch<='9')) { + i += (int)(ch-'0'); + } + ch = getPB(); + c++; + } + if (ch != putPB(ch)) { + ERROR("assert failed"); + } + return( i*s ); +} + +/* + * convertToFloat - converts integers, a and b into a.b + */ + +static double convertToFloat (int a, int b) +{ + int c=10; + double f; + + while (b>c) { + c *= 10; + } + f = ((double)a) + (((double)b)/((double)c)); + return( f ); +} + +/* + * readNumber - returns a float representing the word just read. + */ + +double pushBackBuffer::readNumber (void) +{ + int i; + char ch; + + i = readInt(); + if ((ch = getPB()) == '.') { + return convertToFloat(i, readInt()); + } + putPB(ch); + return (double)i; +} + +/* + * readString - reads a string terminated by white space + * and returns a malloced area of memory containing + * a copy of the characters. + */ + +char *pushBackBuffer::readString (void) +{ + char buffer[MAXPUSHBACKSTACK]; + char *str = 0; + int i=0; + char ch=getPB(); + + while (isWhite(ch)) { + ch=getPB(); + } + while ((i < MAXPUSHBACKSTACK) && (! isWhite(ch)) && (! eofFound)) { + buffer[i] = ch; + i++; + ch = getPB(); + } + if (i < MAXPUSHBACKSTACK) { + buffer[i] = (char)0; + str = (char *)malloc(strlen(buffer)+1); + strcpy(str, buffer); + } + return( str ); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/html/pushback.h b/src/preproc/html/pushback.h new file mode 100644 index 0000000..263a6c5 --- /dev/null +++ b/src/preproc/html/pushback.h @@ -0,0 +1,52 @@ +// -*- C -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + Written by Gaius Mulley (gaius@glam.ac.uk). + +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/>. */ + + +#define eof (char)-1 + + +/* + * defines the class and methods implemented within pushback.cpp + */ + +class pushBackBuffer +{ + private: + char *charStack; + int stackPtr; /* index to push back stack */ + int verbose; + int eofFound; + char *fileName; + int lineNo; + int stdIn; + + public: + pushBackBuffer (char *); + ~ pushBackBuffer (); + char getPB (void); + char putPB (char ch); + void skipUntilToken (void); + void skipToNewline (void); + double readNumber (void); + int readInt (void); + char *readString (void); + int isString (const char *string); +}; + + diff --git a/src/preproc/pic/TODO b/src/preproc/pic/TODO new file mode 100644 index 0000000..53ca282 --- /dev/null +++ b/src/preproc/pic/TODO @@ -0,0 +1,35 @@ +In troff mode, dotted and dashed splines. + +Make DELIMITED have type lstr; this would allow us to give better +error messages for problems within the body of for and if constructs. + +In troff mode without -x, fake \D't' with .ps commands. + +Perhaps an option to set command char. + +Add an output class for dumb line printers. It wouldn't be pretty but +it would be better than nothing. Integrate it with texinfo. Useful +for groff -Tascii as well. + +Option to allow better positioning of arrowheads on arcs. + +Perhaps add PostScript output mode. + +Change the interface to the output class so that output devices have +the opportunity to handle arrowheads themselves. + +Consider whether the line thickness should scale. + +Consider whether the test in a for loop should be fuzzy (as it +apparently is in grap). + +Possibly change fillval so that zero is black. + +Provide a way of getting text blocks (positioned with '.in' rather +than \h), into pic. Should be possible to use block of diverted text +in pic. Possibly something similar to T{ and T} in tbl. + +Option to provide macro backtraces. + +Have a path that is searched by 'copy' statement. Set by environment +variable or command-line option. diff --git a/src/preproc/pic/common.cpp b/src/preproc/pic/common.cpp new file mode 100644 index 0000000..6a4a93e --- /dev/null +++ b/src/preproc/pic/common.cpp @@ -0,0 +1,647 @@ +// -*- 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 "pic.h" +#include "common.h" + +// output a dashed circle as a series of arcs + +void common_output::dashed_circle(const position ¢, double rad, + const line_type <) +{ + assert(lt.type == line_type::dashed); + line_type slt = lt; + slt.type = line_type::solid; + double dash_angle = lt.dash_width/rad; + int ndashes; + double gap_angle; + if (dash_angle >= M_PI/4.0) { + if (dash_angle < M_PI/2.0) { + gap_angle = M_PI/2.0 - dash_angle; + ndashes = 4; + } + else if (dash_angle < M_PI) { + gap_angle = M_PI - dash_angle; + ndashes = 2; + } + else { + circle(cent, rad, slt, -1.0); + return; + } + } + else { + ndashes = 4*int(ceil(M_PI/(4.0*dash_angle))); + gap_angle = (M_PI*2.0)/ndashes - dash_angle; + } + for (int i = 0; i < ndashes; i++) { + double start_angle = i*(dash_angle+gap_angle) - dash_angle/2.0; + solid_arc(cent, rad, start_angle, start_angle + dash_angle, lt); + } +} + +// output a dotted circle as a series of dots + +void common_output::dotted_circle(const position ¢, double rad, + const line_type <) +{ + assert(lt.type == line_type::dotted); + double gap_angle = lt.dash_width/rad; + int ndots; + if (gap_angle >= M_PI/2.0) { + // always have at least 2 dots + gap_angle = M_PI; + ndots = 2; + } + else { + ndots = 4*int(M_PI/(2.0*gap_angle)); + gap_angle = (M_PI*2.0)/ndots; + } + double ang = 0.0; + for (int i = 0; i < ndots; i++, ang += gap_angle) + dot(cent + position(cos(ang), sin(ang))*rad, lt); +} + +// recursive function for dash drawing, used by dashed_ellipse + +void common_output::ellipse_arc(const position ¢, + const position &z0, const position &z1, + const distance &dim, const line_type <) +{ + assert(lt.type == line_type::solid); + assert(dim.x != 0 && dim.y != 0); + double eps = 0.0001; + position zml = (z0 + z1) / 2; + // apply affine transformation (from ellipse to circle) to compute angle + // of new position, then invert transformation to get exact position + double psi = atan2(zml.y / dim.y, zml.x / dim.x); + position zm = position(dim.x * cos(psi), dim.y * sin(psi)); + // to approximate the ellipse arc with one or more circle arcs, we + // first compute the radius of curvature in zm + double a_2 = dim.x * dim.x; + double a_4 = a_2 * a_2; + double b_2 = dim.y * dim.y; + double b_4 = b_2 * b_2; + double e_2 = a_2 - b_2; + double temp = a_4 * zm.y * zm.y + b_4 * zm.x * zm.x; + double rho = sqrt(temp / a_4 / b_4 * temp / a_4 / b_4 * temp); + // compute center of curvature circle + position M = position(e_2 * zm.x / a_2 * zm.x / a_2 * zm.x, + -e_2 * zm.y / b_2 * zm.y / b_2 * zm.y); + // compute distance between circle and ellipse arc at start and end + double phi0 = atan2(z0.y - M.y, z0.x - M.x); + double phi1 = atan2(z1.y - M.y, z1.x - M.x); + position M0 = position(rho * cos(phi0), rho * sin(phi0)) + M; + position M1 = position(rho * cos(phi1), rho * sin(phi1)) + M; + double dist0 = hypot(z0 - M0) / sqrt(z0 * z0); + double dist1 = hypot(z1 - M1) / sqrt(z1 * z1); + if (dist0 < eps && dist1 < eps) + solid_arc(M + cent, rho, phi0, phi1, lt); + else { + ellipse_arc(cent, z0, zm, dim, lt); + ellipse_arc(cent, zm, z1, dim, lt); + } +} + +// output a dashed ellipse as a series of arcs + +void common_output::dashed_ellipse(const position ¢, const distance &dim, + const line_type <) +{ + assert(lt.type == line_type::dashed); + double dim_x = dim.x / 2; + double dim_y = dim.y / 2; + line_type slt = lt; + slt.type = line_type::solid; + double dw = lt.dash_width; + // we use an approximation to compute the ellipse length (found in: + // Bronstein, Semendjajew, Taschenbuch der Mathematik) + double lambda = (dim.x - dim.y) / (dim.x + dim.y); + double le = M_PI / 2 * (dim.x + dim.y) + * ((64 - 3 * lambda * lambda * lambda * lambda ) + / (64 - 16 * lambda * lambda)); + // for symmetry we make nmax a multiple of 8 + int nmax = 8 * int(le / dw / 8 + 0.5); + if (nmax < 8) { + nmax = 8; + dw = le / 8; + } + int ndash = nmax / 2; + double gapwidth = (le - dw * ndash) / ndash; + double l = 0; + position z = position(dim_x, 0); + position zdot = z; + int j = 0; + int jmax = int(10 / lt.dash_width); + for (int i = 0; i <= nmax; i++) { + position zold = z; + position zpre = zdot; + double ld = (int(i / 2) + 0.5) * dw + int((i + 1) / 2) * gapwidth; + double lold = 0; + double dl = 1; + // find next position for fixed arc length + while (l < ld) { + j++; + lold = l; + zold = z; + double phi = j * 2 * M_PI / jmax; + z = position(dim_x * cos(phi), dim_y * sin(phi)); + dl = hypot(z - zold); + l += dl; + } + // interpolate linearly between the last two points, + // using the length difference as the scaling factor + double delta = (ld - lold) / dl; + zdot = zold + (z - zold) * delta; + // compute angle of new position on the affine circle + // and use it to get the exact value on the ellipse + double psi = atan2(zdot.y / dim_y, zdot.x / dim_x); + zdot = position(dim_x * cos(psi), dim_y * sin(psi)); + if ((i % 2 == 0) && (i > 1)) + ellipse_arc(cent, zpre, zdot, dim / 2, slt); + } +} + +// output a dotted ellipse as a series of dots + +void common_output::dotted_ellipse(const position ¢, const distance &dim, + const line_type <) +{ + assert(lt.type == line_type::dotted); + double dim_x = dim.x / 2; + double dim_y = dim.y / 2; + line_type slt = lt; + slt.type = line_type::solid; + // we use an approximation to compute the ellipse length (found in: + // Bronstein, Semendjajew, Taschenbuch der Mathematik) + double lambda = (dim.x - dim.y) / (dim.x + dim.y); + double le = M_PI / 2 * (dim.x + dim.y) + * ((64 - 3 * lambda * lambda * lambda * lambda ) + / (64 - 16 * lambda * lambda)); + // for symmetry we make nmax a multiple of 4 + int ndots = 4 * int(le / lt.dash_width / 4 + 0.5); + if (ndots < 4) + ndots = 4; + double l = 0; + position z = position(dim_x, 0); + int j = 0; + int jmax = int(10 / lt.dash_width); + for (int i = 1; i <= ndots; i++) { + position zold = z; + double lold = l; + double ld = i * le / ndots; + double dl = 1; + // find next position for fixed arc length + while (l < ld) { + j++; + lold = l; + zold = z; + double phi = j * 2 * M_PI / jmax; + z = position(dim_x * cos(phi), dim_y * sin(phi)); + dl = hypot(z - zold); + l += dl; + } + // interpolate linearly between the last two points, + // using the length difference as the scaling factor + double delta = (ld - lold) / dl; + position zdot = zold + (z - zold) * delta; + // compute angle of new position on the affine circle + // and use it to get the exact value on the ellipse + double psi = atan2(zdot.y / dim_y, zdot.x / dim_x); + zdot = position(dim_x * cos(psi), dim_y * sin(psi)); + dot(cent + zdot, slt); + } +} + +// return non-zero iff we can compute a center + +int compute_arc_center(const position &start, const position ¢, + const position &end, position *result) +{ + // This finds the point along the vector from start to cent that + // is equidistant between start and end. + distance c = cent - start; + distance e = end - start; + double n = c*e; + if (n == 0.0) + return 0; + *result = start + c*((e*e)/(2.0*n)); + return 1; +} + +// output a dashed arc as a series of arcs + +void common_output::dashed_arc(const position &start, const position ¢, + const position &end, const line_type <) +{ + assert(lt.type == line_type::dashed); + position c; + if (!compute_arc_center(start, cent, end, &c)) { + line(start, &end, 1, lt); + return; + } + distance start_offset = start - c; + distance end_offset = end - c; + double start_angle = atan2(start_offset.y, start_offset.x); + double end_angle = atan2(end_offset.y, end_offset.x); + double rad = hypot(c - start); + double dash_angle = lt.dash_width/rad; + double total_angle = end_angle - start_angle; + while (total_angle < 0) + total_angle += M_PI + M_PI; + if (total_angle <= dash_angle*2.0) { + solid_arc(cent, rad, start_angle, end_angle, lt); + return; + } + int ndashes = int((total_angle - dash_angle)/(dash_angle*2.0) + .5); + double dash_and_gap_angle = (total_angle - dash_angle)/ndashes; + for (int i = 0; i <= ndashes; i++) + solid_arc(cent, rad, start_angle + i*dash_and_gap_angle, + start_angle + i*dash_and_gap_angle + dash_angle, lt); +} + +// output a dotted arc as a series of dots + +void common_output::dotted_arc(const position &start, const position ¢, + const position &end, const line_type <) +{ + assert(lt.type == line_type::dotted); + position c; + if (!compute_arc_center(start, cent, end, &c)) { + line(start, &end, 1, lt); + return; + } + distance start_offset = start - c; + distance end_offset = end - c; + double start_angle = atan2(start_offset.y, start_offset.x); + double total_angle = atan2(end_offset.y, end_offset.x) - start_angle; + while (total_angle < 0) + total_angle += M_PI + M_PI; + double rad = hypot(c - start); + int ndots = int(total_angle/(lt.dash_width/rad) + .5); + if (ndots == 0) + dot(start, lt); + else { + for (int i = 0; i <= ndots; i++) { + double a = start_angle + (total_angle*i)/ndots; + dot(cent + position(cos(a), sin(a))*rad, lt); + } + } +} + +void common_output::solid_arc(const position ¢, double rad, + double start_angle, double end_angle, + const line_type <) +{ + line_type slt = lt; + slt.type = line_type::solid; + arc(cent + position(cos(start_angle), sin(start_angle))*rad, + cent, + cent + position(cos(end_angle), sin(end_angle))*rad, + slt); +} + + +void common_output::rounded_box(const position ¢, const distance &dim, + double rad, const line_type <, + double fill, char *color_fill) +{ + if (fill >= 0.0 || color_fill) + filled_rounded_box(cent, dim, rad, fill); + switch (lt.type) { + case line_type::invisible: + break; + case line_type::dashed: + dashed_rounded_box(cent, dim, rad, lt); + break; + case line_type::dotted: + dotted_rounded_box(cent, dim, rad, lt); + break; + case line_type::solid: + solid_rounded_box(cent, dim, rad, lt); + break; + default: + assert(0); + } +} + + +void common_output::dashed_rounded_box(const position ¢, + const distance &dim, double rad, + const line_type <) +{ + line_type slt = lt; + slt.type = line_type::solid; + + double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad; + int n_hor_dashes = int(hor_length/(lt.dash_width*2.0) + .5); + double hor_gap_width = (n_hor_dashes != 0 + ? hor_length/n_hor_dashes - lt.dash_width + : 0.0); + + double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad; + int n_vert_dashes = int(vert_length/(lt.dash_width*2.0) + .5); + double vert_gap_width = (n_vert_dashes != 0 + ? vert_length/n_vert_dashes - lt.dash_width + : 0.0); + // Note that each corner arc has to be split into two for dashing, + // because one part is dashed using vert_gap_width, and the other + // using hor_gap_width. + double offset = lt.dash_width/2.0; + dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, + -M_PI/4.0, 0, slt, lt.dash_width, vert_gap_width, &offset); + dash_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad), + cent + position(dim.x/2.0, dim.y/2.0 - rad), + slt, lt.dash_width, vert_gap_width, &offset); + dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, + 0, M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset); + + offset = lt.dash_width/2.0; + dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, + M_PI/4.0, M_PI/2, slt, lt.dash_width, hor_gap_width, &offset); + dash_line(cent + position(dim.x/2.0 - rad, dim.y/2.0), + cent + position(-dim.x/2.0 + rad, dim.y/2.0), + slt, lt.dash_width, hor_gap_width, &offset); + dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, + M_PI/2, 3*M_PI/4.0, slt, lt.dash_width, hor_gap_width, &offset); + + offset = lt.dash_width/2.0; + dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, + 3.0*M_PI/4.0, M_PI, slt, lt.dash_width, vert_gap_width, &offset); + dash_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad), + cent + position(-dim.x/2.0, -dim.y/2.0 + rad), + slt, lt.dash_width, vert_gap_width, &offset); + dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, + M_PI, 5.0*M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset); + + offset = lt.dash_width/2.0; + dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, + 5*M_PI/4.0, 3*M_PI/2.0, slt, lt.dash_width, hor_gap_width, &offset); + dash_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0), + cent + position(dim.x/2.0 - rad, -dim.y/2.0), + slt, lt.dash_width, hor_gap_width, &offset); + dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, + 3*M_PI/2, 7*M_PI/4, slt, lt.dash_width, hor_gap_width, &offset); +} + +// Used by dashed_rounded_box. + +void common_output::dash_arc(const position ¢, double rad, + double start_angle, double end_angle, + const line_type <, + double dash_width, double gap_width, + double *offsetp) +{ + double length = (end_angle - start_angle)*rad; + double pos = 0.0; + for (;;) { + if (*offsetp >= dash_width) { + double rem = dash_width + gap_width - *offsetp; + if (pos + rem > length) { + *offsetp += length - pos; + break; + } + else { + pos += rem; + *offsetp = 0.0; + } + } + else { + double rem = dash_width - *offsetp; + if (pos + rem > length) { + solid_arc(cent, rad, start_angle + pos/rad, end_angle, lt); + *offsetp += length - pos; + break; + } + else { + solid_arc(cent, rad, start_angle + pos/rad, + start_angle + (pos + rem)/rad, lt); + pos += rem; + *offsetp = dash_width; + } + } + } +} + +// Used by dashed_rounded_box. + +void common_output::dash_line(const position &start, const position &end, + const line_type <, + double dash_width, double gap_width, + double *offsetp) +{ + distance dist = end - start; + double length = hypot(dist); + if (length == 0.0) + return; + double pos = 0.0; + for (;;) { + if (*offsetp >= dash_width) { + double rem = dash_width + gap_width - *offsetp; + if (pos + rem > length) { + *offsetp += length - pos; + break; + } + else { + pos += rem; + *offsetp = 0.0; + } + } + else { + double rem = dash_width - *offsetp; + if (pos + rem > length) { + line(start + dist*(pos/length), &end, 1, lt); + *offsetp += length - pos; + break; + } + else { + position p(start + dist*((pos + rem)/length)); + line(start + dist*(pos/length), &p, 1, lt); + pos += rem; + *offsetp = dash_width; + } + } + } +} + +void common_output::dotted_rounded_box(const position ¢, + const distance &dim, double rad, + const line_type <) +{ + line_type slt = lt; + slt.type = line_type::solid; + + double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad; + int n_hor_dots = int(hor_length/lt.dash_width + .5); + double hor_gap_width = (n_hor_dots != 0 + ? hor_length/n_hor_dots + : lt.dash_width); + + double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad; + int n_vert_dots = int(vert_length/lt.dash_width + .5); + double vert_gap_width = (n_vert_dots != 0 + ? vert_length/n_vert_dots + : lt.dash_width); + double epsilon = lt.dash_width/(rad*100.0); + + double offset = 0.0; + dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, + -M_PI/4.0, 0, slt, vert_gap_width, &offset); + dot_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad), + cent + position(dim.x/2.0, dim.y/2.0 - rad), + slt, vert_gap_width, &offset); + dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, + 0, M_PI/4.0 - epsilon, slt, vert_gap_width, &offset); + + offset = 0.0; + dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, + M_PI/4.0, M_PI/2, slt, hor_gap_width, &offset); + dot_line(cent + position(dim.x/2.0 - rad, dim.y/2.0), + cent + position(-dim.x/2.0 + rad, dim.y/2.0), + slt, hor_gap_width, &offset); + dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, + M_PI/2, 3*M_PI/4.0 - epsilon, slt, hor_gap_width, &offset); + + offset = 0.0; + dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, + 3.0*M_PI/4.0, M_PI, slt, vert_gap_width, &offset); + dot_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad), + cent + position(-dim.x/2.0, -dim.y/2.0 + rad), + slt, vert_gap_width, &offset); + dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, + M_PI, 5.0*M_PI/4.0 - epsilon, slt, vert_gap_width, &offset); + + offset = 0.0; + dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, + 5*M_PI/4.0, 3*M_PI/2.0, slt, hor_gap_width, &offset); + dot_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0), + cent + position(dim.x/2.0 - rad, -dim.y/2.0), + slt, hor_gap_width, &offset); + dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, + 3*M_PI/2, 7*M_PI/4 - epsilon, slt, hor_gap_width, &offset); +} + +// Used by dotted_rounded_box. + +void common_output::dot_arc(const position ¢, double rad, + double start_angle, double end_angle, + const line_type <, double gap_width, + double *offsetp) +{ + double length = (end_angle - start_angle)*rad; + double pos = 0.0; + for (;;) { + if (*offsetp == 0.0) { + double ang = start_angle + pos/rad; + dot(cent + position(cos(ang), sin(ang))*rad, lt); + } + double rem = gap_width - *offsetp; + if (pos + rem > length) { + *offsetp += length - pos; + break; + } + else { + pos += rem; + *offsetp = 0.0; + } + } +} + +// Used by dotted_rounded_box. + +void common_output::dot_line(const position &start, const position &end, + const line_type <, double gap_width, + double *offsetp) +{ + distance dist = end - start; + double length = hypot(dist); + if (length == 0.0) + return; + double pos = 0.0; + for (;;) { + if (*offsetp == 0.0) + dot(start + dist*(pos/length), lt); + double rem = gap_width - *offsetp; + if (pos + rem > length) { + *offsetp += length - pos; + break; + } + else { + pos += rem; + *offsetp = 0.0; + } + } +} + +void common_output::solid_rounded_box(const position ¢, + const distance &dim, double rad, + const line_type <) +{ + position tem = cent - dim/2.0; + arc(tem + position(0.0, rad), + tem + position(rad, rad), + tem + position(rad, 0.0), + lt); + tem = cent + position(-dim.x/2.0, dim.y/2.0); + arc(tem + position(rad, 0.0), + tem + position(rad, -rad), + tem + position(0.0, -rad), + lt); + tem = cent + dim/2.0; + arc(tem + position(0.0, -rad), + tem + position(-rad, -rad), + tem + position(-rad, 0.0), + lt); + tem = cent + position(dim.x/2.0, -dim.y/2.0); + arc(tem + position(-rad, 0.0), + tem + position(-rad, rad), + tem + position(0.0, rad), + lt); + position end; + end = cent + position(-dim.x/2.0, dim.y/2.0 - rad); + line(cent - dim/2.0 + position(0.0, rad), &end, 1, lt); + end = cent + position(dim.x/2.0 - rad, dim.y/2.0); + line(cent + position(-dim.x/2.0 + rad, dim.y/2.0), &end, 1, lt); + end = cent + position(dim.x/2.0, -dim.y/2.0 + rad); + line(cent + position(dim.x/2.0, dim.y/2.0 - rad), &end, 1, lt); + end = cent + position(-dim.x/2.0 + rad, -dim.y/2.0); + line(cent + position(dim.x/2.0 - rad, -dim.y/2.0), &end, 1, lt); +} + +void common_output::filled_rounded_box(const position ¢, + const distance &dim, double rad, + double fill) +{ + line_type ilt; + ilt.type = line_type::invisible; + circle(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, ilt, fill); + circle(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, ilt, fill); + circle(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, ilt, fill); + circle(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, ilt, fill); + position vec[4]; + vec[0] = cent + position(dim.x/2.0, dim.y/2.0 - rad); + vec[1] = cent + position(-dim.x/2.0, dim.y/2.0 - rad); + vec[2] = cent + position(-dim.x/2.0, -dim.y/2.0 + rad); + vec[3] = cent + position(dim.x/2.0, -dim.y/2.0 + rad); + polygon(vec, 4, ilt, fill); + vec[0] = cent + position(dim.x/2.0 - rad, dim.y/2.0); + vec[1] = cent + position(-dim.x/2.0 + rad, dim.y/2.0); + vec[2] = cent + position(-dim.x/2.0 + rad, -dim.y/2.0); + vec[3] = cent + position(dim.x/2.0 - rad, -dim.y/2.0); + polygon(vec, 4, ilt, fill); +} diff --git a/src/preproc/pic/common.h b/src/preproc/pic/common.h new file mode 100644 index 0000000..a2d5f70 --- /dev/null +++ b/src/preproc/pic/common.h @@ -0,0 +1,79 @@ +// -*- 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 common_output : public output { +private: + void dash_line(const position &start, const position &end, + const line_type <, double dash_width, double gap_width, + double *offsetp); + void dash_arc(const position ¢, double rad, + double start_angle, double end_angle, const line_type <, + double dash_width, double gap_width, double *offsetp); + void dot_line(const position &start, const position &end, + const line_type <, double gap_width, double *offsetp); + void dot_arc(const position ¢, double rad, + double start_angle, double end_angle, const line_type <, + double gap_width, double *offsetp); +protected: + virtual void dot(const position &, const line_type &) = 0; + void ellipse_arc(const position &, const position &, + const position &, const distance &, + const line_type &); + void dashed_circle(const position &, double rad, const line_type &); + void dotted_circle(const position &, double rad, const line_type &); + void dashed_ellipse(const position &, const distance &, const line_type &); + void dotted_ellipse(const position &, const distance &, const line_type &); + void dashed_arc(const position &, const position &, const position &, + const line_type &); + void dotted_arc(const position &, const position &, const position &, + const line_type &); + virtual void solid_arc(const position ¢, double rad, double start_angle, + double end_angle, const line_type <); + void dashed_rounded_box(const position &, const distance &, double, + const line_type &); + void dotted_rounded_box(const position &, const distance &, double, + const line_type &); + void solid_rounded_box(const position &, const distance &, double, + const line_type &); + void filled_rounded_box(const position &, const distance &, double, + double); +public: + void start_picture(double sc, const position &ll, const position &ur) = 0; + void finish_picture() = 0; + void circle(const position &, double rad, const line_type &, double) = 0; + void text(const position &, text_piece *, int, double) = 0; + void line(const position &, const position *, int n, const line_type &) = 0; + void polygon(const position *, int n, const line_type &, double) = 0; + void spline(const position &, const position *, int n, + const line_type &) = 0; + void arc(const position &, const position &, const position &, + const line_type &) = 0; + void ellipse(const position &, const distance &, + const line_type &, double) = 0; + void rounded_box(const position &, const distance &, double, + const line_type &, double, char *); + void set_color(char *, char *) = 0; + void reset_color() = 0; + char *get_last_filled() = 0; + char *get_outline_color() = 0; +}; + +int compute_arc_center(const position &start, const position ¢, + const position &end, position *result); + diff --git a/src/preproc/pic/lex.cpp b/src/preproc/pic/lex.cpp new file mode 100644 index 0000000..e59801b --- /dev/null +++ b/src/preproc/pic/lex.cpp @@ -0,0 +1,2039 @@ +// -*- 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 "pic.h" +#include "ptable.h" +#include "object.h" +#include "pic.hpp" + +declare_ptable(char) +implement_ptable(char) + +PTABLE(char) macro_table; + +// First character of the range representing $1-$<MAX_ARG>. +// All of them must be invalid input characters. +#ifndef IS_EBCDIC_HOST +#define ARG1 0x80 +#define MAX_ARG 32 +#else +#define ARG1 0x30 +#define MAX_ARG 16 +#endif + +class macro_input : public input { + char *s; + char *p; +public: + macro_input(const char *); + ~macro_input(); + int get(); + int peek(); +}; + +class argument_macro_input : public input { + char *s; + char *p; + char *ap; + int argc; + char *argv[MAX_ARG]; +public: + argument_macro_input(const char *, int, char **); + ~argument_macro_input(); + int get(); + int peek(); +}; + +input::input() : next(0) +{ +} + +input::~input() +{ +} + +int input::get_location(const char **, int *) +{ + return 0; +} + +file_input::file_input(FILE *f, const char *fn) +: fp(f), filename(fn), lineno(0), ptr("") +{ +} + +file_input::~file_input() +{ + fclose(fp); +} + +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] == 'P' + && (line[2] == 'S' || line[2] == 'E' || line[2] == 'F') + && (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 (unsigned char)*ptr++; + else + return EOF; +} + +int file_input::peek() +{ + if (*ptr != '\0' || read_line()) + return (unsigned char)*ptr; + else + return EOF; +} + +int file_input::get_location(const char **fnp, int *lnp) +{ + *fnp = filename; + *lnp = lineno; + return 1; +} + +macro_input::macro_input(const char *str) +{ + p = s = strsave(str); +} + +macro_input::~macro_input() +{ + free(s); +} + +int macro_input::get() +{ + if (p == 0 || *p == '\0') + return EOF; + else + return (unsigned char)*p++; +} + +int macro_input::peek() +{ + if (p == 0 || *p == '\0') + return EOF; + else + return (unsigned char)*p; +} + +char *process_body(const char *body) +{ + char *s = strsave(body); + int j = 0; + for (int i = 0; s[i] != '\0'; i++) + if (s[i] == '$' && csdigit(s[i + 1])) { + int n = 0; + int start = i; + i++; + while (csdigit(s[i])) + if (n > MAX_ARG) + i++; + else + n = 10 * n + s[i++] - '0'; + if (n > MAX_ARG) { + string arg; + for (int k = start; k < i; k++) + arg += s[k]; + lex_error("invalid macro argument number %1", arg.contents()); + } + else if (n > 0) + s[j++] = ARG1 + n - 1; + i--; + } + else + s[j++] = s[i]; + s[j] = '\0'; + return s; +} + +argument_macro_input::argument_macro_input(const char *body, int ac, char **av) +: ap(0), argc(ac) +{ + for (int i = 0; i < argc; i++) + argv[i] = av[i]; + p = s = process_body(body); +} + +argument_macro_input::~argument_macro_input() +{ + for (int i = 0; i < argc; i++) + free(argv[i]); + free(s); +} + +int argument_macro_input::get() +{ + if (ap) { + if (*ap != '\0') + return (unsigned char)*ap++; + ap = 0; + } + if (p == 0) + return EOF; + while ((unsigned char)*p >= ARG1 + && (unsigned char)*p <= ARG1 + MAX_ARG - 1) { + int i = (unsigned char)*p++ - ARG1; + if (i < argc && argv[i] != 0 && argv[i][0] != '\0') { + ap = argv[i]; + return (unsigned char)*ap++; + } + } + if (*p == '\0') + return EOF; + return (unsigned char)*p++; +} + +int argument_macro_input::peek() +{ + if (ap) { + if (*ap != '\0') + return (unsigned char)*ap; + ap = 0; + } + if (p == 0) + return EOF; + while ((unsigned char)*p >= ARG1 + && (unsigned char)*p <= ARG1 + MAX_ARG - 1) { + int i = (unsigned char)*p++ - ARG1; + if (i < argc && argv[i] != 0 && argv[i][0] != '\0') { + ap = argv[i]; + return (unsigned char)*ap; + } + } + if (*p == '\0') + return EOF; + return (unsigned char)*p; +} + +class input_stack { + static input *current_input; + static int bol_flag; +public: + static void push(input *); + static void clear(); + static int get_char(); + static int peek_char(); + static int get_location(const char **fnp, int *lnp); + static void push_back(unsigned char c, int was_bol = 0); + static int bol(); +}; + +input *input_stack::current_input = 0; +int input_stack::bol_flag = 0; + +inline int input_stack::bol() +{ + return bol_flag; +} + +void input_stack::clear() +{ + while (current_input != 0) { + input *tem = current_input; + current_input = current_input->next; + delete tem; + } + bol_flag = 1; +} + +void input_stack::push(input *in) +{ + in->next = current_input; + current_input = in; +} + +void lex_init(input *top) +{ + input_stack::clear(); + input_stack::push(top); +} + +void lex_cleanup() +{ + while (input_stack::get_char() != EOF) + ; +} + +int input_stack::get_char() +{ + while (current_input != 0) { + int c = current_input->get(); + if (c != EOF) { + bol_flag = c == '\n'; + return c; + } + // don't pop the top-level input off the stack + if (current_input->next == 0) + return EOF; + input *tem = current_input; + current_input = current_input->next; + delete tem; + } + return EOF; +} + +int input_stack::peek_char() +{ + while (current_input != 0) { + int c = current_input->peek(); + if (c != EOF) + return c; + if (current_input->next == 0) + return EOF; + input *tem = current_input; + current_input = current_input->next; + delete tem; + } + return EOF; +} + +class char_input : public input { + int c; +public: + char_input(int); + int get(); + int peek(); +}; + +char_input::char_input(int n) : c((unsigned char)n) +{ +} + +int char_input::get() +{ + int n = c; + c = EOF; + return n; +} + +int char_input::peek() +{ + return c; +} + +void input_stack::push_back(unsigned char c, int was_bol) +{ + push(new char_input(c)); + bol_flag = was_bol; +} + +int input_stack::get_location(const char **fnp, int *lnp) +{ + for (input *p = current_input; p; p = p->next) + if (p->get_location(fnp, lnp)) + return 1; + return 0; +} + +string context_buffer; + +string token_buffer; +double token_double; +int token_int; + +void interpolate_macro_with_args(const char *body) +{ + char *argv[MAX_ARG]; + int argc = 0; + int ignore = 0; + int i; + for (i = 0; i < MAX_ARG; i++) + argv[i] = 0; + int level = 0; + int c; + enum { NORMAL, IN_STRING, IN_STRING_QUOTED } state = NORMAL; + do { + token_buffer.clear(); + for (;;) { + c = input_stack::get_char(); + if (c == EOF) { + lex_error("end of input while scanning macro arguments"); + break; + } + if (state == NORMAL && level == 0 && (c == ',' || c == ')')) { + if (token_buffer.length() > 0) { + token_buffer += '\0'; + if (!ignore) { + if (argc == MAX_ARG) { + lex_warning("only %1 macro arguments supported", MAX_ARG); + ignore = 1; + } + else + argv[argc] = strsave(token_buffer.contents()); + } + } + // for 'foo()', argc = 0 + if (argc > 0 || c != ')' || i > 0) + if (!ignore) + argc++; + break; + } + token_buffer += char(c); + switch (state) { + case NORMAL: + if (c == '"') + state = IN_STRING; + else if (c == '(') + level++; + else if (c == ')') + level--; + break; + case IN_STRING: + if (c == '"') + state = NORMAL; + else if (c == '\\') + state = IN_STRING_QUOTED; + break; + case IN_STRING_QUOTED: + state = IN_STRING; + break; + } + } + } while (c != ')' && c != EOF); + input_stack::push(new argument_macro_input(body, argc, argv)); +} + +static int docmp(const char *s1, int n1, const char *s2, int n2) +{ + if (n1 < n2) { + int r = memcmp(s1, s2, n1); + return r ? r : -1; + } + else if (n1 > n2) { + int r = memcmp(s1, s2, n2); + return r ? r : 1; + } + else + return memcmp(s1, s2, n1); +} + +int lookup_keyword(const char *str, int len) +{ + static struct keyword { + const char *name; + int token; + } table[] = { + { "Here", HERE }, + { "above", ABOVE }, + { "aligned", ALIGNED }, + { "and", AND }, + { "arc", ARC }, + { "arrow", ARROW }, + { "at", AT }, + { "atan2", ATAN2 }, + { "below", BELOW }, + { "between", BETWEEN }, + { "bottom", BOTTOM }, + { "box", BOX }, + { "by", BY }, + { "ccw", CCW }, + { "center", CENTER }, + { "chop", CHOP }, + { "circle", CIRCLE }, + { "color", COLORED }, + { "colored", COLORED }, + { "colour", COLORED }, + { "coloured", COLORED }, + { "command", COMMAND }, + { "copy", COPY }, + { "cos", COS }, + { "cw", CW }, + { "dashed", DASHED }, + { "define", DEFINE }, + { "diam", DIAMETER }, + { "diameter", DIAMETER }, + { "do", DO }, + { "dotted", DOTTED }, + { "down", DOWN }, + { "east", EAST }, + { "ellipse", ELLIPSE }, + { "else", ELSE }, + { "end", END }, + { "exp", EXP }, + { "figname", FIGNAME }, + { "fill", FILL }, + { "filled", FILL }, + { "for", FOR }, + { "from", FROM }, + { "height", HEIGHT }, + { "ht", HEIGHT }, + { "if", IF }, + { "int", INT }, + { "invis", INVISIBLE }, + { "invisible", INVISIBLE }, + { "last", LAST }, + { "left", LEFT }, + { "line", LINE }, + { "ljust", LJUST }, + { "log", LOG }, + { "lower", LOWER }, + { "max", K_MAX }, + { "min", K_MIN }, + { "move", MOVE }, + { "north", NORTH }, + { "of", OF }, + { "outline", OUTLINED }, + { "outlined", OUTLINED }, + { "plot", PLOT }, + { "print", PRINT }, + { "rad", RADIUS }, + { "radius", RADIUS }, + { "rand", RAND }, + { "reset", RESET }, + { "right", RIGHT }, + { "rjust", RJUST }, + { "same", SAME }, + { "sh", SH }, + { "shaded", SHADED }, + { "sin", SIN }, + { "solid", SOLID }, + { "south", SOUTH }, + { "spline", SPLINE }, + { "sprintf", SPRINTF }, + { "sqrt", SQRT }, + { "srand", SRAND }, + { "start", START }, + { "the", THE }, + { "then", THEN }, + { "thick", THICKNESS }, + { "thickness", THICKNESS }, + { "thru", THRU }, + { "to", TO }, + { "top", TOP }, + { "undef", UNDEF }, + { "until", UNTIL }, + { "up", UP }, + { "upper", UPPER }, + { "way", WAY }, + { "west", WEST }, + { "wid", WIDTH }, + { "width", WIDTH }, + { "with", WITH }, + { "xslanted", XSLANTED }, + { "yslanted", YSLANTED }, + }; + + const keyword *start = table; + const keyword *end = table + sizeof(table)/sizeof(table[0]); + while (start < end) { + // start <= target < end + const keyword *mid = start + (end - start)/2; + + int cmp = docmp(str, len, mid->name, strlen(mid->name)); + if (cmp == 0) + return mid->token; + if (cmp < 0) + end = mid; + else + start = mid + 1; + } + return 0; +} + +int get_token_after_dot(int c) +{ + // get_token deals with the case where c is a digit + switch (c) { + case 'h': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + context_buffer = ".ht"; + return DOT_HT; + } + else if (c == 'e') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'i') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'g') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'h') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + context_buffer = ".height"; + return DOT_HT; + } + input_stack::push_back('h'); + } + input_stack::push_back('g'); + } + input_stack::push_back('i'); + } + input_stack::push_back('e'); + } + input_stack::push_back('h'); + return '.'; + case 'x': + input_stack::get_char(); + context_buffer = ".x"; + return DOT_X; + case 'y': + input_stack::get_char(); + context_buffer = ".y"; + return DOT_Y; + case 'c': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'e') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'n') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'e') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'r') { + input_stack::get_char(); + context_buffer = ".center"; + return DOT_C; + } + input_stack::push_back('e'); + } + input_stack::push_back('t'); + } + input_stack::push_back('n'); + } + input_stack::push_back('e'); + } + context_buffer = ".c"; + return DOT_C; + case 'n': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'e') { + input_stack::get_char(); + context_buffer = ".ne"; + return DOT_NE; + } + else if (c == 'w') { + input_stack::get_char(); + context_buffer = ".nw"; + return DOT_NW; + } + else { + context_buffer = ".n"; + return DOT_N; + } + break; + case 'e': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'n') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'd') { + input_stack::get_char(); + context_buffer = ".end"; + return DOT_END; + } + input_stack::push_back('n'); + context_buffer = ".e"; + return DOT_E; + } + context_buffer = ".e"; + return DOT_E; + case 'w': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'i') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'd') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'h') { + input_stack::get_char(); + context_buffer = ".width"; + return DOT_WID; + } + input_stack::push_back('t'); + } + context_buffer = ".wid"; + return DOT_WID; + } + input_stack::push_back('i'); + } + context_buffer = ".w"; + return DOT_W; + case 's': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'e') { + input_stack::get_char(); + context_buffer = ".se"; + return DOT_SE; + } + else if (c == 'w') { + input_stack::get_char(); + context_buffer = ".sw"; + return DOT_SW; + } + else { + if (c == 't') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'a') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'r') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + context_buffer = ".start"; + return DOT_START; + } + input_stack::push_back('r'); + } + input_stack::push_back('a'); + } + input_stack::push_back('t'); + } + context_buffer = ".s"; + return DOT_S; + } + break; + case 't': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'o') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'p') { + input_stack::get_char(); + context_buffer = ".top"; + return DOT_N; + } + input_stack::push_back('o'); + } + context_buffer = ".t"; + return DOT_N; + case 'l': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'e') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'f') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + context_buffer = ".left"; + return DOT_W; + } + input_stack::push_back('f'); + } + input_stack::push_back('e'); + } + context_buffer = ".l"; + return DOT_W; + case 'r': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'a') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'd') { + input_stack::get_char(); + context_buffer = ".rad"; + return DOT_RAD; + } + input_stack::push_back('a'); + } + else if (c == 'i') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'g') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'h') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + context_buffer = ".right"; + return DOT_E; + } + input_stack::push_back('h'); + } + input_stack::push_back('g'); + } + input_stack::push_back('i'); + } + context_buffer = ".r"; + return DOT_E; + case 'b': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'o') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'o') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'm') { + input_stack::get_char(); + context_buffer = ".bottom"; + return DOT_S; + } + input_stack::push_back('o'); + } + input_stack::push_back('t'); + } + context_buffer = ".bot"; + return DOT_S; + } + input_stack::push_back('o'); + } + context_buffer = ".b"; + return DOT_S; + default: + context_buffer = '.'; + return '.'; + } +} + +int get_token(int lookup_flag) +{ + context_buffer.clear(); + for (;;) { + int n = 0; + int bol = input_stack::bol(); + int c = input_stack::get_char(); + if (bol && c == command_char) { + token_buffer.clear(); + token_buffer += c; + // the newline is not part of the token + for (;;) { + c = input_stack::peek_char(); + if (c == EOF || c == '\n') + break; + input_stack::get_char(); + token_buffer += char(c); + } + context_buffer = token_buffer; + return COMMAND_LINE; + } + switch (c) { + case EOF: + return EOF; + case ' ': + case '\t': + break; + case '\\': + { + int d = input_stack::peek_char(); + if (d != '\n') { + context_buffer = '\\'; + return '\\'; + } + input_stack::get_char(); + break; + } + case '#': + do { + c = input_stack::get_char(); + } while (c != '\n' && c != EOF); + if (c == '\n') + context_buffer = '\n'; + return c; + case '"': + context_buffer = '"'; + token_buffer.clear(); + for (;;) { + c = input_stack::get_char(); + if (c == '\\') { + context_buffer += '\\'; + c = input_stack::peek_char(); + if (c == '"') { + input_stack::get_char(); + token_buffer += '"'; + context_buffer += '"'; + } + else + token_buffer += '\\'; + } + else if (c == '\n') { + error("newline in string"); + break; + } + else if (c == EOF) { + error("missing '\"'"); + break; + } + else if (c == '"') { + context_buffer += '"'; + break; + } + else { + context_buffer += char(c); + token_buffer += char(c); + } + } + return TEXT; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int overflow = 0; + n = 0; + for (;;) { + if (n > (INT_MAX - 9)/10) { + overflow = 1; + break; + } + n *= 10; + n += c - '0'; + context_buffer += char(c); + c = input_stack::peek_char(); + if (c == EOF || !csdigit(c)) + break; + c = input_stack::get_char(); + } + token_double = n; + if (overflow) { + for (;;) { + token_double *= 10.0; + token_double += c - '0'; + context_buffer += char(c); + c = input_stack::peek_char(); + if (c == EOF || !csdigit(c)) + break; + c = input_stack::get_char(); + } + // if somebody asks for 1000000000000th, we will silently + // give them INT_MAXth + double temp = token_double; // work around gas 1.34/sparc bug + if (token_double > INT_MAX) + n = INT_MAX; + else + n = int(temp); + } + } + switch (c) { + case 'i': + case 'I': + context_buffer += char(c); + input_stack::get_char(); + return NUMBER; + case '.': + { + context_buffer += '.'; + input_stack::get_char(); + got_dot: + double factor = 1.0; + for (;;) { + c = input_stack::peek_char(); + if (c == EOF || !csdigit(c)) + break; + input_stack::get_char(); + context_buffer += char(c); + factor /= 10.0; + if (c != '0') + token_double += factor*(c - '0'); + } + if (c != 'e' && c != 'E') { + if (c == 'i' || c == 'I') { + context_buffer += char(c); + input_stack::get_char(); + } + return NUMBER; + } + } + // fall through + case 'e': + case 'E': + { + int echar = c; + input_stack::get_char(); + c = input_stack::peek_char(); + int sign = '+'; + if (c == '+' || c == '-') { + sign = c; + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == EOF || !csdigit(c)) { + input_stack::push_back(sign); + input_stack::push_back(echar); + return NUMBER; + } + context_buffer += char(echar); + context_buffer += char(sign); + } + else { + if (c == EOF || !csdigit(c)) { + input_stack::push_back(echar); + return NUMBER; + } + context_buffer += char(echar); + } + input_stack::get_char(); + context_buffer += char(c); + n = c - '0'; + for (;;) { + c = input_stack::peek_char(); + if (c == EOF || !csdigit(c)) + break; + input_stack::get_char(); + context_buffer += char(c); + n = n*10 + (c - '0'); + } + if (sign == '-') + n = -n; + if (c == 'i' || c == 'I') { + context_buffer += char(c); + input_stack::get_char(); + } + token_double *= pow(10.0, n); + return NUMBER; + } + case 'n': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'd') { + input_stack::get_char(); + token_int = n; + context_buffer += "nd"; + return ORDINAL; + } + input_stack::push_back('n'); + return NUMBER; + case 'r': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'd') { + input_stack::get_char(); + token_int = n; + context_buffer += "rd"; + return ORDINAL; + } + input_stack::push_back('r'); + return NUMBER; + case 't': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'h') { + input_stack::get_char(); + token_int = n; + context_buffer += "th"; + return ORDINAL; + } + input_stack::push_back('t'); + return NUMBER; + case 's': + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + token_int = n; + context_buffer += "st"; + return ORDINAL; + } + input_stack::push_back('s'); + return NUMBER; + default: + return NUMBER; + } + break; + case '\'': + { + c = input_stack::peek_char(); + if (c == 't') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == 'h') { + input_stack::get_char(); + context_buffer = "'th"; + return TH; + } + else + input_stack::push_back('t'); + } + context_buffer = "'"; + return '\''; + } + case '.': + { + c = input_stack::peek_char(); + if (c != EOF && csdigit(c)) { + n = 0; + token_double = 0.0; + context_buffer = '.'; + goto got_dot; + } + return get_token_after_dot(c); + } + case '<': + c = input_stack::peek_char(); + if (c == '-') { + input_stack::get_char(); + c = input_stack::peek_char(); + if (c == '>') { + input_stack::get_char(); + context_buffer = "<->"; + return DOUBLE_ARROW_HEAD; + } + context_buffer = "<-"; + return LEFT_ARROW_HEAD; + } + else if (c == '=') { + input_stack::get_char(); + context_buffer = "<="; + return LESSEQUAL; + } + context_buffer = "<"; + return '<'; + case '-': + c = input_stack::peek_char(); + if (c == '>') { + input_stack::get_char(); + context_buffer = "->"; + return RIGHT_ARROW_HEAD; + } + context_buffer = "-"; + return '-'; + case '!': + c = input_stack::peek_char(); + if (c == '=') { + input_stack::get_char(); + context_buffer = "!="; + return NOTEQUAL; + } + context_buffer = "!"; + return '!'; + case '>': + c = input_stack::peek_char(); + if (c == '=') { + input_stack::get_char(); + context_buffer = ">="; + return GREATEREQUAL; + } + context_buffer = ">"; + return '>'; + case '=': + c = input_stack::peek_char(); + if (c == '=') { + input_stack::get_char(); + context_buffer = "=="; + return EQUALEQUAL; + } + context_buffer = "="; + return '='; + case '&': + c = input_stack::peek_char(); + if (c == '&') { + input_stack::get_char(); + context_buffer = "&&"; + return ANDAND; + } + context_buffer = "&"; + return '&'; + case '|': + c = input_stack::peek_char(); + if (c == '|') { + input_stack::get_char(); + context_buffer = "||"; + return OROR; + } + context_buffer = "|"; + return '|'; + default: + if (c != EOF && csalpha(c)) { + token_buffer.clear(); + token_buffer = c; + for (;;) { + c = input_stack::peek_char(); + if (c == EOF || (!csalnum(c) && c != '_')) + break; + input_stack::get_char(); + token_buffer += char(c); + } + int tok = lookup_keyword(token_buffer.contents(), + token_buffer.length()); + if (tok != 0) { + context_buffer = token_buffer; + return tok; + } + char *def = 0; + if (lookup_flag) { + token_buffer += '\0'; + def = macro_table.lookup(token_buffer.contents()); + token_buffer.set_length(token_buffer.length() - 1); + if (def) { + if (c == '(') { + input_stack::get_char(); + interpolate_macro_with_args(def); + } + else + input_stack::push(new macro_input(def)); + } + } + if (!def) { + context_buffer = token_buffer; + if (csupper(token_buffer[0])) + return LABEL; + else + return VARIABLE; + } + } + else { + context_buffer = char(c); + return (unsigned char)c; + } + break; + } + } +} + +int get_delimited() +{ + token_buffer.clear(); + int c = input_stack::get_char(); + while (c == ' ' || c == '\t' || c == '\n') + c = input_stack::get_char(); + if (c == EOF) { + lex_error("missing delimiter"); + return 0; + } + context_buffer = char(c); + int had_newline = 0; + int start = c; + int level = 0; + enum { NORMAL, IN_STRING, IN_STRING_QUOTED, DELIM_END } state = NORMAL; + for (;;) { + c = input_stack::get_char(); + if (c == EOF) { + lex_error("missing closing delimiter"); + return 0; + } + if (c == '\n') + had_newline = 1; + else if (!had_newline) + context_buffer += char(c); + switch (state) { + case NORMAL: + if (start == '{') { + if (c == '{') { + level++; + break; + } + if (c == '}') { + if (--level < 0) + state = DELIM_END; + break; + } + } + else { + if (c == start) { + state = DELIM_END; + break; + } + } + if (c == '"') + state = IN_STRING; + break; + case IN_STRING_QUOTED: + if (c == '\n') + state = NORMAL; + else + state = IN_STRING; + break; + case IN_STRING: + if (c == '"' || c == '\n') + state = NORMAL; + else if (c == '\\') + state = IN_STRING_QUOTED; + break; + case DELIM_END: + // This case it just to shut cfront 2.0 up. + default: + assert(0); + } + if (state == DELIM_END) + break; + token_buffer += c; + } + return 1; +} + +void do_define() +{ + int t = get_token(0); // do not expand what we are defining + if (t != VARIABLE && t != LABEL) { + lex_error("can only define variable or placename"); + return; + } + token_buffer += '\0'; + string nm = token_buffer; + const char *name = nm.contents(); + if (!get_delimited()) + return; + token_buffer += '\0'; + macro_table.define(name, strsave(token_buffer.contents())); +} + +void do_undef() +{ + int t = get_token(0); // do not expand what we are undefining + if (t != VARIABLE && t != LABEL) { + lex_error("can only define variable or placename"); + return; + } + token_buffer += '\0'; + macro_table.define(token_buffer.contents(), 0); +} + + +class for_input : public input { + char *var; + char *body; + double from; + double to; + int by_is_multiplicative; + double by; + const char *p; + int done_newline; +public: + for_input(char *, double, double, int, double, char *); + ~for_input(); + int get(); + int peek(); +}; + +for_input::for_input(char *vr, double f, double t, + int bim, double b, char *bd) +: var(vr), body(bd), from(f), to(t), by_is_multiplicative(bim), by(b), + p(body), done_newline(0) +{ +} + +for_input::~for_input() +{ + free(var); + free(body); +} + +int for_input::get() +{ + if (p == 0) + return EOF; + for (;;) { + if (*p != '\0') + return (unsigned char)*p++; + if (!done_newline) { + done_newline = 1; + return '\n'; + } + double val; + if (!lookup_variable(var, &val)) { + lex_error("body of 'for' terminated enclosing block"); + return EOF; + } + if (by_is_multiplicative) + val *= by; + else + val += by; + define_variable(var, val); + if ((from <= to && val > to) + || (from >= to && val < to)) { + p = 0; + return EOF; + } + p = body; + done_newline = 0; + } +} + +int for_input::peek() +{ + if (p == 0) + return EOF; + if (*p != '\0') + return (unsigned char)*p; + if (!done_newline) + return '\n'; + double val; + if (!lookup_variable(var, &val)) + return EOF; + if (by_is_multiplicative) { + if (val * by > to) + return EOF; + } + else { + if ((from <= to && val + by > to) + || (from >= to && val + by < to)) + return EOF; + } + if (*body == '\0') + return EOF; + return (unsigned char)*body; +} + +void do_for(char *var, double from, double to, int by_is_multiplicative, + double by, char *body) +{ + define_variable(var, from); + if ((by_is_multiplicative && by <= 0) + || (by > 0 && from > to) + || (by < 0 && from < to)) + return; + input_stack::push(new for_input(var, from, to, + by_is_multiplicative, by, body)); +} + + +void do_copy(const char *filename) +{ + errno = 0; + FILE *fp = fopen(filename, "r"); + if (fp == 0) { + lex_error("can't open '%1': %2", filename, strerror(errno)); + return; + } + input_stack::push(new file_input(fp, filename)); +} + +class copy_thru_input : public input { + int done; + char *body; + char *until; + const char *p; + const char *ap; + int argv[MAX_ARG]; + int argc; + string line; + int get_line(); + virtual int inget() = 0; +public: + copy_thru_input(const char *b, const char *u); + ~copy_thru_input(); + int get(); + int peek(); +}; + +class copy_file_thru_input : public copy_thru_input { + input *in; +public: + copy_file_thru_input(input *, const char *b, const char *u); + ~copy_file_thru_input(); + int inget(); +}; + +copy_file_thru_input::copy_file_thru_input(input *i, const char *b, + const char *u) +: copy_thru_input(b, u), in(i) +{ +} + +copy_file_thru_input::~copy_file_thru_input() +{ + delete in; +} + +int copy_file_thru_input::inget() +{ + if (!in) + return EOF; + else + return in->get(); +} + +class copy_rest_thru_input : public copy_thru_input { +public: + copy_rest_thru_input(const char *, const char *u); + int inget(); +}; + +copy_rest_thru_input::copy_rest_thru_input(const char *b, const char *u) +: copy_thru_input(b, u) +{ +} + +int copy_rest_thru_input::inget() +{ + while (next != 0) { + int c = next->get(); + if (c != EOF) + return c; + if (next->next == 0) + return EOF; + input *tem = next; + next = next->next; + delete tem; + } + return EOF; + +} + +copy_thru_input::copy_thru_input(const char *b, const char *u) +: done(0) +{ + ap = 0; + body = process_body(b); + p = 0; + until = strsave(u); +} + + +copy_thru_input::~copy_thru_input() +{ + delete[] body; + delete[] until; +} + +int copy_thru_input::get() +{ + if (ap) { + if (*ap != '\0') + return (unsigned char)*ap++; + ap = 0; + } + for (;;) { + if (p == 0) { + if (!get_line()) + break; + p = body; + } + if (*p == '\0') { + p = 0; + return '\n'; + } + while ((unsigned char)*p >= ARG1 + && (unsigned char)*p <= ARG1 + MAX_ARG - 1) { + int i = (unsigned char)*p++ - ARG1; + if (i < argc && line[argv[i]] != '\0') { + ap = line.contents() + argv[i]; + return (unsigned char)*ap++; + } + } + if (*p != '\0') + return (unsigned char)*p++; + } + return EOF; +} + +int copy_thru_input::peek() +{ + if (ap) { + if (*ap != '\0') + return (unsigned char)*ap; + ap = 0; + } + for (;;) { + if (p == 0) { + if (!get_line()) + break; + p = body; + } + if (*p == '\0') + return '\n'; + while ((unsigned char)*p >= ARG1 + && (unsigned char)*p <= ARG1 + MAX_ARG - 1) { + int i = (unsigned char)*p++ - ARG1; + if (i < argc && line[argv[i]] != '\0') { + ap = line.contents() + argv[i]; + return (unsigned char)*ap; + } + } + if (*p != '\0') + return (unsigned char)*p; + } + return EOF; +} + +int copy_thru_input::get_line() +{ + if (done) + return 0; + line.clear(); + argc = 0; + int c = inget(); + for (;;) { + while (c == ' ') + c = inget(); + if (c == EOF || c == '\n') + break; + if (argc == MAX_ARG) { + do { + c = inget(); + } while (c != '\n' && c != EOF); + break; + } + argv[argc++] = line.length(); + do { + line += char(c); + c = inget(); + } while (c != ' ' && c != '\n'); + line += '\0'; + } + if (until != 0 && argc > 0 && strcmp(&line[argv[0]], until) == 0) { + done = 1; + return 0; + } + return argc > 0 || c == '\n'; +} + +class simple_file_input : public input { + const char *filename; + int lineno; + FILE *fp; +public: + simple_file_input(FILE *, const char *); + ~simple_file_input(); + int get(); + int peek(); + int get_location(const char **, int *); +}; + +simple_file_input::simple_file_input(FILE *p, const char *s) +: filename(s), lineno(1), fp(p) +{ +} + +simple_file_input::~simple_file_input() +{ + // don't delete the filename + fclose(fp); +} + +int simple_file_input::get() +{ + int c = getc(fp); + while (is_invalid_input_char(c)) { + error("invalid input character code %1", c); + c = getc(fp); + } + if (c == '\n') + lineno++; + return c; +} + +int simple_file_input::peek() +{ + int c = getc(fp); + while (is_invalid_input_char(c)) { + error("invalid input character code %1", c); + c = getc(fp); + } + if (c != EOF) + ungetc(c, fp); + return c; +} + +int simple_file_input::get_location(const char **fnp, int *lnp) +{ + *fnp = filename; + *lnp = lineno; + return 1; +} + + +void copy_file_thru(const char *filename, const char *body, const char *until) +{ + errno = 0; + FILE *fp = fopen(filename, "r"); + if (fp == 0) { + lex_error("can't open '%1': %2", filename, strerror(errno)); + return; + } + input *in = new copy_file_thru_input(new simple_file_input(fp, filename), + body, until); + input_stack::push(in); +} + +void copy_rest_thru(const char *body, const char *until) +{ + input_stack::push(new copy_rest_thru_input(body, until)); +} + +void push_body(const char *s) +{ + input_stack::push(new char_input('\n')); + input_stack::push(new macro_input(s)); +} + +int delim_flag = 0; + +char *get_thru_arg() +{ + int c = input_stack::peek_char(); + while (c == ' ') { + input_stack::get_char(); + c = input_stack::peek_char(); + } + if (c != EOF && csalpha(c)) { + // looks like a macro + input_stack::get_char(); + token_buffer = c; + for (;;) { + c = input_stack::peek_char(); + if (c == EOF || (!csalnum(c) && c != '_')) + break; + input_stack::get_char(); + token_buffer += char(c); + } + context_buffer = token_buffer; + token_buffer += '\0'; + char *def = macro_table.lookup(token_buffer.contents()); + if (def) + return strsave(def); + // I guess it wasn't a macro after all; so push the macro name back. + // -2 because we added a '\0' + for (int i = token_buffer.length() - 2; i >= 0; i--) + input_stack::push_back(token_buffer[i]); + } + if (get_delimited()) { + token_buffer += '\0'; + return strsave(token_buffer.contents()); + } + else + return 0; +} + +int lookahead_token = -1; +string old_context_buffer; + +void do_lookahead() +{ + if (lookahead_token == -1) { + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + } +} + +int yylex() +{ + if (delim_flag) { + assert(lookahead_token == -1); + if (delim_flag == 2) { + if ((yylval.str = get_thru_arg()) != 0) + return DELIMITED; + else + return 0; + } + else { + if (get_delimited()) { + token_buffer += '\0'; + yylval.str = strsave(token_buffer.contents()); + return DELIMITED; + } + else + return 0; + } + } + for (;;) { + int t; + if (lookahead_token >= 0) { + t = lookahead_token; + lookahead_token = -1; + } + else + t = get_token(1); + switch (t) { + case '\n': + return ';'; + case EOF: + return 0; + case DEFINE: + do_define(); + break; + case UNDEF: + do_undef(); + break; + case ORDINAL: + yylval.n = token_int; + return t; + case NUMBER: + yylval.x = token_double; + return t; + case COMMAND_LINE: + case TEXT: + token_buffer += '\0'; + if (!input_stack::get_location(&yylval.lstr.filename, + &yylval.lstr.lineno)) { + yylval.lstr.filename = 0; + yylval.lstr.lineno = -1; + } + yylval.lstr.str = strsave(token_buffer.contents()); + return t; + case LABEL: + case VARIABLE: + token_buffer += '\0'; + yylval.str = strsave(token_buffer.contents()); + return t; + case LEFT: + // change LEFT to LEFT_CORNER when followed by OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token == OF) + return LEFT_CORNER; + else + return t; + case RIGHT: + // change RIGHT to RIGHT_CORNER when followed by OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token == OF) + return RIGHT_CORNER; + else + return t; + case UPPER: + // recognise UPPER only before LEFT or RIGHT + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != LEFT && lookahead_token != RIGHT) { + yylval.str = strsave("upper"); + return VARIABLE; + } + else + return t; + case LOWER: + // recognise LOWER only before LEFT or RIGHT + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != LEFT && lookahead_token != RIGHT) { + yylval.str = strsave("lower"); + return VARIABLE; + } + else + return t; + case NORTH: + // recognise NORTH only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("north"); + return VARIABLE; + } + else + return t; + case SOUTH: + // recognise SOUTH only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("south"); + return VARIABLE; + } + else + return t; + case EAST: + // recognise EAST only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("east"); + return VARIABLE; + } + else + return t; + case WEST: + // recognise WEST only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("west"); + return VARIABLE; + } + else + return t; + case TOP: + // recognise TOP only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("top"); + return VARIABLE; + } + else + return t; + case BOTTOM: + // recognise BOTTOM only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("bottom"); + return VARIABLE; + } + else + return t; + case CENTER: + // recognise CENTER only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("center"); + return VARIABLE; + } + else + return t; + case START: + // recognise START only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("start"); + return VARIABLE; + } + else + return t; + case END: + // recognise END only before OF + old_context_buffer = context_buffer; + lookahead_token = get_token(1); + if (lookahead_token != OF) { + yylval.str = strsave("end"); + return VARIABLE; + } + else + return t; + default: + return t; + } + } +} + +void lex_error(const char *message, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + const char *filename; + int lineno; + if (!input_stack::get_location(&filename, &lineno)) + error(message, arg1, arg2, arg3); + else + error_with_file_and_line(filename, lineno, message, arg1, arg2, arg3); +} + +void lex_warning(const char *message, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + const char *filename; + int lineno; + if (!input_stack::get_location(&filename, &lineno)) + warning(message, arg1, arg2, arg3); + else + warning_with_file_and_line(filename, lineno, message, arg1, arg2, arg3); +} + +void yyerror(const char *s) +{ + const char *filename; + int lineno; + const char *context = 0; + if (lookahead_token == -1) { + if (context_buffer.length() > 0) { + context_buffer += '\0'; + context = context_buffer.contents(); + } + } + else { + if (old_context_buffer.length() > 0) { + old_context_buffer += '\0'; + context = old_context_buffer.contents(); + } + } + if (!input_stack::get_location(&filename, &lineno)) { + if (context) { + if (context[0] == '\n' && context[1] == '\0') + error("%1 before newline", s); + else + error("%1 before '%2'", s, context); + } + else + error("%1 at end of picture", s); + } + else { + if (context) { + if (context[0] == '\n' && context[1] == '\0') + error_with_file_and_line(filename, lineno, "%1 before newline", s); + else + error_with_file_and_line(filename, lineno, "%1 before '%2'", + s, context); + } + else + error_with_file_and_line(filename, lineno, "%1 at end of picture", s); + } +} + diff --git a/src/preproc/pic/main.cpp b/src/preproc/pic/main.cpp new file mode 100644 index 0000000..f7e194f --- /dev/null +++ b/src/preproc/pic/main.cpp @@ -0,0 +1,664 @@ +/* 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 "pic.h" + +extern int yyparse(); +extern "C" const char *Version_string; + +output *out; +char *graphname; // the picture box name in TeX mode + +bool want_flyback = false; +// groff pic supports '.PY' to work around mm package stepping on 'PF'. +bool want_alternate_flyback = false; +int zero_length_line_flag = 0; +// Non-zero means we're using a groff driver. +int driver_extension_flag = 1; +int compatible_flag = 0; +int safer_flag = 1; +int command_char = '.'; // the character that introduces lines + // that should be passed through transparently +static int lf_flag = 1; // non-zero if we should attempt to understand + // lines beginning with '.lf' + +// Non-zero means a parse error was encountered. +static int had_parse_error = 0; + +void do_file(const char *filename); + +class top_input : public input { + FILE *fp; + int bol; + int eof; + int push_back[3]; + int start_lineno; +public: + top_input(FILE *); + int get(); + int peek(); + int get_location(const char **, int *); +}; + +top_input::top_input(FILE *p) : fp(p), bol(1), eof(0) +{ + push_back[0] = push_back[1] = push_back[2] = EOF; + start_lineno = current_lineno; +} + +int top_input::get() +{ + if (eof) + return EOF; + if (push_back[2] != EOF) { + int c = push_back[2]; + push_back[2] = EOF; + return c; + } + else if (push_back[1] != EOF) { + int c = push_back[1]; + push_back[1] = EOF; + return c; + } + else if (push_back[0] != EOF) { + int c = push_back[0]; + push_back[0] = EOF; + return c; + } + int c = getc(fp); + while (is_invalid_input_char(c)) { + error("invalid input character code %1", int(c)); + c = getc(fp); + bol = 0; + } + if (bol && c == '.') { + c = getc(fp); + if (c == 'P') { + c = getc(fp); + if (c == 'E' || c == 'F' || c == 'Y') { + int d = getc(fp); + if (d != EOF) + ungetc(d, fp); + if (d == EOF || d == ' ' || d == '\n' || compatible_flag) { + eof = 1; + want_flyback = (c == 'F'); + want_alternate_flyback = (c == 'Y'); + return EOF; + } + push_back[0] = c; + push_back[1] = 'P'; + return '.'; + } + if (c == 'S') { + c = getc(fp); + if (c != EOF) + ungetc(c, fp); + if (c == EOF || c == ' ' || c == '\n' || compatible_flag) { + error("nested .PS"); + eof = 1; + return EOF; + } + push_back[0] = 'S'; + push_back[1] = 'P'; + return '.'; + } + if (c != EOF) + ungetc(c, fp); + push_back[0] = 'P'; + return '.'; + } + else { + if (c != EOF) + ungetc(c, fp); + return '.'; + } + } + if (c == '\n') { + bol = 1; + current_lineno++; + return '\n'; + } + bol = 0; + if (c == EOF) { + eof = 1; + error("end of file before .PE, .PF, or .PY"); + error_with_file_and_line(current_filename, start_lineno - 1, + ".PS was here"); + } + return c; +} + +int top_input::peek() +{ + if (eof) + return EOF; + if (push_back[2] != EOF) + return push_back[2]; + if (push_back[1] != EOF) + return push_back[1]; + if (push_back[0] != EOF) + return push_back[0]; + int c = getc(fp); + while (is_invalid_input_char(c)) { + error("invalid input character code %1", int(c)); + c = getc(fp); + bol = 0; + } + if (bol && c == '.') { + c = getc(fp); + if (c == 'P') { + c = getc(fp); + if (c == 'E' || c == 'F' || c == 'Y') { + int d = getc(fp); + if (d != EOF) + ungetc(d, fp); + if (d == EOF || d == ' ' || d == '\n' || compatible_flag) { + eof = 1; + want_flyback = (c == 'F'); + want_alternate_flyback = (c == 'Y'); + return EOF; + } + push_back[0] = c; + push_back[1] = 'P'; + push_back[2] = '.'; + return '.'; + } + if (c == 'S') { + c = getc(fp); + if (c != EOF) + ungetc(c, fp); + if (c == EOF || c == ' ' || c == '\n' || compatible_flag) { + error("nested .PS"); + eof = 1; + return EOF; + } + push_back[0] = 'S'; + push_back[1] = 'P'; + push_back[2] = '.'; + return '.'; + } + if (c != EOF) + ungetc(c, fp); + push_back[0] = 'P'; + push_back[1] = '.'; + return '.'; + } + else { + if (c != EOF) + ungetc(c, fp); + push_back[0] = '.'; + return '.'; + } + } + if (c != EOF) + ungetc(c, fp); + if (c == '\n') + return '\n'; + return c; +} + +int top_input::get_location(const char **filenamep, int *linenop) +{ + *filenamep = current_filename; + *linenop = current_lineno; + return 1; +} + +void do_picture(FILE *fp) +{ + want_flyback = false; + int c; + if (!graphname) + free(graphname); + graphname = strsave("graph"); // default picture name in TeX mode + while ((c = getc(fp)) == ' ') + ; + if (c == '<') { + string filename; + while ((c = getc(fp)) == ' ') + ; + while (c != EOF && c != ' ' && c != '\n') { + filename += char(c); + c = getc(fp); + } + if (c == ' ') { + do { + c = getc(fp); + } while (c != EOF && c != '\n'); + } + if (c == '\n') + current_lineno++; + if (filename.length() == 0) + error("missing filename after '<'"); + else { + filename += '\0'; + const char *old_filename = current_filename; + int old_lineno = current_lineno; + // filenames must be permanent + do_file(strsave(filename.contents())); + current_filename = old_filename; + current_lineno = old_lineno; + } + out->set_location(current_filename, current_lineno); + } + else { + out->set_location(current_filename, current_lineno); + string start_line; + while (c != EOF) { + if (c == '\n') { + current_lineno++; + break; + } + start_line += c; + c = getc(fp); + } + if (c == EOF) + return; + start_line += '\0'; + double wid, ht; + switch (sscanf(&start_line[0], "%lf %lf", &wid, &ht)) { + case 1: + ht = 0.0; + break; + case 2: + break; + default: + ht = wid = 0.0; + break; + } + out->set_desired_width_height(wid, ht); + out->set_args(start_line.contents()); + lex_init(new top_input(fp)); + if (yyparse()) { + had_parse_error = 1; + lex_error("giving up on this picture"); + } + parse_cleanup(); + lex_cleanup(); + + // skip the rest of the .PE/.PF/.PY line + while ((c = getc(fp)) != EOF && c != '\n') + ; + if (c == '\n') + current_lineno++; + out->set_location(current_filename, current_lineno); + } +} + +void do_file(const char *filename) +{ + FILE *fp; + if (strcmp(filename, "-") == 0) + fp = stdin; + else { + errno = 0; + fp = fopen(filename, "r"); + if (fp == 0) { + delete out; + fatal("can't open '%1': %2", filename, strerror(errno)); + } + } + string fn(filename); + fn += '\0'; + normalize_for_lf(fn); + current_filename = fn.contents(); + out->set_location(current_filename, 1); + current_lineno = 1; + enum { START, MIDDLE, HAD_DOT, HAD_P, HAD_PS, HAD_l, HAD_lf } state = START; + for (;;) { + int c = getc(fp); + while (is_invalid_input_char(c)) { + error("invalid input character code %1", int(c)); + c = getc(fp); + } + if (c == EOF) + break; + switch (state) { + case START: + if (c == '.') + state = HAD_DOT; + else { + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case MIDDLE: + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + break; + case HAD_DOT: + if (c == 'P') + state = HAD_P; + else if (lf_flag && c == 'l') + state = HAD_l; + else { + putchar('.'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_P: + if (c == 'S') + state = HAD_PS; + else { + putchar('.'); + putchar('P'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_PS: + if (c == ' ' || c == '\n' || compatible_flag) { + ungetc(c, fp); + do_picture(fp); + state = START; + } + else { + fputs(".PS", stdout); + putchar(c); + state = MIDDLE; + } + break; + case HAD_l: + if (c == 'f') + state = HAD_lf; + else { + putchar('.'); + putchar('l'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_lf: + if (c == ' ' || c == '\n' || compatible_flag) { + string line; + while (c != EOF) { + line += c; + if (c == '\n') { + current_lineno++; + break; + } + c = getc(fp); + } + line += '\0'; + interpret_lf_args(line.contents()); + printf(".lf%s", line.contents()); + state = START; + } + else { + fputs(".lf", stdout); + putchar(c); + state = MIDDLE; + } + break; + default: + assert(0); + } + } + switch (state) { + case START: + break; + case MIDDLE: + putchar('\n'); + break; + case HAD_DOT: + fputs(".\n", stdout); + break; + case HAD_P: + fputs(".P\n", stdout); + break; + case HAD_PS: + fputs(".PS\n", stdout); + break; + case HAD_l: + fputs(".l\n", stdout); + break; + case HAD_lf: + fputs(".lf\n", stdout); + break; + } + if (fp != stdin) + fclose(fp); +} + +#ifdef FIG_SUPPORT +void do_whole_file(const char *filename) +{ + // Do not set current_filename. + FILE *fp; + if (strcmp(filename, "-") == 0) + fp = stdin; + else { + errno = 0; + fp = fopen(filename, "r"); + if (fp == 0) + fatal("can't open '%1': %2", filename, strerror(errno)); + } + lex_init(new file_input(fp, filename)); + if (yyparse()) + had_parse_error = 1; + parse_cleanup(); + lex_cleanup(); +} +#endif + +void usage(FILE *stream) +{ + fprintf(stream, "usage: %s [-CnSU] [file ...]\n", program_name); +#ifdef TEX_SUPPORT + fprintf(stream, "usage: %s -t [-cCSUz] [file ...]\n", program_name); +#endif +#ifdef FIG_SUPPORT + fprintf(stream, "usage: %s -f [-v] [file]\n", program_name); +#endif + fprintf(stream, "usage: %s {-v | --version}\n", program_name); + fprintf(stream, "usage: %s --help\n", program_name); +} + +#if defined(__MSDOS__) || defined(__EMX__) +static char *fix_program_name(char *arg, char *dflt) +{ + if (!arg) + return dflt; + char *prog = strchr(arg, '\0'); + for (;;) { + if (prog == arg) + break; + --prog; + if (strchr("\\/:", *prog)) { + prog++; + break; + } + } + char *ext = strchr(prog, '.'); + if (ext) + *ext = '\0'; + for (char *p = prog; *p; p++) + if ('A' <= *p && *p <= 'Z') + *p = 'a' + (*p - 'A'); + return prog; +} +#endif /* __MSDOS__ || __EMX__ */ + +int main(int argc, char **argv) +{ + setlocale(LC_NUMERIC, "C"); +#if defined(__MSDOS__) || defined(__EMX__) + argv[0] = fix_program_name(argv[0], "pic"); +#endif /* __MSDOS__ || __EMX__ */ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int opt; +#ifdef TEX_SUPPORT + int tex_flag = 0; + int tpic_flag = 0; +#endif +#ifdef FIG_SUPPORT + int whole_file_flag = 0; + int fig_flag = 0; +#endif + 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, "T:CDSUtcvnxzpf", long_options, NULL)) + != EOF) + switch (opt) { + case 'C': + compatible_flag = 1; + break; + case 'D': + case 'T': + break; + case 'S': + safer_flag = 1; + break; + case 'U': + safer_flag = 0; + break; + case 'f': +#ifdef FIG_SUPPORT + whole_file_flag++; + fig_flag++; +#else + fatal("fig support not included"); +#endif + break; + case 'n': + driver_extension_flag = 0; + break; + case 'p': + case 'x': + warning("-%1 option is obsolete", char(opt)); + break; + case 't': +#ifdef TEX_SUPPORT + tex_flag++; +#else + fatal("TeX support not included"); +#endif + break; + case 'c': +#ifdef TEX_SUPPORT + tpic_flag++; +#else + fatal("TeX support not included"); +#endif + break; + case 'v': + { + printf("GNU pic (groff) version %s\n", Version_string); + exit(0); + break; + } + case 'z': + // zero length lines will be printed as dots + zero_length_line_flag++; + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + parse_init(); +#ifdef TEX_SUPPORT + if (tpic_flag) { + out = make_tpic_output(); + lf_flag = 0; + } + else if (tex_flag) { + out = make_tex_output(); + command_char = '\\'; + lf_flag = 0; + } + else +#endif +#ifdef FIG_SUPPORT + if (fig_flag) + out = make_fig_output(); + else +#endif + { + out = make_troff_output(); + printf(".do if !dPS .ds PS\n" + ".do if !dPE .ds PE\n" + ".do if !dPF .ds PF\n" + ".do if !dPY .ds PY\n"); + } +#ifdef FIG_SUPPORT + if (whole_file_flag) { + if (optind >= argc) + do_whole_file("-"); + else if (argc - optind > 1) { + usage(stderr); + exit(1); + } else + do_whole_file(argv[optind]); + } + else { +#endif + if (optind >= argc) + do_file("-"); + else + for (int i = optind; i < argc; i++) + do_file(argv[i]); +#ifdef FIG_SUPPORT + } +#endif + delete out; + if (ferror(stdout) || fflush(stdout) < 0) + fatal("output error"); + return had_parse_error; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/pic/object.cpp b/src/preproc/pic/object.cpp new file mode 100644 index 0000000..e207fb1 --- /dev/null +++ b/src/preproc/pic/object.cpp @@ -0,0 +1,2079 @@ +// -*- 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 "pic.h" +#include "ptable.h" +#include "object.h" + +void print_object_list(object *); + +line_type::line_type() +: type(solid), thickness(1.0) +{ +} + +output::output() : args(0), desired_height(0.0), desired_width(0.0) +{ +} + +output::~output() +{ + delete[] args; +} + +void output::set_desired_width_height(double wid, double ht) +{ + desired_width = wid; + desired_height = ht; +} + +void output::set_args(const char *s) +{ + delete[] args; + if (s == 0 || *s == '\0') + args = 0; + else + args = strsave(s); +} + +int output::supports_filled_polygons() +{ + return 0; +} + +void output::begin_block(const position &, const position &) +{ +} + +void output::end_block() +{ +} + +double output::compute_scale(double sc, const position &ll, const position &ur) +{ + distance dim = ur - ll; + if (desired_width != 0.0 || desired_height != 0.0) { + sc = 0.0; + if (desired_width != 0.0) { + if (dim.x == 0.0) + error("width specified for picture with zero width"); + else + sc = dim.x/desired_width; + } + if (desired_height != 0.0) { + if (dim.y == 0.0) + error("height specified for picture with zero height"); + else { + double tem = dim.y/desired_height; + if (tem > sc) + sc = tem; + } + } + return sc == 0.0 ? 1.0 : sc; + } + else { + if (sc <= 0.0) + sc = 1.0; + distance sdim = dim/sc; + double max_width = 0.0; + lookup_variable("maxpswid", &max_width); + double max_height = 0.0; + lookup_variable("maxpsht", &max_height); + if ((max_width > 0.0 && sdim.x > max_width) + || (max_height > 0.0 && sdim.y > max_height)) { + double xscale = dim.x/max_width; + double yscale = dim.y/max_height; + return xscale > yscale ? xscale : yscale; + } + else + return sc; + } +} + +position::position(const place &pl) +{ + if (pl.obj != 0) { + // Use two statements to work around bug in SGI C++. + object *tem = pl.obj; + *this = tem->origin(); + } + else { + x = pl.x; + y = pl.y; + } +} + +position::position() : x(0.0), y(0.0) +{ +} + +position::position(double a, double b) : x(a), y(b) +{ +} + + +int operator==(const position &a, const position &b) +{ + return a.x == b.x && a.y == b.y; +} + +int operator!=(const position &a, const position &b) +{ + return a.x != b.x || a.y != b.y; +} + +position &position::operator+=(const position &a) +{ + x += a.x; + y += a.y; + return *this; +} + +position &position::operator-=(const position &a) +{ + x -= a.x; + y -= a.y; + return *this; +} + +position &position::operator*=(double a) +{ + x *= a; + y *= a; + return *this; +} + +position &position::operator/=(double a) +{ + x /= a; + y /= a; + return *this; +} + +position operator-(const position &a) +{ + return position(-a.x, -a.y); +} + +position operator+(const position &a, const position &b) +{ + return position(a.x + b.x, a.y + b.y); +} + +position operator-(const position &a, const position &b) +{ + return position(a.x - b.x, a.y - b.y); +} + +position operator/(const position &a, double n) +{ + return position(a.x/n, a.y/n); +} + +position operator*(const position &a, double n) +{ + return position(a.x*n, a.y*n); +} + +// dot product + +double operator*(const position &a, const position &b) +{ + return a.x*b.x + a.y*b.y; +} + +double hypot(const position &a) +{ + return groff_hypot(a.x, a.y); +} + +struct arrow_head_type { + double height; + double width; + int solid; +}; + +void draw_arrow(const position &pos, const distance &dir, + const arrow_head_type &aht, const line_type <, + char *outline_color_for_fill) +{ + double hyp = hypot(dir); + if (hyp == 0.0) { + error("cannot draw arrow on object with zero length"); + return; + } + position base = -dir; + base *= aht.height/hyp; + position n(dir.y, -dir.x); + n *= aht.width/(hyp*2.0); + line_type slt = lt; + slt.type = line_type::solid; + if (aht.solid && out->supports_filled_polygons()) { + position v[3]; + v[0] = pos; + v[1] = pos + base + n; + v[2] = pos + base - n; + // fill with outline color + out->set_color(outline_color_for_fill, outline_color_for_fill); + // make stroke thin to avoid arrow sticking + slt.thickness = 0.1; + out->polygon(v, 3, slt, 1); + } + else { + // use two line segments to avoid arrow sticking + out->line(pos + base - n, &pos, 1, slt); + out->line(pos + base + n, &pos, 1, slt); + } +} + +object::object() : prev(0), next(0) +{ +} + +object::~object() +{ +} + +void object::move_by(const position &) +{ +} + +void object::print() +{ +} + +void object::print_text() +{ +} + +int object::blank() +{ + return 0; +} + +struct bounding_box { + int blank; + position ll; + position ur; + + bounding_box(); + void encompass(const position &); +}; + +bounding_box::bounding_box() +: blank(1) +{ +} + +void bounding_box::encompass(const position &pos) +{ + if (blank) { + ll = pos; + ur = pos; + blank = 0; + } + else { + if (pos.x < ll.x) + ll.x = pos.x; + if (pos.y < ll.y) + ll.y = pos.y; + if (pos.x > ur.x) + ur.x = pos.x; + if (pos.y > ur.y) + ur.y = pos.y; + } +} + +void object::update_bounding_box(bounding_box *) +{ +} + +position object::origin() +{ + return position(0.0,0.0); +} + +position object::north() +{ + return origin(); +} + +position object::south() +{ + return origin(); +} + +position object::east() +{ + return origin(); +} + +position object::west() +{ + return origin(); +} + +position object::north_east() +{ + return origin(); +} + +position object::north_west() +{ + return origin(); +} + +position object::south_east() +{ + return origin(); +} + +position object::south_west() +{ + return origin(); +} + +position object::start() +{ + return origin(); +} + +position object::end() +{ + return origin(); +} + +position object::center() +{ + return origin(); +} + +double object::width() +{ + return 0.0; +} + +double object::radius() +{ + return 0.0; +} + +double object::height() +{ + return 0.0; +} + +place *object::find_label(const char *) +{ + return 0; +} + +segment::segment(const position &a, int n, segment *p) +: is_absolute(n), pos(a), next(p) +{ +} + +text_item::text_item(char *t, const char *fn, int ln) +: next(0), text(t), filename(fn), lineno(ln) +{ + adj.h = CENTER_ADJUST; + adj.v = NONE_ADJUST; +} + +text_item::~text_item() +{ + delete[] text; +} + +object_spec::object_spec(object_type t) : type(t) +{ + flags = 0; + tbl = 0; + segment_list = 0; + segment_width = segment_height = 0.0; + segment_is_absolute = 0; + text = 0; + shaded = 0; + xslanted = 0; + yslanted = 0; + outlined = 0; + with = 0; + dir = RIGHT_DIRECTION; +} + +object_spec::~object_spec() +{ + delete tbl; + while (segment_list != 0) { + segment *tem = segment_list; + segment_list = segment_list->next; + delete tem; + } + object *p = oblist.head; + while (p != 0) { + object *tem = p; + p = p->next; + delete tem; + } + while (text != 0) { + text_item *tem = text; + text = text->next; + delete tem; + } + delete with; + delete[] shaded; + delete[] outlined; +} + +class command_object : public object { + char *s; + const char *filename; + int lineno; +public: + command_object(char *, const char *, int); + ~command_object(); + object_type type() { return OTHER_OBJECT; } + void print(); +}; + +command_object::command_object(char *p, const char *fn, int ln) +: s(p), filename(fn), lineno(ln) +{ +} + +command_object::~command_object() +{ + delete[] s; +} + +void command_object::print() +{ + out->command(s, filename, lineno); +} + +object *make_command_object(char *s, const char *fn, int ln) +{ + return new command_object(s, fn, ln); +} + +class mark_object : public object { +public: + mark_object(); + object_type type(); +}; + +object *make_mark_object() +{ + return new mark_object(); +} + +mark_object::mark_object() +{ +} + +object_type mark_object::type() +{ + return MARK_OBJECT; +} + +object_list::object_list() : head(0), tail(0) +{ +} + +void object_list::append(object *obj) +{ + if (tail == 0) { + obj->next = obj->prev = 0; + head = tail = obj; + } + else { + obj->prev = tail; + obj->next = 0; + tail->next = obj; + tail = obj; + } +} + +void object_list::wrap_up_block(object_list *ol) +{ + object *p; + for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev) + ; + assert(p != 0); + ol->head = p->next; + if (ol->head) { + ol->tail = tail; + ol->head->prev = 0; + } + else + ol->tail = 0; + tail = p->prev; + if (tail) + tail->next = 0; + else + head = 0; + delete p; +} + +text_piece::text_piece() +: text(0), filename(0), lineno(-1) +{ + adj.h = CENTER_ADJUST; + adj.v = NONE_ADJUST; +} + +text_piece::~text_piece() +{ + free(text); +} + +class graphic_object : public object { + int ntext; + text_piece *text; + int aligned; +protected: + line_type lt; + char *outline_color; + char *color_fill; +public: + graphic_object(); + ~graphic_object(); + object_type type() = 0; + void print_text(); + void add_text(text_item *, int); + void set_dotted(double); + void set_dashed(double); + void set_thickness(double); + void set_invisible(); + void set_outline_color(char *); + char *get_outline_color(); + virtual void set_fill(double); + virtual void set_xslanted(double); + virtual void set_yslanted(double); + virtual void set_fill_color(char *); +}; + +graphic_object::graphic_object() +: ntext(0), text(0), aligned(0), outline_color(0), color_fill(0) +{ +} + +void graphic_object::set_dotted(double wid) +{ + lt.type = line_type::dotted; + lt.dash_width = wid; +} + +void graphic_object::set_dashed(double wid) +{ + lt.type = line_type::dashed; + lt.dash_width = wid; +} + +void graphic_object::set_thickness(double th) +{ + lt.thickness = th; +} + +void graphic_object::set_fill(double) +{ +} + +void graphic_object::set_xslanted(double) +{ +} + +void graphic_object::set_yslanted(double) +{ +} + +void graphic_object::set_fill_color(char *c) +{ + color_fill = strsave(c); +} + +void graphic_object::set_outline_color(char *c) +{ + outline_color = strsave(c); +} + +char *graphic_object::get_outline_color() +{ + return outline_color; +} + +void graphic_object::set_invisible() +{ + lt.type = line_type::invisible; +} + +void graphic_object::add_text(text_item *t, int a) +{ + aligned = a; + int len = 0; + text_item *p; + for (p = t; p; p = p->next) + len++; + if (len == 0) + text = 0; + else { + text = new text_piece[len]; + for (p = t, len = 0; p; p = p->next, len++) { + text[len].text = p->text; + p->text = 0; + text[len].adj = p->adj; + text[len].filename = p->filename; + text[len].lineno = p->lineno; + } + } + ntext = len; +} + +void graphic_object::print_text() +{ + double angle = 0.0; + if (aligned) { + position d(end() - start()); + if (d.x != 0.0 || d.y != 0.0) + angle = atan2(d.y, d.x); + } + if (text != 0) { + out->set_color(color_fill, get_outline_color()); + out->text(center(), text, ntext, angle); + out->reset_color(); + } +} + +graphic_object::~graphic_object() +{ + if (text) + delete[] text; +} + +class rectangle_object : public graphic_object { +protected: + position cent; + position dim; +public: + rectangle_object(const position &); + double width() { return dim.x; } + double height() { return dim.y; } + position origin() { return cent; } + position center() { return cent; } + position north() { return position(cent.x, cent.y + dim.y/2.0); } + position south() { return position(cent.x, cent.y - dim.y/2.0); } + position east() { return position(cent.x + dim.x/2.0, cent.y); } + position west() { return position(cent.x - dim.x/2.0, cent.y); } + position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); } + position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); } + position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); } + position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); } + object_type type() = 0; + void update_bounding_box(bounding_box *); + void move_by(const position &); +}; + +rectangle_object::rectangle_object(const position &d) +: dim(d) +{ +} + +void rectangle_object::update_bounding_box(bounding_box *p) +{ + p->encompass(cent - dim/2.0); + p->encompass(cent + dim/2.0); +} + +void rectangle_object::move_by(const position &a) +{ + cent += a; +} + +class closed_object : public rectangle_object { +public: + closed_object(const position &); + object_type type() = 0; + void set_fill(double); + void set_xslanted(double); + void set_yslanted(double); + void set_fill_color(char *fill); +protected: + double fill; // < 0 if not filled + double xslanted; // !=0 if x is slanted + double yslanted; // !=0 if y is slanted + char *color_fill; // = 0 if not colored +}; + +closed_object::closed_object(const position &pos) +: rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0) +{ +} + +void closed_object::set_fill(double f) +{ + assert(f >= 0.0); + fill = f; +} + +/* accept positive and negative values */ +void closed_object::set_xslanted(double s) +{ + //assert(s >= 0.0); + xslanted = s; +} +/* accept positive and negative values */ +void closed_object::set_yslanted(double s) +{ + //assert(s >= 0.0); + yslanted = s; +} + +void closed_object::set_fill_color(char *f) +{ + color_fill = strsave(f); +} + +class box_object : public closed_object { + double xrad; + double yrad; +public: + box_object(const position &, double); + object_type type() { return BOX_OBJECT; } + void print(); + position north_east(); + position north_west(); + position south_east(); + position south_west(); +}; + +box_object::box_object(const position &pos, double r) +: closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r) +{ +} + +const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2; + +position box_object::north_east() +{ + return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad, + cent.y + dim.y/2.0 - CHOP_FACTOR*yrad); +} + +position box_object::north_west() +{ + return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad, + cent.y + dim.y/2.0 - CHOP_FACTOR*yrad); +} + +position box_object::south_east() +{ + return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad, + cent.y - dim.y/2.0 + CHOP_FACTOR*yrad); +} + +position box_object::south_west() +{ + return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad, + cent.y - dim.y/2.0 + CHOP_FACTOR*yrad); +} + +void box_object::print() +{ + if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0) + return; + out->set_color(color_fill, graphic_object::get_outline_color()); + if (xrad == 0.0) { + distance dim2 = dim/2.0; + position vec[4]; + /* error("x slanted %1", xslanted); */ + /* error("y slanted %1", yslanted); */ + vec[0] = cent + position(dim2.x, -(dim2.y - yslanted)); /* lr */ + vec[1] = cent + position(dim2.x + xslanted, dim2.y + yslanted); /* ur */ + vec[2] = cent + position(-(dim2.x - xslanted), dim2.y); /* ul */ + vec[3] = cent + position(-(dim2.x), -dim2.y); /* ll */ + out->polygon(vec, 4, lt, fill); + } + else { + distance abs_dim(fabs(dim.x), fabs(dim.y)); + out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill, color_fill); + } + out->reset_color(); +} + +graphic_object *object_spec::make_box(position *curpos, direction *dirp) +{ + static double last_box_height; + static double last_box_width; + static double last_box_radius; + static int have_last_box = 0; + if (!(flags & HAS_HEIGHT)) { + if ((flags & IS_SAME) && have_last_box) + height = last_box_height; + else + lookup_variable("boxht", &height); + } + if (!(flags & HAS_WIDTH)) { + if ((flags & IS_SAME) && have_last_box) + width = last_box_width; + else + lookup_variable("boxwid", &width); + } + if (!(flags & HAS_RADIUS)) { + if ((flags & IS_SAME) && have_last_box) + radius = last_box_radius; + else + lookup_variable("boxrad", &radius); + } + last_box_width = width; + last_box_height = height; + last_box_radius = radius; + have_last_box = 1; + radius = fabs(radius); + if (radius*2.0 > fabs(width)) + radius = fabs(width/2.0); + if (radius*2.0 > fabs(height)) + radius = fabs(height/2.0); + box_object *p = new box_object(position(width, height), radius); + if (!position_rectangle(p, curpos, dirp)) { + delete p; + p = 0; + } + return p; +} + +// return non-zero for success + +int object_spec::position_rectangle(rectangle_object *p, + position *curpos, direction *dirp) +{ + position pos; + dir = *dirp; // ignore any direction in attribute list + position motion; + switch (dir) { + case UP_DIRECTION: + motion.y = p->height()/2.0; + break; + case DOWN_DIRECTION: + motion.y = -p->height()/2.0; + break; + case LEFT_DIRECTION: + motion.x = -p->width()/2.0; + break; + case RIGHT_DIRECTION: + motion.x = p->width()/2.0; + break; + default: + assert(0); + } + if (flags & HAS_AT) { + pos = at; + if (flags & HAS_WITH) { + place offset; + place here; + here.obj = p; + if (!with->follow(here, &offset)) + return 0; + pos -= offset; + } + } + else { + pos = *curpos; + pos += motion; + } + p->move_by(pos); + pos += motion; + *curpos = pos; + return 1; +} + +class block_object : public rectangle_object { + object_list oblist; + PTABLE(place) *tbl; +public: + block_object(const position &, const object_list &ol, PTABLE(place) *t); + ~block_object(); + place *find_label(const char *); + object_type type(); + void move_by(const position &); + void print(); +}; + +block_object::block_object(const position &d, const object_list &ol, + PTABLE(place) *t) +: rectangle_object(d), oblist(ol), tbl(t) +{ +} + +block_object::~block_object() +{ + delete tbl; + object *p = oblist.head; + while (p != 0) { + object *tem = p; + p = p->next; + delete tem; + } +} + +void block_object::print() +{ + out->begin_block(south_west(), north_east()); + print_object_list(oblist.head); + out->end_block(); +} + +static void adjust_objectless_places(PTABLE(place) *tbl, const position &a) +{ + // Adjust all the labels that aren't attached to objects. + PTABLE_ITERATOR(place) iter(tbl); + const char *key; + place *pl; + while (iter.next(&key, &pl)) + if (key && csupper(key[0]) && pl->obj == 0) { + pl->x += a.x; + pl->y += a.y; + } +} + +void block_object::move_by(const position &a) +{ + cent += a; + for (object *p = oblist.head; p; p = p->next) + p->move_by(a); + adjust_objectless_places(tbl, a); +} + + +place *block_object::find_label(const char *name) +{ + return tbl->lookup(name); +} + +object_type block_object::type() +{ + return BLOCK_OBJECT; +} + +graphic_object *object_spec::make_block(position *curpos, direction *dirp) +{ + bounding_box bb; + for (object *p = oblist.head; p; p = p->next) + p->update_bounding_box(&bb); + position dim; + if (!bb.blank) { + position m = -(bb.ll + bb.ur)/2.0; + for (object *p = oblist.head; p; p = p->next) + p->move_by(m); + adjust_objectless_places(tbl, m); + dim = bb.ur - bb.ll; + } + if (flags & HAS_WIDTH) + dim.x = width; + if (flags & HAS_HEIGHT) + dim.y = height; + block_object *block = new block_object(dim, oblist, tbl); + if (!position_rectangle(block, curpos, dirp)) { + delete block; + block = 0; + } + tbl = 0; + oblist.head = oblist.tail = 0; + return block; +} + +class text_object : public rectangle_object { +public: + text_object(const position &); + object_type type() { return TEXT_OBJECT; } +}; + +text_object::text_object(const position &d) +: rectangle_object(d) +{ +} + +graphic_object *object_spec::make_text(position *curpos, direction *dirp) +{ + if (!(flags & HAS_HEIGHT)) { + lookup_variable("textht", &height); + int nitems = 0; + for (text_item *t = text; t; t = t->next) + nitems++; + height *= nitems; + } + if (!(flags & HAS_WIDTH)) + lookup_variable("textwid", &width); + text_object *p = new text_object(position(width, height)); + if (!position_rectangle(p, curpos, dirp)) { + delete p; + p = 0; + } + return p; +} + + +class ellipse_object : public closed_object { +public: + ellipse_object(const position &); + position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0), + cent.y + dim.y/(M_SQRT2*2.0)); } + position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0), + cent.y + dim.y/(M_SQRT2*2.0)); } + position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0), + cent.y - dim.y/(M_SQRT2*2.0)); } + position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0), + cent.y - dim.y/(M_SQRT2*2.0)); } + double radius() { return dim.x/2.0; } + object_type type() { return ELLIPSE_OBJECT; } + void print(); +}; + +ellipse_object::ellipse_object(const position &d) +: closed_object(d) +{ +} + +void ellipse_object::print() +{ + if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0) + return; + out->set_color(color_fill, graphic_object::get_outline_color()); + out->ellipse(cent, dim, lt, fill); + out->reset_color(); +} + +graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp) +{ + static double last_ellipse_height; + static double last_ellipse_width; + static int have_last_ellipse = 0; + if (!(flags & HAS_HEIGHT)) { + if ((flags & IS_SAME) && have_last_ellipse) + height = last_ellipse_height; + else + lookup_variable("ellipseht", &height); + } + if (!(flags & HAS_WIDTH)) { + if ((flags & IS_SAME) && have_last_ellipse) + width = last_ellipse_width; + else + lookup_variable("ellipsewid", &width); + } + last_ellipse_width = width; + last_ellipse_height = height; + have_last_ellipse = 1; + ellipse_object *p = new ellipse_object(position(width, height)); + if (!position_rectangle(p, curpos, dirp)) { + delete p; + return 0; + } + return p; +} + +class circle_object : public ellipse_object { +public: + circle_object(double); + object_type type() { return CIRCLE_OBJECT; } + void print(); +}; + +circle_object::circle_object(double diam) +: ellipse_object(position(diam, diam)) +{ +} + +void circle_object::print() +{ + if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0) + return; + out->set_color(color_fill, graphic_object::get_outline_color()); + out->circle(cent, dim.x/2.0, lt, fill); + out->reset_color(); +} + +graphic_object *object_spec::make_circle(position *curpos, direction *dirp) +{ + static double last_circle_radius; + static int have_last_circle = 0; + if (!(flags & HAS_RADIUS)) { + if ((flags & IS_SAME) && have_last_circle) + radius = last_circle_radius; + else + lookup_variable("circlerad", &radius); + } + last_circle_radius = radius; + have_last_circle = 1; + circle_object *p = new circle_object(radius*2.0); + if (!position_rectangle(p, curpos, dirp)) { + delete p; + return 0; + } + return p; +} + +class move_object : public graphic_object { + position strt; + position en; +public: + move_object(const position &s, const position &e); + position origin() { return en; } + object_type type() { return MOVE_OBJECT; } + void update_bounding_box(bounding_box *); + void move_by(const position &); +}; + +move_object::move_object(const position &s, const position &e) +: strt(s), en(e) +{ +} + +void move_object::update_bounding_box(bounding_box *p) +{ + p->encompass(strt); + p->encompass(en); +} + +void move_object::move_by(const position &a) +{ + strt += a; + en += a; +} + +graphic_object *object_spec::make_move(position *curpos, direction *dirp) +{ + static position last_move; + static int have_last_move = 0; + *dirp = dir; + // No need to look at at since 'at' attribute sets 'from' attribute. + position startpos = (flags & HAS_FROM) ? from : *curpos; + if (!(flags & HAS_SEGMENT)) { + if ((flags & IS_SAME) && have_last_move) + segment_pos = last_move; + else { + switch (dir) { + case UP_DIRECTION: + segment_pos.y = segment_height; + break; + case DOWN_DIRECTION: + segment_pos.y = -segment_height; + break; + case LEFT_DIRECTION: + segment_pos.x = -segment_width; + break; + case RIGHT_DIRECTION: + segment_pos.x = segment_width; + break; + default: + assert(0); + } + } + } + segment_list = new segment(segment_pos, segment_is_absolute, segment_list); + // Reverse the segment_list so that it's in forward order. + segment *old = segment_list; + segment_list = 0; + while (old != 0) { + segment *tem = old->next; + old->next = segment_list; + segment_list = old; + old = tem; + } + // Compute the end position. + position endpos = startpos; + for (segment *s = segment_list; s; s = s->next) + if (s->is_absolute) + endpos = s->pos; + else + endpos += s->pos; + have_last_move = 1; + last_move = endpos - startpos; + move_object *p = new move_object(startpos, endpos); + *curpos = endpos; + return p; +} + +class linear_object : public graphic_object { +protected: + char arrow_at_start; + char arrow_at_end; + arrow_head_type aht; + position strt; + position en; +public: + linear_object(const position &s, const position &e); + position start() { return strt; } + position end() { return en; } + void move_by(const position &); + void update_bounding_box(bounding_box *) = 0; + object_type type() = 0; + void add_arrows(int at_start, int at_end, const arrow_head_type &); +}; + +class line_object : public linear_object { +protected: + position *v; + int n; +public: + line_object(const position &s, const position &e, position *, int); + ~line_object(); + position origin() { return strt; } + position center() { return (strt + en)/2.0; } + position north() { return (en.y - strt.y) > 0 ? en : strt; } + position south() { return (en.y - strt.y) < 0 ? en : strt; } + position east() { return (en.x - strt.x) > 0 ? en : strt; } + position west() { return (en.x - strt.x) < 0 ? en : strt; } + object_type type() { return LINE_OBJECT; } + void update_bounding_box(bounding_box *); + void print(); + void move_by(const position &); +}; + +class arrow_object : public line_object { +public: + arrow_object(const position &, const position &, position *, int); + object_type type() { return ARROW_OBJECT; } +}; + +class spline_object : public line_object { +public: + spline_object(const position &, const position &, position *, int); + object_type type() { return SPLINE_OBJECT; } + void print(); + void update_bounding_box(bounding_box *); +}; + +linear_object::linear_object(const position &s, const position &e) +: arrow_at_start(0), arrow_at_end(0), strt(s), en(e) +{ +} + +void linear_object::move_by(const position &a) +{ + strt += a; + en += a; +} + +void linear_object::add_arrows(int at_start, int at_end, + const arrow_head_type &a) +{ + arrow_at_start = at_start; + arrow_at_end = at_end; + aht = a; +} + +line_object::line_object(const position &s, const position &e, + position *p, int i) +: linear_object(s, e), v(p), n(i) +{ +} + +void line_object::print() +{ + if (lt.type == line_type::invisible) + return; + out->set_color(0, graphic_object::get_outline_color()); + // shorten line length to avoid arrow sticking. + position sp = strt; + if (arrow_at_start) { + position base = v[0] - strt; + double hyp = hypot(base); + if (hyp == 0.0) { + error("cannot draw arrow on object with zero length"); + return; + } + if (aht.solid && out->supports_filled_polygons()) { + base *= aht.height / hyp; + draw_arrow(strt, strt - v[0], aht, lt, + graphic_object::get_outline_color()); + sp = strt + base; + } else { + base *= fabs(lt.thickness) / hyp / 72 / 4; + sp = strt + base; + draw_arrow(sp, sp - v[0], aht, lt, + graphic_object::get_outline_color()); + } + } + if (arrow_at_end) { + position base = v[n-1] - (n > 1 ? v[n-2] : strt); + double hyp = hypot(base); + if (hyp == 0.0) { + error("cannot draw arrow on object with zero length"); + return; + } + if (aht.solid && out->supports_filled_polygons()) { + base *= aht.height / hyp; + draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, + graphic_object::get_outline_color()); + v[n-1] = en - base; + } else { + base *= fabs(lt.thickness) / hyp / 72 / 4; + v[n-1] = en - base; + draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, + graphic_object::get_outline_color()); + } + } + out->line(sp, v, n, lt); + out->reset_color(); +} + +void line_object::update_bounding_box(bounding_box *p) +{ + p->encompass(strt); + for (int i = 0; i < n; i++) + p->encompass(v[i]); +} + +void line_object::move_by(const position &pos) +{ + linear_object::move_by(pos); + for (int i = 0; i < n; i++) + v[i] += pos; +} + +void spline_object::update_bounding_box(bounding_box *p) +{ + p->encompass(strt); + p->encompass(en); + /* + + If + + p1 = q1/2 + q2/2 + p2 = q1/6 + q2*5/6 + p3 = q2*5/6 + q3/6 + p4 = q2/2 + q3/2 + [ the points for the Bezier cubic ] + + and + + t = .5 + + then + + (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4 + [ the equation for the Bezier cubic ] + + = .125*q1 + .75*q2 + .125*q3 + + */ + for (int i = 1; i < n; i++) + p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125); +} + +arrow_object::arrow_object(const position &s, const position &e, + position *p, int i) +: line_object(s, e, p, i) +{ +} + +spline_object::spline_object(const position &s, const position &e, + position *p, int i) +: line_object(s, e, p, i) +{ +} + +void spline_object::print() +{ + if (lt.type == line_type::invisible) + return; + out->set_color(0, graphic_object::get_outline_color()); + // shorten line length for spline to avoid arrow sticking + position sp = strt; + if (arrow_at_start) { + position base = v[0] - strt; + double hyp = hypot(base); + if (hyp == 0.0) { + error("cannot draw arrow on object with zero length"); + return; + } + if (aht.solid && out->supports_filled_polygons()) { + base *= aht.height / hyp; + draw_arrow(strt, strt - v[0], aht, lt, + graphic_object::get_outline_color()); + sp = strt + base*0.1; // to reserve spline shape + } else { + base *= fabs(lt.thickness) / hyp / 72 / 4; + sp = strt + base; + draw_arrow(sp, sp - v[0], aht, lt, + graphic_object::get_outline_color()); + } + } + if (arrow_at_end) { + position base = v[n-1] - (n > 1 ? v[n-2] : strt); + double hyp = hypot(base); + if (hyp == 0.0) { + error("cannot draw arrow on object with zero length"); + return; + } + if (aht.solid && out->supports_filled_polygons()) { + base *= aht.height / hyp; + draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, + graphic_object::get_outline_color()); + v[n-1] = en - base*0.1; // to reserve spline shape + } else { + base *= fabs(lt.thickness) / hyp / 72 / 4; + v[n-1] = en - base; + draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, + graphic_object::get_outline_color()); + } + } + out->spline(sp, v, n, lt); + out->reset_color(); +} + +line_object::~line_object() +{ + delete[] v; +} + +linear_object *object_spec::make_line(position *curpos, direction *dirp) +{ + static position last_line; + static int have_last_line = 0; + *dirp = dir; + // We handle 'at' only in conjunction with 'with', otherwise it is + // the same as the 'from' attribute. + position startpos; + if ((flags & HAS_AT) && (flags & HAS_WITH)) + // handled later -- we need the end position + startpos = *curpos; + else if (flags & HAS_FROM) + startpos = from; + else + startpos = *curpos; + if (!(flags & HAS_SEGMENT)) { + if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT) + && have_last_line) + segment_pos = last_line; + else + switch (dir) { + case UP_DIRECTION: + segment_pos.y = segment_height; + break; + case DOWN_DIRECTION: + segment_pos.y = -segment_height; + break; + case LEFT_DIRECTION: + segment_pos.x = -segment_width; + break; + case RIGHT_DIRECTION: + segment_pos.x = segment_width; + break; + default: + assert(0); + } + } + segment_list = new segment(segment_pos, segment_is_absolute, segment_list); + // reverse the segment_list so that it's in forward order + segment *old = segment_list; + segment_list = 0; + while (old != 0) { + segment *tem = old->next; + old->next = segment_list; + segment_list = old; + old = tem; + } + // Absolutise all movements + position endpos = startpos; + int nsegments = 0; + segment *s; + for (s = segment_list; s; s = s->next, nsegments++) + if (s->is_absolute) + endpos = s->pos; + else { + endpos += s->pos; + s->pos = endpos; + s->is_absolute = 1; // to avoid confusion + } + if ((flags & HAS_AT) && (flags & HAS_WITH)) { + // 'tmpobj' works for arrows and splines too -- we only need positions + line_object tmpobj(startpos, endpos, 0, 0); + position pos = at; + place offset; + place here; + here.obj = &tmpobj; + if (!with->follow(here, &offset)) + return 0; + pos -= offset; + for (s = segment_list; s; s = s->next) + s->pos += pos; + startpos += pos; + endpos += pos; + } + // handle chop + line_object *p = 0; + position *v = new position[nsegments]; + int i = 0; + for (s = segment_list; s; s = s->next, i++) + v[i] = s->pos; + if (flags & IS_DEFAULT_CHOPPED) { + lookup_variable("circlerad", &start_chop); + end_chop = start_chop; + flags |= IS_CHOPPED; + } + if (flags & IS_CHOPPED) { + position start_chop_vec, end_chop_vec; + if (start_chop != 0.0) { + start_chop_vec = v[0] - startpos; + start_chop_vec *= start_chop / hypot(start_chop_vec); + } + if (end_chop != 0.0) { + end_chop_vec = (v[nsegments - 1] + - (nsegments > 1 ? v[nsegments - 2] : startpos)); + end_chop_vec *= end_chop / hypot(end_chop_vec); + } + startpos += start_chop_vec; + v[nsegments - 1] -= end_chop_vec; + endpos -= end_chop_vec; + } + switch (type) { + case SPLINE_OBJECT: + p = new spline_object(startpos, endpos, v, nsegments); + break; + case ARROW_OBJECT: + p = new arrow_object(startpos, endpos, v, nsegments); + break; + case LINE_OBJECT: + p = new line_object(startpos, endpos, v, nsegments); + break; + default: + assert(0); + } + have_last_line = 1; + last_line = endpos - startpos; + *curpos = endpos; + return p; +} + +class arc_object : public linear_object { + int clockwise; + position cent; + double rad; +public: + arc_object(int, const position &, const position &, const position &); + position origin() { return cent; } + position center() { return cent; } + double radius() { return rad; } + position north(); + position south(); + position east(); + position west(); + position north_east(); + position north_west(); + position south_east(); + position south_west(); + void update_bounding_box(bounding_box *); + object_type type() { return ARC_OBJECT; } + void print(); + void move_by(const position &pos); +}; + +arc_object::arc_object(int cw, const position &s, const position &e, + const position &c) +: linear_object(s, e), clockwise(cw), cent(c) +{ + rad = hypot(c - s); +} + +void arc_object::move_by(const position &pos) +{ + linear_object::move_by(pos); + cent += pos; +} + +// we get arc corners from the corresponding circle + +position arc_object::north() +{ + position result(cent); + result.y += rad; + return result; +} + +position arc_object::south() +{ + position result(cent); + result.y -= rad; + return result; +} + +position arc_object::east() +{ + position result(cent); + result.x += rad; + return result; +} + +position arc_object::west() +{ + position result(cent); + result.x -= rad; + return result; +} + +position arc_object::north_east() +{ + position result(cent); + result.x += rad/M_SQRT2; + result.y += rad/M_SQRT2; + return result; +} + +position arc_object::north_west() +{ + position result(cent); + result.x -= rad/M_SQRT2; + result.y += rad/M_SQRT2; + return result; +} + +position arc_object::south_east() +{ + position result(cent); + result.x += rad/M_SQRT2; + result.y -= rad/M_SQRT2; + return result; +} + +position arc_object::south_west() +{ + position result(cent); + result.x -= rad/M_SQRT2; + result.y -= rad/M_SQRT2; + return result; +} + + +void arc_object::print() +{ + if (lt.type == line_type::invisible) + return; + out->set_color(0, graphic_object::get_outline_color()); + // handle arrow direction; make shorter line for arc + position sp, ep, b; + if (clockwise) { + sp = en; + ep = strt; + } else { + sp = strt; + ep = en; + } + if (arrow_at_start) { + double theta = aht.height / rad; + if (clockwise) + theta = - theta; + b = strt - cent; + b = position(b.x*cos(theta) - b.y*sin(theta), + b.x*sin(theta) + b.y*cos(theta)) + cent; + if (clockwise) + ep = b; + else + sp = b; + if (aht.solid && out->supports_filled_polygons()) { + draw_arrow(strt, strt - b, aht, lt, + graphic_object::get_outline_color()); + } else { + position v = b; + theta = fabs(lt.thickness) / 72 / 4 / rad; + if (clockwise) + theta = - theta; + b = strt - cent; + b = position(b.x*cos(theta) - b.y*sin(theta), + b.x*sin(theta) + b.y*cos(theta)) + cent; + draw_arrow(b, b - v, aht, lt, + graphic_object::get_outline_color()); + out->line(b, &v, 1, lt); + } + } + if (arrow_at_end) { + double theta = aht.height / rad; + if (!clockwise) + theta = - theta; + b = en - cent; + b = position(b.x*cos(theta) - b.y*sin(theta), + b.x*sin(theta) + b.y*cos(theta)) + cent; + if (clockwise) + sp = b; + else + ep = b; + if (aht.solid && out->supports_filled_polygons()) { + draw_arrow(en, en - b, aht, lt, + graphic_object::get_outline_color()); + } else { + position v = b; + theta = fabs(lt.thickness) / 72 / 4 / rad; + if (!clockwise) + theta = - theta; + b = en - cent; + b = position(b.x*cos(theta) - b.y*sin(theta), + b.x*sin(theta) + b.y*cos(theta)) + cent; + draw_arrow(b, b - v, aht, lt, + graphic_object::get_outline_color()); + out->line(b, &v, 1, lt); + } + } + out->arc(sp, cent, ep, lt); + out->reset_color(); +} + +inline double max(double a, double b) +{ + return a > b ? a : b; +} + +void arc_object::update_bounding_box(bounding_box *p) +{ + p->encompass(strt); + p->encompass(en); + position start_offset = strt - cent; + if (start_offset.x == 0.0 && start_offset.y == 0.0) + return; + position end_offset = en - cent; + if (end_offset.x == 0.0 && end_offset.y == 0.0) + return; + double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0); + double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0); + if (clockwise) { + double temp = start_quad; + start_quad = end_quad; + end_quad = temp; + } + if (start_quad < 0.0) + start_quad += 4.0; + while (end_quad <= start_quad) + end_quad += 4.0; + double r = max(hypot(start_offset), hypot(end_offset)); + for (int q = int(start_quad) + 1; q < end_quad; q++) { + position offset; + switch (q % 4) { + case 0: + offset.x = r; + break; + case 1: + offset.y = r; + break; + case 2: + offset.x = -r; + break; + case 3: + offset.y = -r; + break; + } + p->encompass(cent + offset); + } +} + +// We ignore the with attribute. The at attribute always refers to the center. + +linear_object *object_spec::make_arc(position *curpos, direction *dirp) +{ + *dirp = dir; + int cw = (flags & IS_CLOCKWISE) != 0; + // compute the start + position startpos; + if (flags & HAS_FROM) + startpos = from; + else + startpos = *curpos; + if (!(flags & HAS_RADIUS)) + lookup_variable("arcrad", &radius); + // compute the end + position endpos; + if (flags & HAS_TO) + endpos = to; + else { + position m(radius, radius); + // Adjust the signs. + if (cw) { + if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION) + m.x = -m.x; + if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION) + m.y = -m.y; + *dirp = direction((dir + 3) % 4); + } + else { + if (dir == UP_DIRECTION || dir == LEFT_DIRECTION) + m.x = -m.x; + if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION) + m.y = -m.y; + *dirp = direction((dir + 1) % 4); + } + endpos = startpos + m; + } + // compute the center + position centerpos; + if (flags & HAS_AT) + centerpos = at; + else if (startpos == endpos) + centerpos = startpos; + else { + position h = (endpos - startpos)/2.0; + double d = hypot(h); + if (radius <= 0) + radius = .25; + // make the radius big enough + if (radius < d) + radius = d; + double alpha = acos(d/radius); + double theta = atan2(h.y, h.x); + if (cw) + theta -= alpha; + else + theta += alpha; + centerpos = position(cos(theta), sin(theta))*radius + startpos; + } + arc_object *p = new arc_object(cw, startpos, endpos, centerpos); + *curpos = endpos; + return p; +} + +graphic_object *object_spec::make_linear(position *curpos, direction *dirp) +{ + linear_object *obj; + if (type == ARC_OBJECT) + obj = make_arc(curpos, dirp); + else + obj = make_line(curpos, dirp); + if (type == ARROW_OBJECT + && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0) + flags |= HAS_RIGHT_ARROW_HEAD; + if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) { + arrow_head_type a; + int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0; + int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0; + if (flags & HAS_HEIGHT) + a.height = height; + else + lookup_variable("arrowht", &a.height); + if (flags & HAS_WIDTH) + a.width = width; + else + lookup_variable("arrowwid", &a.width); + double solid; + lookup_variable("arrowhead", &solid); + a.solid = solid != 0.0; + obj->add_arrows(at_start, at_end, a); + } + return obj; +} + +object *object_spec::make_object(position *curpos, direction *dirp) +{ + graphic_object *obj = 0; + switch (type) { + case BLOCK_OBJECT: + obj = make_block(curpos, dirp); + break; + case BOX_OBJECT: + obj = make_box(curpos, dirp); + break; + case TEXT_OBJECT: + obj = make_text(curpos, dirp); + break; + case ELLIPSE_OBJECT: + obj = make_ellipse(curpos, dirp); + break; + case CIRCLE_OBJECT: + obj = make_circle(curpos, dirp); + break; + case MOVE_OBJECT: + obj = make_move(curpos, dirp); + break; + case ARC_OBJECT: + case LINE_OBJECT: + case SPLINE_OBJECT: + case ARROW_OBJECT: + obj = make_linear(curpos, dirp); + break; + case MARK_OBJECT: + case OTHER_OBJECT: + default: + assert(0); + break; + } + if (obj) { + if (flags & IS_INVISIBLE) + obj->set_invisible(); + if (text != 0) + obj->add_text(text, (flags & IS_ALIGNED) != 0); + if (flags & IS_DOTTED) + obj->set_dotted(dash_width); + else if (flags & IS_DASHED) + obj->set_dashed(dash_width); + double th; + if (flags & HAS_THICKNESS) + th = thickness; + else + lookup_variable("linethick", &th); + obj->set_thickness(th); + if (flags & IS_OUTLINED) + obj->set_outline_color(outlined); + if (flags & IS_XSLANTED) + obj->set_xslanted(xslanted); + if (flags & IS_YSLANTED) + obj->set_yslanted(yslanted); + if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) { + if (flags & IS_SHADED) + obj->set_fill_color(shaded); + else { + if (flags & IS_DEFAULT_FILLED) + lookup_variable("fillval", &fill); + if (fill < 0.0) + error("bad fill value %1", fill); + else + obj->set_fill(fill); + } + } + } + return obj; +} + +struct string_list { + string_list *next; + char *str; + string_list(char *); + ~string_list(); +}; + +string_list::string_list(char *s) +: next(0), str(s) +{ +} + +string_list::~string_list() +{ + free(str); +} + +/* A path is used to hold the argument to the 'with' attribute. For + example, '.nw' or '.A.s' or '.A'. The major operation on a path is to + take a place and follow the path through the place to place within the + place. Note that '.A.B.C.sw' will work. + + For compatibility with DWB pic, 'with' accepts positions also (this + is incorrectly documented in CSTR 116). */ + +path::path(corner c) +: crn(c), label_list(0), ypath(0), is_position(0) +{ +} + +path::path(position p) +: crn(0), label_list(0), ypath(0), is_position(1) +{ + pos.x = p.x; + pos.y = p.y; +} + +path::path(char *l, corner c) +: crn(c), ypath(0), is_position(0) +{ + label_list = new string_list(l); +} + +path::~path() +{ + while (label_list) { + string_list *tem = label_list; + label_list = label_list->next; + delete tem; + } + delete ypath; +} + +void path::append(corner c) +{ + assert(crn == 0); + crn = c; +} + +void path::append(char *s) +{ + string_list **p; + for (p = &label_list; *p; p = &(*p)->next) + ; + *p = new string_list(s); +} + +void path::set_ypath(path *p) +{ + ypath = p; +} + +// return non-zero for success + +int path::follow(const place &pl, place *result) const +{ + if (is_position) { + result->x = pos.x; + result->y = pos.y; + result->obj = 0; + return 1; + } + const place *p = &pl; + for (string_list *lb = label_list; lb; lb = lb->next) + if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) { + lex_error("object does not contain a place '%1'", lb->str); + return 0; + } + if (crn == 0 || p->obj == 0) + *result = *p; + else { + position ps = ((p->obj)->*(crn))(); + result->x = ps.x; + result->y = ps.y; + result->obj = 0; + } + if (ypath) { + place tem; + if (!ypath->follow(pl, &tem)) + return 0; + result->y = tem.y; + if (result->obj != tem.obj) + result->obj = 0; + } + return 1; +} + +void print_object_list(object *p) +{ + for (; p; p = p->next) { + p->print(); + p->print_text(); + } +} + +void print_picture(object *obj) +{ + bounding_box bb; + for (object *p = obj; p; p = p->next) + p->update_bounding_box(&bb); + double scale; + lookup_variable("scale", &scale); + out->start_picture(scale, bb.ll, bb.ur); + print_object_list(obj); + out->finish_picture(); +} + diff --git a/src/preproc/pic/object.h b/src/preproc/pic/object.h new file mode 100644 index 0000000..e39b6a6 --- /dev/null +++ b/src/preproc/pic/object.h @@ -0,0 +1,227 @@ +// -*- 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/>. */ + +struct place; + +enum object_type { + OTHER_OBJECT, + BOX_OBJECT, + CIRCLE_OBJECT, + ELLIPSE_OBJECT, + ARC_OBJECT, + SPLINE_OBJECT, + LINE_OBJECT, + ARROW_OBJECT, + MOVE_OBJECT, + TEXT_OBJECT, + BLOCK_OBJECT, + MARK_OBJECT + }; + +struct bounding_box; + +struct object { + object *prev; + object *next; + object(); + virtual ~object(); + virtual position origin(); + virtual double width(); + virtual double radius(); + virtual double height(); + virtual position north(); + virtual position south(); + virtual position east(); + virtual position west(); + virtual position north_east(); + virtual position north_west(); + virtual position south_east(); + virtual position south_west(); + virtual position start(); + virtual position end(); + virtual position center(); + virtual place *find_label(const char *); + virtual void move_by(const position &); + virtual int blank(); + virtual void update_bounding_box(bounding_box *); + virtual object_type type() = 0; + virtual void print(); + virtual void print_text(); +}; + +typedef position (object::*corner)(); + +struct place { + object *obj; + double x, y; +}; + +struct string_list; + +class path { + position pos; + corner crn; + string_list *label_list; + path *ypath; + int is_position; +public: + path(corner = 0); + path(position); + path(char *, corner = 0); + ~path(); + void append(corner); + void append(char *); + void set_ypath(path *); + int follow(const place &, place *) const; +}; + +struct object_list { + object *head; + object *tail; + object_list(); + void append(object *); + void wrap_up_block(object_list *); +}; + +declare_ptable(place) + +// these go counterclockwise +enum direction { + RIGHT_DIRECTION, + UP_DIRECTION, + LEFT_DIRECTION, + DOWN_DIRECTION + }; + +struct graphics_state { + double x, y; + direction dir; +}; + +struct saved_state : public graphics_state { + saved_state *prev; + PTABLE(place) *tbl; +}; + + +struct text_item { + text_item *next; + char *text; + adjustment adj; + const char *filename; + int lineno; + + text_item(char *, const char *, int); + ~text_item(); +}; + +const unsigned long IS_DOTTED = 01; +const unsigned long IS_DASHED = 02; +const unsigned long IS_CLOCKWISE = 04; +const unsigned long IS_INVISIBLE = 020; +const unsigned long HAS_LEFT_ARROW_HEAD = 040; +const unsigned long HAS_RIGHT_ARROW_HEAD = 0100; +const unsigned long HAS_SEGMENT = 0200; +const unsigned long IS_SAME = 0400; +const unsigned long HAS_FROM = 01000; +const unsigned long HAS_AT = 02000; +const unsigned long HAS_WITH = 04000; +const unsigned long HAS_HEIGHT = 010000; +const unsigned long HAS_WIDTH = 020000; +const unsigned long HAS_RADIUS = 040000; +const unsigned long HAS_TO = 0100000; +const unsigned long IS_CHOPPED = 0200000; +const unsigned long IS_DEFAULT_CHOPPED = 0400000; +const unsigned long HAS_THICKNESS = 01000000; +const unsigned long IS_FILLED = 02000000; +const unsigned long IS_DEFAULT_FILLED = 04000000; +const unsigned long IS_ALIGNED = 010000000; +const unsigned long IS_SHADED = 020000000; +const unsigned long IS_OUTLINED = 040000000; +const unsigned long IS_XSLANTED = 0100000000; +const unsigned long IS_YSLANTED = 0200000000; + +struct segment { + int is_absolute; + position pos; + segment *next; + segment(const position &, int, segment *); +}; + +class rectangle_object; +class graphic_object; +class linear_object; + +struct object_spec { + unsigned long flags; + object_type type; + object_list oblist; + PTABLE(place) *tbl; + double dash_width; + position from; + position to; + position at; + position by; + path *with; + text_item *text; + double height; + double radius; + double width; + double segment_width; + double segment_height; + double start_chop; + double end_chop; + double thickness; + double fill; + double xslanted; + double yslanted; + char *shaded; + char *outlined; + direction dir; + segment *segment_list; + position segment_pos; + int segment_is_absolute; + + object_spec(object_type); + ~object_spec(); + object *make_object(position *, direction *); + graphic_object *make_box(position *, direction *); + graphic_object *make_block(position *, direction *); + graphic_object *make_text(position *, direction *); + graphic_object *make_ellipse(position *, direction *); + graphic_object *make_circle(position *, direction *); + linear_object *make_line(position *, direction *); + linear_object *make_arc(position *, direction *); + graphic_object *make_linear(position *, direction *); + graphic_object *make_move(position *, direction *); + int position_rectangle(rectangle_object *p, position *curpos, + direction *dirp); +}; + + +object *make_object(object_spec *, position *, direction *); + +object *make_mark_object(); +object *make_command_object(char *, const char *, int); + +int lookup_variable(const char *name, double *val); +void define_variable(const char *name, double val); + +void print_picture(object *); + diff --git a/src/preproc/pic/output.h b/src/preproc/pic/output.h new file mode 100644 index 0000000..f01e0a1 --- /dev/null +++ b/src/preproc/pic/output.h @@ -0,0 +1,82 @@ +// -*- 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/>. */ + +struct line_type { + enum { invisible, solid, dotted, dashed } type; + double dash_width; + double thickness; // the thickness is in points + + line_type(); +}; + + +class output { +protected: + char *args; + double desired_height; // zero if no height specified + double desired_width; // zero if no depth specified + double compute_scale(double, const position &, const position &); +public: + output(); + virtual ~output(); + void set_desired_width_height(double wid, double ht); + void set_args(const char *); + virtual void start_picture(double sc, const position &ll, const position &ur) = 0; + virtual void finish_picture() = 0; + virtual void circle(const position &, double rad, + const line_type &, double) = 0; + virtual void text(const position &, text_piece *, int, double) = 0; + virtual void line(const position &, const position *, int n, + const line_type &) = 0; + virtual void polygon(const position *, int n, + const line_type &, double) = 0; + virtual void spline(const position &, const position *, int n, + const line_type &) = 0; + virtual void arc(const position &, const position &, const position &, + const line_type &) = 0; + virtual void ellipse(const position &, const distance &, + const line_type &, double) = 0; + virtual void rounded_box(const position &, const distance &, double, + const line_type &, double, char *) = 0; + virtual void command(const char *, const char *, int) = 0; + virtual void set_location(const char *, int) {} + virtual void set_color(char *, char *) = 0; + virtual void reset_color() = 0; + virtual char *get_last_filled() = 0; + virtual char *get_outline_color() = 0; + virtual int supports_filled_polygons(); + virtual void begin_block(const position &ll, const position &ur); + virtual void end_block(); +}; + +extern output *out; + +/* #define FIG_SUPPORT 1 */ +#define TEX_SUPPORT 1 + +output *make_troff_output(); + +#ifdef TEX_SUPPORT +output *make_tex_output(); +output *make_tpic_output(); +#endif /* TEX_SUPPORT */ + +#ifdef FIG_SUPPORT +output *make_fig_output(); +#endif /* FIG_SUPPORT */ diff --git a/src/preproc/pic/pic.1.man b/src/preproc/pic/pic.1.man new file mode 100644 index 0000000..727cbc3 --- /dev/null +++ b/src/preproc/pic/pic.1.man @@ -0,0 +1,1569 @@ +.TH @g@pic @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +@g@pic \- compile pictures for +.I troff +or TeX +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1989-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_pic_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 +. +. +.\" ==================================================================== +.\" Definitions +.\" ==================================================================== +. +.ie t \{\ +. ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X +. ds lx L\h'-0.36m'\v'-0.22v'\s-2A\s0\h'-0.15m'\v'0.22v'\*[tx] +.\} +.el \{\ +. ds tx TeX +. ds lx LaTeX +.\} +. +.ie \n(.g .ds ic \/ +.el .ds ic \^ +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY @g@pic +.RB [ \-CnSU ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY @g@pic +.B \-t +.RB [ \-cCSUz ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY @g@pic +.B \-\-help +.YS +. +. +.SY @g@pic +.B \-v +. +.SY @g@pic +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU implementation of +.I pic \" generic +is part of the +.MR groff @MAN1EXT@ +document formatting system. +. +.I @g@pic +is a +.MR @g@troff @MAN1EXT@ +preprocessor that translates descriptions of diagrammatic pictures +embedded in +.MR roff @MAN7EXT@ +or \*[tx] input files into the language understood by \*[tx] or +.IR @g@troff . +. +It copies the contents of each +.I file +to the standard output stream, +except that lines between +.B .PS +and any of +.BR .PE , +.BR .PF , +or +.B .PY +are interpreted as picture descriptions in the +.I pic +language. +. +End a +.I @g@pic +picture with +.B .PE +to leave the drawing position at the bottom of the picture, +and with +.B .PF +or +.B .PY +to leave it at the top. +. +Normally, +.I @g@pic +is not executed directly by the user, +but invoked by specifying the +.B \-p +option to +.MR groff @MAN1EXT@ . +. +If no +.I file +operands are given on the command line, +or if +.I file +is +.RB \[lq] \- \[rq], +the standard input stream is read. +. +. +.P +It is the user's responsibility to provide appropriate definitions +of the +.BR PS , +.BR PE , +and one or both of the +.B PF +and +.B PY +macros. +. +When a macro package does not supply these, +obtain simple definitions with the +.I groff +option +.BR \-mpic ; +these will center each picture. +. +. +.P +GNU +.I pic \" GNU +supports +.B PY +as a synonym of +.B PF +to work around a name space collision with the +.I mm +macro package, +which defines +.B PF +as a page footer management macro. +. +Use +.B PF +preferentially unless a similar problem faces your document. +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-c +Be more compatible with +.IR tpic ; +implies +.BR \-t . +. +Lines beginning with +.B \[rs] +are not passed through transparently. +. +Lines beginning with +.B .\& +are passed through with the initial +.B .\& +changed to +.BR \[rs] . +. +A line beginning with +.B .ps +is given special treatment: +it takes an optional integer argument specifying the line thickness +(pen size) +in milliinches; +a missing argument restores the previous line thickness; +the default line thickness is 8\~milliinches. +. +The line thickness thus specified takes effect only when a +non-negative line thickness has not been specified by use of the +.B \%thickness +attribute or by setting the +.B \%linethick +variable. +. +. +.TP +.B \-C +Recognize +.BR .PS , +.BR .PE , +.BR .PF , +and +.B .PY +even when followed by a character other than space or newline. +. +. +.TP +.B \-n +Don't use +.I groff +extensions to the +.I troff \" generic +drawing commands. +. +Specify this option if a postprocessor you're using doesn't support +these extensions, +described in +.MR groff_out @MAN5EXT@ . +. +This option also causes +.I @g@pic +not to use zero-length lines to draw dots in +.I troff \" generic +mode. +. +. +.TP +.B \-S +Operate in +.I safer mode; +.B sh +commands are ignored. +. +This mode, +enabled by default, +can be useful when operating on untrustworthy input. +. +. +.TP +.B \-t +Produce \*[tx] output. +. +. +.TP +.B \-U +Operate in +.I unsafe mode; +.B sh +commands are interpreted. +. +. +.TP +.B \-z +In \*[tx] mode, +draw dots using zero-length lines. +. +. +.P +The following options supported by other versions of +.I pic \" generic +are ignored. +. +. +.TP +.B \-D +Draw all lines using the \[rs]D escape sequence. +GNU +.I pic \" GNU +always does this. +. +.TP +.BI \-T\~ dev +Generate output for the +.I troff \" generic +device +.IR dev . +. +This is unnecessary because the +.I troff \" generic +output generated by +GNU +.I pic \" GNU +is device-independent. +. +. +.\" ==================================================================== +.SH Usage +.\" ==================================================================== +. +This section primarily discusses the differences between GNU +.I pic \" GNU +and the Eighth Edition Research Unix version of AT&T +.I pic \" AT&T +(1985). +. +Many of these differences also apply to later versions of AT&T +.IR pic . +. +. +.\" ==================================================================== +.SS "\*[tx] mode" +.\" ==================================================================== +. +\*[tx]-compatible output is produced when the +.B \-t +option is specified. +. +You must use a \*[tx] driver that supports +.I tpic +version 2 specials. +. +.RI ( tpic +was a fork of AT&T +.I pic \" AT&T +by Tim Morgan of the University of California at Irvine that diverged +from its source around 1984. +. +It is best known today for lending its name to a group of +.B \[rs]special +commands it produced for \*[tx].) +.\" http://ftp.cs.stanford.edu/tex/texhax/texhax90.019 +. +. +.P +Lines beginning with +.B \[rs] +are passed through transparently; +a +.B % +is added to the end of the line to avoid unwanted spaces. +. +You can safely use this feature to change fonts or the value of +.BR \[rs]baselineskip . +. +Anything else may well produce undesirable results; +use at your own risk. +. +By default, +lines beginning with a dot are not treated specially\[em]but see the +.B \-c +option. +. +. +.P +In \*[tx] mode, +.I @g@pic +will define a vbox called +.B \[rs]graph +for each picture. +. +Use GNU +.IR pic 's \" GNU +.B figname +command to change the name of the vbox. +. +You must print that vbox yourself using the command +. +.RS +.EX +\[rs]centerline{\[rs]box\[rs]graph} +.EE +.RE +. +for instance. +. +Since the vbox has a height of zero +(it is defined with +.BR \[rs]vtop ) +this will produce slightly more vertical space above the picture than +below it; +. +.RS +.EX +\[rs]centerline{\[rs]raise 1em\[rs]box\[rs]graph} +.EE +.RE +. +would avoid this. +. +To give the vbox a positive height and a depth of zero +(as used by \*[lx]'s +.IR \%graphics.sty , +for example) +define the following macro in your document. +. +.RS +.EX +\[rs]def\[rs]gpicbox#1{% + \[rs]vbox{\[rs]unvbox\[rs]csname #1\[rs]endcsname\[rs]kern 0pt}} +.EE +.RE +. +You can then simply say +.B \[rs]gpicbox{graph} +instead of +.BR \[rs]box\[rs]graph . +. +. +.\" ==================================================================== +.SS Commands +.\" ==================================================================== +. +Several commands new to GNU +.I pic \" GNU +accept delimiters, +shown in their synopses as braces +.BR "{ }" . +. +Nesting of braces is supported. +. +Any other characters +(except a space, +tab, +or newline) +.\" XXX even crazy control characters, ugh--src/preproc/pic/lex.cpp:1266 +may be used as alternative delimiters, +in which case the members of a given pair must be identical. +. +Strings are recognized within delimiters of either kind; +they may contain the delimiter character or unbalanced braces. +. +. +.TP +\fBfor\fR \fIvariable\fR \fB=\fR \fIexpr1\fR \fBto\fR \fIexpr2\fR \ +[\fBby\fR [\fB*\fR]\,\fIexpr3\/\fR] \fBdo\fR \fIX\fR \fIbody\fR \fIX\fR +Set +.I variable +to +.IR expr1 . +. +While the value of +.I variable +is less than or equal to +.IR expr2 , +do +.I body +and increment +.I variable +by +.IR expr3 ; +if +.B by +is not given, +increment +.I variable +by 1. +. +If +.I expr3 +is prefixed by +.B * +then +.I variable +will instead be multiplied by +.IR expr3 . +. +The value of +.I expr3 +can be negative for the additive case; +.I variable +is then tested whether it is greater than or equal to +.IR expr2 . +. +For the multiplicative case, +.I expr3 +must be greater than zero. +. +If the constraints aren't met, +the loop isn't executed. +. +.I X +can be any character not occurring in +.IR body . +. +.TP +\fBif\fR \fIexpr\fR \fBthen\fR \fIX\fR \fIif-true\fR \fIX\fR \ +[\fBelse\fR \fIY\fR \fIif-false\fR \fIY\fR] +Evaluate +.IR expr ; +if it is non-zero then do +.IR if-true , +otherwise do +.IR if-false . +. +.I X +can be any character not occurring in +.IR if-true . +. +.I Y +can be any character not occurring in +.IR if-false . +. +.TP +.BI print\~ arg\c +\~.\|.\|. +Concatenate and write arguments to the standard error stream followed by +a newline. +. +Each +.I arg +must be an expression, +a position, +or text. +. +This is useful for debugging. +. +.TP +.BI command\~ arg\c +\~.\|.\|. +.\" Move right margin to indentation since we must indent more later. +.RS +Concatenate arguments +and pass them as a line to +.I troff \" generic +or \*[tx]. +. +Each +.I arg +must be an expression, +a position, +or text. +. +.B command +allows the values of +.I pic +variables to be passed to the formatter. +. +For example, +. +.RS +.EX +\&.PS +x = 14 +command ".ds string x is " x "." +\&.PE +\[rs]*[string] +.EE +.RE +. +produces +. +.RS +.EX +x is 14. +.EE +.RE +when formatted with +.IR troff . \" generic +.RE +. +. +.TP +\fBsh\fR \fIX\fR \fIcommand\fR \fIX\fR +Pass +.I command +to a shell. +. +. +.TP +\fBcopy\fR \fB"\,\fIfilename\/\fB"\fR +Include +.I filename +at this point in the file. +. +. +.TP +.BR copy\~ [ \[dq]\c +.IB filename \[dq]\c +.RB ]\~ thru\~\c +.IR "X body X" \~\c \" space in roman: we must use 2-font macro with \c +.RB [ until\~ \[dq]\c +.IB word \[dq]\c +] +.TQ +.BR copy\~ [ \[dq]\c +.IB filename \[dq]\c +.RB ]\~ thru\~\c +.IR macro \~\c \" space roman because we must use 2-font macro with \c +.RB [ until\~ \[dq]\c +.IB word \[dq]\c +] +.\" Move right margin to indentation since we must indent more later. +.RS +This construct does +.I body +once for each line of +.IR filename ; +the line is split into blank-delimited words, +and occurrences of +.BI $ i +in +.IR body , +for +.I i +between 1 and 9, +are replaced by the +.IR i -th +word of the line. +. +If +.I filename +is not given, +lines are taken from the current input up to +.BR .PE . +. +If an +.B until +clause is specified, +lines will be read only until a line the first word of which is +.IR word ; +that line will then be discarded. +. +.I X +can be any character not occurring in +.IR body . +. +For example, +. +.RS \" now move further +.EX +\&.PS +copy thru % circle at ($1,$2) % until "END" +1 2 +3 4 +5 6 +END +box +\&.PE +.EE +.RE +. +and +. +.RS +.EX +\&.PS +circle at (1,2) +circle at (3,4) +circle at (5,6) +box +\&.PE +.EE +.RE +. +are equivalent. +. +The commands to be performed for each line can also be taken from a +macro defined earlier by giving the name of the macro as the argument to +.BR thru . +. +The argument after +.B thru +is looked up as a macro name first; +if not defined, +its first character is interpreted as a delimiter. +.RE +. +. +.TP +.B reset +.TQ +.BI reset\~ pvar1\c +.RB [ , ]\~\c +.IR pvar2 \~.\|.\|. +Reset predefined variables +.IR pvar1 , +.I pvar2 +\&.\|.\|.\& to their default values; +if no arguments are given, +reset all predefined variables to their default values. +. +Variable names may be separated by commas, +spaces, +or both. +. +Assigning a value to +.B scale +also causes all predefined variables that control dimensions to be reset +to their default values times the new value of +.BR scale . +. +. +.TP +\fBplot\fR \fIexpr\fR [\fB"\,\fItext\*(ic\fB"\fR] +This is a text object which is constructed by using +.I text +as a format string for sprintf +with an argument of +.IR expr . +. +If +.I text +is omitted a format string of +.B \[dq]%g\[dq] +is used. +. +Attributes can be specified in the same way as for a normal text +object. +Be very careful that you specify an appropriate format string; +.I @g@pic +does only very limited checking of the string. +. +This is deprecated in favour of +.BR sprintf . +. +.TP +.IB var \~:=\~ expr +.RS +This syntax resembles variable assignment with +.B = +except that +.I var +must already be defined, +and +.I expr +will be assigned to +.I var +without creating a variable local to the current block. +. +(By contrast, +.B = +defines +.I var +in the current block if it is not already defined there, +and then changes the value in the current block only.) +. +For example, +. +.RS +.EX +.B .PS +.B x = 3 +.B y = 3 +.B [ +.B x := 5 +.B y = 5 +.B ] +.B print x " " y +.B .PE +.EE +.RE +. +writes +. +.RS +.EX +5 3 +.EE +.RE +. +to the standard error stream. +.RE +. +. +.\" ==================================================================== +.SS Expressions +.\" ==================================================================== +. +The syntax for expressions has been significantly extended. +. +. +.P +.IB x\ \[ha]\ y +(exponentiation) +.br +.BI sin( x ) +.br +.BI cos( x ) +.br +.BI atan2( y , \ x ) +.br +.BI log( x ) +(base 10) +.br +.BI exp( x ) +(base 10, i.e.\& +.ie t 10\v'-.4m'\fIx\*(ic\fR\v'.4m') +.el 10\[ha]\fIx\fR) +.br +.BI sqrt( x ) +.br +.BI int( x ) +.br +.B rand() +(return a random number between 0 and 1) +.br +.BI rand( x ) +(return a random number between 1 and +.IR x ; +deprecated) +.br +.BI srand( x ) +(set the random number seed) +.br +.BI max( e1 , \ e2 ) +.br +.BI min( e1 , \ e2 ) +.br +.BI ! e +.br +\fIe1\fB && \fIe2\fR +.br +\fIe1\fB || \fIe2\fR +.br +\fIe1\fB == \fIe2\fR +.br +\fIe1\fB != \fIe2\fR +.br +\fIe1\fB >= \fIe2\fR +.br +\fIe1\fB > \fIe2\fR +.br +\fIe1\fB <= \fIe2\fR +.br +\fIe1\fB < \fIe2\fR +.br +\fB"\,\fIstr1\*(ic\fB" == "\,\fIstr2\*(ic\fB"\fR +.br +\fB"\,\fIstr1\*(ic\fB" != "\,\fIstr2\*(ic\fB"\fR +.br +. +. +.LP +String comparison expressions must be parenthesised in some contexts +to avoid ambiguity. +. +. +.\" ==================================================================== +.SS "Other changes" +.\" ==================================================================== +. +A bare expression, +.IR expr , +is acceptable as an attribute; +it is equivalent to +.IR dir\ expr , +where +.I dir +is the current direction. +. +For example +.LP +.RS +.B line 2i +.RE +.LP +means draw a line 2\ inches long in the current direction. +. +The \[oq]i\[cq] +(or \[oq]I\[cq]) +character is ignored; +to use another measurement unit, +set the +.I scale +variable to an appropriate value. +. +. +.LP +The maximum width and height of the picture are taken from the variables +.B maxpswid +and +.BR maxpsht . +. +Initially, +these have values 8.5 and 11. +. +. +.LP +Scientific notation is allowed for numbers. +For example +. +. +.RS +.LP +.B +x = 5e\-2 +.RE +. +. +.LP +Text attributes can be compounded. +. +For example, +. +.RS +.LP +.B +"foo" above ljust +.RE +. +. +.LP +is valid. +. +. +.LP +There is no limit to the depth to which blocks can be examined. +. +For example, +.RS +.LP +.EX +[A: [B: [C: box ]]] with .A.B.C.sw at 1,2 +circle at last [\^].A.B.C +.EE +.RE +. +. +.LP +is acceptable. +. +. +.LP +Arcs now have compass points determined by the circle of which the arc +is a part. +. +. +.LP +Circles, +ellipses, +and arcs can be dotted or dashed. +. +In \*[tx] mode splines can be dotted or dashed also. +. +. +.LP +Boxes can have rounded corners. +. +The +.B rad +attribute specifies the radius of the quarter-circles at each corner. +If no +.B rad +or +.B diam +attribute is given, +a radius of +.B boxrad +is used. +. +Initially, +.B boxrad +has a value of\ 0. +. +A box with rounded corners can be dotted or dashed. +. +. +.LP +Boxes can have slanted sides. +. +This effectively changes the shape of a box from a rectangle to an +arbitrary parallelogram. +. +The +.B xslanted +and +.B yslanted +attributes specify the x and y\~offset of the box's upper right +corner from its default position. +. +. +.LP +The +.B .PS +line can have a second argument specifying a maximum height for +the picture. +. +If the width of zero is specified the width will be ignored in computing +the scaling factor for the picture. +. +GNU +.I pic \" GNU +will always scale a picture by the same amount vertically as well as +horizontally. +. +This is different from DWB 2.0 +.I pic \" foreign +which may scale a picture by a different amount vertically than +horizontally if a height is specified. +. +. +.LP +Each text object has an invisible box associated with it. +. +The compass points of a text object are determined by this box. +. +The implicit motion associated with the object is also determined +by this box. +. +The dimensions of this box are taken from the width and height +attributes; +if the width attribute is not supplied then the width will be taken to +be +.BR textwid ; +if the height attribute is not supplied then the height will be taken to +be the number of text strings associated with the object times +.BR textht . +. +Initially, +.B textwid +and +.B textht +have a value of 0. +. +. +.LP +In +(almost all) +places where a quoted text string can be used, +an expression of the form +. +. +.IP +.BI sprintf(\[dq] format \[dq],\~ arg ,\fR\~.\|.\|.\fB) +. +. +.LP +can also be used; +this will produce the arguments formatted according to +.IR format , +which should be a string as described in +.MR printf 3 +appropriate for the number of arguments supplied. +. +Only the modifiers +.RB \[lq] # \[rq], +.RB \[lq] \- \[rq], +.RB \[lq] + \[rq], +and \[lq]\~\[rq] [space]), +a minimum field width, +an optional precision, +and the conversion specifiers +.BR %e , +.BR %E , +.BR %f , +.BR %g , +.BR %G , +and +.B %% +are supported. +. +. +.LP +The thickness of the lines used to draw objects is controlled by the +.B linethick +variable. +. +This gives the thickness of lines in points. +. +A negative value means use the default thickness: +in \*[tx] output mode, +this means use a thickness of 8 milliinches; +in \*[tx] output mode with the +.B \-c +option, +this means use the line thickness specified by +.B .ps +lines; +in +.I troff +output mode, +this means use a thickness proportional to the pointsize. +. +A zero value means draw the thinnest possible line supported by +the output device. +. +Initially, +it has a value of \-1. +. +There is also a +.BR thick [ ness ] +attribute. +. +For example, +. +. +.RS +.LP +.B circle thickness 1.5 +.RE +. +. +.LP +would draw a circle using a line with a thickness of 1.5 points. +. +The thickness of lines is not affected by the +value of the +.B scale +variable, +nor by the width or height given in the +.B .PS +line. +. +. +.LP +Boxes +(including boxes with rounded corners or slanted sides), +circles and ellipses can be filled by giving them an attribute of +.BR fill [ ed ]. +. +This takes an optional argument of an expression with a value between +0 and 1; +0 will fill it with white, +1 with black, +values in between with a proportionally gray shade. +. +A value greater than 1 can also be used: +this means fill with the +shade of gray that is currently being used for text and lines. +. +Normally this will be black, +but output devices may provide a mechanism for changing this. +. +Without an argument, +then the value of the variable +.B \%fillval +will be used. +. +Initially, +this has a value of 0.5. +. +The invisible attribute does not affect the filling of objects. +. +Any text associated with a filled object will be added after the object +has been filled, +so that the text will not be obscured by the filling. +. +. +.P +Additional modifiers are available to draw colored objects: +.BR \%outline [ d ] +sets the color of the outline, +.B shaded +the fill color, +and +.BR colo [ u ] r [ ed ] +sets both. +. +All expect a subsequent string argument specifying the color. +. +.RS +.EX +circle shaded \[dq]green\[dq] outline \[dq]black\[dq] +.EE +.RE +. +Color is not yet supported in \*[tx] mode. +. +Device macro files like +.I ps.tmac +declare color names; +you can define additional ones with the +.B \%defcolor +request +(see +.MR groff @MAN7EXT@ ). +. +. +.LP +To change the name of the vbox in \*[tx] mode, +set the pseudo-variable +.B \%figname +(which is actually a specially parsed command) +within a picture. +. +Example: +.RS +.LP +.B .PS +.br +.B figname = foobar; +.br +.B ... +.br +.B .PE +.RE +. +. +.LP +The picture is then available in the box +.BR \[rs]foobar . +. +. +.LP +.I @g@pic +assumes that at the beginning of a picture both glyph and fill color are +set to the default value. +. +. +.LP +Arrow heads will be drawn as solid triangles if the variable +.B arrowhead +is non-zero and either \*[tx] mode is enabled or the +.B \-n +option has not been given. +. +Initially, +.B arrowhead +has a value of\ 1. +. +Solid arrow heads are always filled with the current outline color. +. +. +.LP +The +.I troff +output of +.I @g@pic +is device-independent. +. +The +.B \-T +option is therefore redundant. +. +All numbers are taken to be in inches; +numbers are never interpreted to be in +.I troff +machine units. +. +. +.LP +Objects can have an +.B aligned +attribute. +. +This will only work if the postprocessor is +.MR grops @MAN1EXT@ +or +.MR gropdf @MAN1EXT@ . +. +Any text associated with an object having the +.B aligned +attribute will be rotated about the center of the object +so that it is aligned in the direction from the start point +to the end point of the object. +. +This attribute will have no effect on objects whose start and end points +are coincident. +. +. +.LP +In places where +.IB n th +is allowed, +.BI \[aq] expr \[aq]th +is also allowed. +. +.RB \[lq] \[aq]th \[lq] +is a single token: +no space is allowed between the apostrophe and the +.RB \[lq] th \[rq]. +. +. +For example, +.IP +.EX +for i = 1 to 4 do { + line from \[aq]i\[aq]th box.nw to \[aq]i+1\[aq]th box.se +} +.EE +. +. +.\" ==================================================================== +.SH Conversion +.\" ==================================================================== +. +To obtain a stand-alone picture from a +.I @g@pic +file, +enclose your +.I pic \" language +code with +.B .PS +and +.B .PE +requests; +.I roff +configuration commands may be added at the beginning of the file, +but no +.I roff +text. +. +. +.LP +It is necessary to feed this file into +.I groff +without adding any page information, +so you must check which +.B .PS +and +.B .PE +requests are actually called. +. +For example, +the +.I mm +macro package adds a page number, +which is very annoying. +. +At the moment, +calling standard +.I groff +without any macro package works. +. +Alternatively, +you can define your own requests, +e.g., +to do nothing: +. +. +.LP +.RS +.EX +\&.de PS +\&.. +\&.de PE +\&.. +.EE +.RE +. +. +.LP +.I groff +itself does not provide direct conversion into other graphics file +formats. +. +But there are lots of possibilities if you first transform your +picture into PostScript\*R format using the +.I groff +option +.BR \-Tps . +. +Since this +.IR ps -file +lacks BoundingBox information it is not very useful by itself, but it +may be fed into other conversion programs, usually named +.BI ps2 other +or +.BI psto other +or the like. +. +Moreover, +the PostScript interpreter Ghostscript +.RI ( gs (1)) +has built-in graphics conversion devices that are called with the option +. +. +.LP +.RS +.BI "gs \-sDEVICE=" <devname> +.RE +. +. +.LP +Call +. +. +.LP +.RS +.B gs \-\-help +.RE +. +. +.LP +for a list of the available devices. +. +. +.LP +An alternative may be to use the +.B \-Tpdf +option to convert your picture directly into +.B PDF +format. +. +The MediaBox of the file produced can be controlled by passing a +.B \-P\-p +papersize to +.IR groff . +. +. +.br +.ne 3v +.P +As the Encapsulated PostScript File Format +.B EPS +is getting more and more important, +and the conversion wasn't regarded trivial in the past you might be +interested to know that there is a conversion tool named +.I ps2eps +which does the right job. +. +It is much better than the tool +.I ps2epsi +packaged with +.IR gs . +. +. +.LP +For bitmapped graphic formats, +you should use +.IR pstopnm ; +the resulting (intermediate) +.MR pnm 5 +file can be then converted to virtually any graphics format using the +tools of the +.B netpbm +package. +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @MACRODIR@/pic.tmac +offers simple definitions of the +.BR PS , +.BR PE , +.BR PF , +and +.B PY +macros. +. +. +.\" ==================================================================== +.SH Bugs +.\" ==================================================================== +. +Characters that are invalid as input to GNU +.I troff \" GNU +(see the +.I groff +Texinfo manual or +.MR groff_char @MAN7EXT@ +for a list) +are rejected even in \*[tx] mode. +. +. +.LP +The interpretation of +.B \%fillval +is incompatible with the +.I pic \" AT&T +in Tenth Edition Research Unix, +which interprets 0 as black and 1 as white. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.TP +.I @DOCDIR@/\:pic\:.ps +\[lq]Making Pictures with GNU pic\[rq], +by Eric S.\& Raymond. +. +This file, +together with its source, +.IR pic.ms , +is part of the +.I groff +distribution. +. +. +.P +\[lq]PIC\[em]A Graphics Language for Typesetting: User Manual\[rq], +by Brian W.\& Kernighan, +1984 +(revised 1991), +AT&T Bell Laboratories Computing Science Technical Report No.\& 116 +. +. +.P +.I ps2eps +is available from CTAN mirrors, e.g., +.UR ftp://\:ftp\:.dante\:.de/\:tex\-archive/\:support/\:ps2eps/ +.UE +. +. +.LP +W.\& Richard Stevens, +.UR http://\:www\:.kohala\:.com/\:start/\:troff/\:pic2html\:.html +.I Turning PIC into HTML +.UE +. +. +.LP +W.\& Richard Stevens, +.UR http://\:www\:.kohala\:.com/\:start/\:troff/\:pic\:.examples\:.ps +.IR "Examples of " pic " Macros" +.UE +. +. +.LP +.MR @g@troff @MAN1EXT@ , +.MR groff_out @MAN5EXT@ , +.MR tex 1 , +.MR gs 1 , +.MR ps2eps 1 , +.MR pstopnm 1 , +.MR ps2epsi 1 , +.MR pnm 5 +. +. +.\" Clean up. +.rm tx +.rm lx +.rm ic +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_pic_1_man_C] +.do rr *groff_pic_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/preproc/pic/pic.am b/src/preproc/pic/pic.am new file mode 100644 index 0000000..fae1372 --- /dev/null +++ b/src/preproc/pic/pic.am @@ -0,0 +1,56 @@ +# 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 += pic +pic_CPPFLAGS = $(AM_CPPFLAGS) \ + -I $(top_srcdir)/src/preproc/pic \ + -I $(top_builddir)/src/preproc/pic +pic_LDADD = libgroff.a $(LIBM) lib/libgnu.a +pic_SOURCES = \ + src/preproc/pic/pic.ypp \ + src/preproc/pic/lex.cpp \ + src/preproc/pic/main.cpp \ + src/preproc/pic/object.cpp \ + src/preproc/pic/common.cpp \ + src/preproc/pic/troff.cpp \ + src/preproc/pic/tex.cpp \ + src/preproc/pic/pic.h \ + src/preproc/pic/position.h \ + src/preproc/pic/text.h \ + src/preproc/pic/common.h \ + src/preproc/pic/output.h \ + src/preproc/pic/object.h + +PREFIXMAN1 += src/preproc/pic/pic.1 +EXTRA_DIST += \ + src/preproc/pic/TODO \ + src/preproc/pic/pic.1.man + +# Since pic_CPPFLAGS was set, all .o files have a 'pic-' prefix. +src/preproc/pic/pic-lex.$(OBJEXT): src/preproc/pic/pic.hpp + +MAINTAINERCLEANFILES += \ + src/preproc/pic/pic.cpp \ + src/preproc/pic/pic.hpp \ + src/preproc/pic/pic.output + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/pic/pic.cpp b/src/preproc/pic/pic.cpp new file mode 100644 index 0000000..14b15e4 --- /dev/null +++ b/src/preproc/pic/pic.cpp @@ -0,0 +1,5080 @@ +/* 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 19 "../src/preproc/pic/pic.ypp" + +#include "pic.h" +#include "ptable.h" +#include "object.h" + +extern int delim_flag; +extern void copy_rest_thru(const char *, const char *); +extern void copy_file_thru(const char *, const char *, const char *); +extern void push_body(const char *); +extern void do_for(char *var, double from, double to, + int by_is_multiplicative, double by, char *body); +extern void do_lookahead(); + +/* Maximum number of characters produced by printf("%g") */ +#define GDIGITS 14 + +int yylex(); +void yyerror(const char *); + +void reset(const char *nm); +void reset_all(); + +place *lookup_label(const char *); +void define_label(const char *label, const place *pl); + +direction current_direction; +position current_position; + +implement_ptable(place) + +PTABLE(place) top_table; + +PTABLE(place) *current_table = &top_table; +saved_state *current_saved_state = 0; + +object_list olist; + +const char *ordinal_postfix(int n); +const char *object_type_name(object_type type); +char *format_number(const char *fmt, double n); +char *do_sprintf(const char *fmt, const double *v, int nv); + + +#line 115 "src/preproc/pic/pic.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_PIC_PIC_HPP_INCLUDED +# define YY_YY_SRC_PREPROC_PIC_PIC_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" */ + LABEL = 258, /* LABEL */ + VARIABLE = 259, /* VARIABLE */ + NUMBER = 260, /* NUMBER */ + TEXT = 261, /* TEXT */ + COMMAND_LINE = 262, /* COMMAND_LINE */ + DELIMITED = 263, /* DELIMITED */ + ORDINAL = 264, /* ORDINAL */ + TH = 265, /* TH */ + LEFT_ARROW_HEAD = 266, /* LEFT_ARROW_HEAD */ + RIGHT_ARROW_HEAD = 267, /* RIGHT_ARROW_HEAD */ + DOUBLE_ARROW_HEAD = 268, /* DOUBLE_ARROW_HEAD */ + LAST = 269, /* LAST */ + BOX = 270, /* BOX */ + CIRCLE = 271, /* CIRCLE */ + ELLIPSE = 272, /* ELLIPSE */ + ARC = 273, /* ARC */ + LINE = 274, /* LINE */ + ARROW = 275, /* ARROW */ + MOVE = 276, /* MOVE */ + SPLINE = 277, /* SPLINE */ + HEIGHT = 278, /* HEIGHT */ + RADIUS = 279, /* RADIUS */ + FIGNAME = 280, /* FIGNAME */ + WIDTH = 281, /* WIDTH */ + DIAMETER = 282, /* DIAMETER */ + UP = 283, /* UP */ + DOWN = 284, /* DOWN */ + RIGHT = 285, /* RIGHT */ + LEFT = 286, /* LEFT */ + FROM = 287, /* FROM */ + TO = 288, /* TO */ + AT = 289, /* AT */ + WITH = 290, /* WITH */ + BY = 291, /* BY */ + THEN = 292, /* THEN */ + SOLID = 293, /* SOLID */ + DOTTED = 294, /* DOTTED */ + DASHED = 295, /* DASHED */ + CHOP = 296, /* CHOP */ + SAME = 297, /* SAME */ + INVISIBLE = 298, /* INVISIBLE */ + LJUST = 299, /* LJUST */ + RJUST = 300, /* RJUST */ + ABOVE = 301, /* ABOVE */ + BELOW = 302, /* BELOW */ + OF = 303, /* OF */ + THE = 304, /* THE */ + WAY = 305, /* WAY */ + BETWEEN = 306, /* BETWEEN */ + AND = 307, /* AND */ + HERE = 308, /* HERE */ + DOT_N = 309, /* DOT_N */ + DOT_E = 310, /* DOT_E */ + DOT_W = 311, /* DOT_W */ + DOT_S = 312, /* DOT_S */ + DOT_NE = 313, /* DOT_NE */ + DOT_SE = 314, /* DOT_SE */ + DOT_NW = 315, /* DOT_NW */ + DOT_SW = 316, /* DOT_SW */ + DOT_C = 317, /* DOT_C */ + DOT_START = 318, /* DOT_START */ + DOT_END = 319, /* DOT_END */ + DOT_X = 320, /* DOT_X */ + DOT_Y = 321, /* DOT_Y */ + DOT_HT = 322, /* DOT_HT */ + DOT_WID = 323, /* DOT_WID */ + DOT_RAD = 324, /* DOT_RAD */ + SIN = 325, /* SIN */ + COS = 326, /* COS */ + ATAN2 = 327, /* ATAN2 */ + LOG = 328, /* LOG */ + EXP = 329, /* EXP */ + SQRT = 330, /* SQRT */ + K_MAX = 331, /* K_MAX */ + K_MIN = 332, /* K_MIN */ + INT = 333, /* INT */ + RAND = 334, /* RAND */ + SRAND = 335, /* SRAND */ + COPY = 336, /* COPY */ + THRU = 337, /* THRU */ + TOP = 338, /* TOP */ + BOTTOM = 339, /* BOTTOM */ + UPPER = 340, /* UPPER */ + LOWER = 341, /* LOWER */ + SH = 342, /* SH */ + PRINT = 343, /* PRINT */ + CW = 344, /* CW */ + CCW = 345, /* CCW */ + FOR = 346, /* FOR */ + DO = 347, /* DO */ + IF = 348, /* IF */ + ELSE = 349, /* ELSE */ + ANDAND = 350, /* ANDAND */ + OROR = 351, /* OROR */ + NOTEQUAL = 352, /* NOTEQUAL */ + EQUALEQUAL = 353, /* EQUALEQUAL */ + LESSEQUAL = 354, /* LESSEQUAL */ + GREATEREQUAL = 355, /* GREATEREQUAL */ + LEFT_CORNER = 356, /* LEFT_CORNER */ + RIGHT_CORNER = 357, /* RIGHT_CORNER */ + NORTH = 358, /* NORTH */ + SOUTH = 359, /* SOUTH */ + EAST = 360, /* EAST */ + WEST = 361, /* WEST */ + CENTER = 362, /* CENTER */ + END = 363, /* END */ + START = 364, /* START */ + RESET = 365, /* RESET */ + UNTIL = 366, /* UNTIL */ + PLOT = 367, /* PLOT */ + THICKNESS = 368, /* THICKNESS */ + FILL = 369, /* FILL */ + COLORED = 370, /* COLORED */ + OUTLINED = 371, /* OUTLINED */ + SHADED = 372, /* SHADED */ + XSLANTED = 373, /* XSLANTED */ + YSLANTED = 374, /* YSLANTED */ + ALIGNED = 375, /* ALIGNED */ + SPRINTF = 376, /* SPRINTF */ + COMMAND = 377, /* COMMAND */ + DEFINE = 378, /* DEFINE */ + UNDEF = 379 /* UNDEF */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define LABEL 258 +#define VARIABLE 259 +#define NUMBER 260 +#define TEXT 261 +#define COMMAND_LINE 262 +#define DELIMITED 263 +#define ORDINAL 264 +#define TH 265 +#define LEFT_ARROW_HEAD 266 +#define RIGHT_ARROW_HEAD 267 +#define DOUBLE_ARROW_HEAD 268 +#define LAST 269 +#define BOX 270 +#define CIRCLE 271 +#define ELLIPSE 272 +#define ARC 273 +#define LINE 274 +#define ARROW 275 +#define MOVE 276 +#define SPLINE 277 +#define HEIGHT 278 +#define RADIUS 279 +#define FIGNAME 280 +#define WIDTH 281 +#define DIAMETER 282 +#define UP 283 +#define DOWN 284 +#define RIGHT 285 +#define LEFT 286 +#define FROM 287 +#define TO 288 +#define AT 289 +#define WITH 290 +#define BY 291 +#define THEN 292 +#define SOLID 293 +#define DOTTED 294 +#define DASHED 295 +#define CHOP 296 +#define SAME 297 +#define INVISIBLE 298 +#define LJUST 299 +#define RJUST 300 +#define ABOVE 301 +#define BELOW 302 +#define OF 303 +#define THE 304 +#define WAY 305 +#define BETWEEN 306 +#define AND 307 +#define HERE 308 +#define DOT_N 309 +#define DOT_E 310 +#define DOT_W 311 +#define DOT_S 312 +#define DOT_NE 313 +#define DOT_SE 314 +#define DOT_NW 315 +#define DOT_SW 316 +#define DOT_C 317 +#define DOT_START 318 +#define DOT_END 319 +#define DOT_X 320 +#define DOT_Y 321 +#define DOT_HT 322 +#define DOT_WID 323 +#define DOT_RAD 324 +#define SIN 325 +#define COS 326 +#define ATAN2 327 +#define LOG 328 +#define EXP 329 +#define SQRT 330 +#define K_MAX 331 +#define K_MIN 332 +#define INT 333 +#define RAND 334 +#define SRAND 335 +#define COPY 336 +#define THRU 337 +#define TOP 338 +#define BOTTOM 339 +#define UPPER 340 +#define LOWER 341 +#define SH 342 +#define PRINT 343 +#define CW 344 +#define CCW 345 +#define FOR 346 +#define DO 347 +#define IF 348 +#define ELSE 349 +#define ANDAND 350 +#define OROR 351 +#define NOTEQUAL 352 +#define EQUALEQUAL 353 +#define LESSEQUAL 354 +#define GREATEREQUAL 355 +#define LEFT_CORNER 356 +#define RIGHT_CORNER 357 +#define NORTH 358 +#define SOUTH 359 +#define EAST 360 +#define WEST 361 +#define CENTER 362 +#define END 363 +#define START 364 +#define RESET 365 +#define UNTIL 366 +#define PLOT 367 +#define THICKNESS 368 +#define FILL 369 +#define COLORED 370 +#define OUTLINED 371 +#define SHADED 372 +#define XSLANTED 373 +#define YSLANTED 374 +#define ALIGNED 375 +#define SPRINTF 376 +#define COMMAND 377 +#define DEFINE 378 +#define UNDEF 379 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 65 "../src/preproc/pic/pic.ypp" + + char *str; + int n; + double x; + struct { double x, y; } pair; + struct { double x; char *body; } if_data; + struct { char *str; const char *filename; int lineno; } lstr; + struct { double *v; int nv; int maxv; } dv; + struct { double val; int is_multiplicative; } by; + place pl; + object *obj; + corner crn; + path *pth; + object_spec *spec; + saved_state *pstate; + graphics_state state; + object_type obtype; + +#line 435 "src/preproc/pic/pic.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_PIC_PIC_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_LABEL = 3, /* LABEL */ + YYSYMBOL_VARIABLE = 4, /* VARIABLE */ + YYSYMBOL_NUMBER = 5, /* NUMBER */ + YYSYMBOL_TEXT = 6, /* TEXT */ + YYSYMBOL_COMMAND_LINE = 7, /* COMMAND_LINE */ + YYSYMBOL_DELIMITED = 8, /* DELIMITED */ + YYSYMBOL_ORDINAL = 9, /* ORDINAL */ + YYSYMBOL_TH = 10, /* TH */ + YYSYMBOL_LEFT_ARROW_HEAD = 11, /* LEFT_ARROW_HEAD */ + YYSYMBOL_RIGHT_ARROW_HEAD = 12, /* RIGHT_ARROW_HEAD */ + YYSYMBOL_DOUBLE_ARROW_HEAD = 13, /* DOUBLE_ARROW_HEAD */ + YYSYMBOL_LAST = 14, /* LAST */ + YYSYMBOL_BOX = 15, /* BOX */ + YYSYMBOL_CIRCLE = 16, /* CIRCLE */ + YYSYMBOL_ELLIPSE = 17, /* ELLIPSE */ + YYSYMBOL_ARC = 18, /* ARC */ + YYSYMBOL_LINE = 19, /* LINE */ + YYSYMBOL_ARROW = 20, /* ARROW */ + YYSYMBOL_MOVE = 21, /* MOVE */ + YYSYMBOL_SPLINE = 22, /* SPLINE */ + YYSYMBOL_HEIGHT = 23, /* HEIGHT */ + YYSYMBOL_RADIUS = 24, /* RADIUS */ + YYSYMBOL_FIGNAME = 25, /* FIGNAME */ + YYSYMBOL_WIDTH = 26, /* WIDTH */ + YYSYMBOL_DIAMETER = 27, /* DIAMETER */ + YYSYMBOL_UP = 28, /* UP */ + YYSYMBOL_DOWN = 29, /* DOWN */ + YYSYMBOL_RIGHT = 30, /* RIGHT */ + YYSYMBOL_LEFT = 31, /* LEFT */ + YYSYMBOL_FROM = 32, /* FROM */ + YYSYMBOL_TO = 33, /* TO */ + YYSYMBOL_AT = 34, /* AT */ + YYSYMBOL_WITH = 35, /* WITH */ + YYSYMBOL_BY = 36, /* BY */ + YYSYMBOL_THEN = 37, /* THEN */ + YYSYMBOL_SOLID = 38, /* SOLID */ + YYSYMBOL_DOTTED = 39, /* DOTTED */ + YYSYMBOL_DASHED = 40, /* DASHED */ + YYSYMBOL_CHOP = 41, /* CHOP */ + YYSYMBOL_SAME = 42, /* SAME */ + YYSYMBOL_INVISIBLE = 43, /* INVISIBLE */ + YYSYMBOL_LJUST = 44, /* LJUST */ + YYSYMBOL_RJUST = 45, /* RJUST */ + YYSYMBOL_ABOVE = 46, /* ABOVE */ + YYSYMBOL_BELOW = 47, /* BELOW */ + YYSYMBOL_OF = 48, /* OF */ + YYSYMBOL_THE = 49, /* THE */ + YYSYMBOL_WAY = 50, /* WAY */ + YYSYMBOL_BETWEEN = 51, /* BETWEEN */ + YYSYMBOL_AND = 52, /* AND */ + YYSYMBOL_HERE = 53, /* HERE */ + YYSYMBOL_DOT_N = 54, /* DOT_N */ + YYSYMBOL_DOT_E = 55, /* DOT_E */ + YYSYMBOL_DOT_W = 56, /* DOT_W */ + YYSYMBOL_DOT_S = 57, /* DOT_S */ + YYSYMBOL_DOT_NE = 58, /* DOT_NE */ + YYSYMBOL_DOT_SE = 59, /* DOT_SE */ + YYSYMBOL_DOT_NW = 60, /* DOT_NW */ + YYSYMBOL_DOT_SW = 61, /* DOT_SW */ + YYSYMBOL_DOT_C = 62, /* DOT_C */ + YYSYMBOL_DOT_START = 63, /* DOT_START */ + YYSYMBOL_DOT_END = 64, /* DOT_END */ + YYSYMBOL_DOT_X = 65, /* DOT_X */ + YYSYMBOL_DOT_Y = 66, /* DOT_Y */ + YYSYMBOL_DOT_HT = 67, /* DOT_HT */ + YYSYMBOL_DOT_WID = 68, /* DOT_WID */ + YYSYMBOL_DOT_RAD = 69, /* DOT_RAD */ + YYSYMBOL_SIN = 70, /* SIN */ + YYSYMBOL_COS = 71, /* COS */ + YYSYMBOL_ATAN2 = 72, /* ATAN2 */ + YYSYMBOL_LOG = 73, /* LOG */ + YYSYMBOL_EXP = 74, /* EXP */ + YYSYMBOL_SQRT = 75, /* SQRT */ + YYSYMBOL_K_MAX = 76, /* K_MAX */ + YYSYMBOL_K_MIN = 77, /* K_MIN */ + YYSYMBOL_INT = 78, /* INT */ + YYSYMBOL_RAND = 79, /* RAND */ + YYSYMBOL_SRAND = 80, /* SRAND */ + YYSYMBOL_COPY = 81, /* COPY */ + YYSYMBOL_THRU = 82, /* THRU */ + YYSYMBOL_TOP = 83, /* TOP */ + YYSYMBOL_BOTTOM = 84, /* BOTTOM */ + YYSYMBOL_UPPER = 85, /* UPPER */ + YYSYMBOL_LOWER = 86, /* LOWER */ + YYSYMBOL_SH = 87, /* SH */ + YYSYMBOL_PRINT = 88, /* PRINT */ + YYSYMBOL_CW = 89, /* CW */ + YYSYMBOL_CCW = 90, /* CCW */ + YYSYMBOL_FOR = 91, /* FOR */ + YYSYMBOL_DO = 92, /* DO */ + YYSYMBOL_IF = 93, /* IF */ + YYSYMBOL_ELSE = 94, /* ELSE */ + YYSYMBOL_ANDAND = 95, /* ANDAND */ + YYSYMBOL_OROR = 96, /* OROR */ + YYSYMBOL_NOTEQUAL = 97, /* NOTEQUAL */ + YYSYMBOL_EQUALEQUAL = 98, /* EQUALEQUAL */ + YYSYMBOL_LESSEQUAL = 99, /* LESSEQUAL */ + YYSYMBOL_GREATEREQUAL = 100, /* GREATEREQUAL */ + YYSYMBOL_LEFT_CORNER = 101, /* LEFT_CORNER */ + YYSYMBOL_RIGHT_CORNER = 102, /* RIGHT_CORNER */ + YYSYMBOL_NORTH = 103, /* NORTH */ + YYSYMBOL_SOUTH = 104, /* SOUTH */ + YYSYMBOL_EAST = 105, /* EAST */ + YYSYMBOL_WEST = 106, /* WEST */ + YYSYMBOL_CENTER = 107, /* CENTER */ + YYSYMBOL_END = 108, /* END */ + YYSYMBOL_START = 109, /* START */ + YYSYMBOL_RESET = 110, /* RESET */ + YYSYMBOL_UNTIL = 111, /* UNTIL */ + YYSYMBOL_PLOT = 112, /* PLOT */ + YYSYMBOL_THICKNESS = 113, /* THICKNESS */ + YYSYMBOL_FILL = 114, /* FILL */ + YYSYMBOL_COLORED = 115, /* COLORED */ + YYSYMBOL_OUTLINED = 116, /* OUTLINED */ + YYSYMBOL_SHADED = 117, /* SHADED */ + YYSYMBOL_XSLANTED = 118, /* XSLANTED */ + YYSYMBOL_YSLANTED = 119, /* YSLANTED */ + YYSYMBOL_ALIGNED = 120, /* ALIGNED */ + YYSYMBOL_SPRINTF = 121, /* SPRINTF */ + YYSYMBOL_COMMAND = 122, /* COMMAND */ + YYSYMBOL_DEFINE = 123, /* DEFINE */ + YYSYMBOL_UNDEF = 124, /* UNDEF */ + YYSYMBOL_125_ = 125, /* '.' */ + YYSYMBOL_126_ = 126, /* '(' */ + YYSYMBOL_127_ = 127, /* '`' */ + YYSYMBOL_128_ = 128, /* '[' */ + YYSYMBOL_129_ = 129, /* ',' */ + YYSYMBOL_130_ = 130, /* '<' */ + YYSYMBOL_131_ = 131, /* '>' */ + YYSYMBOL_132_ = 132, /* '+' */ + YYSYMBOL_133_ = 133, /* '-' */ + YYSYMBOL_134_ = 134, /* '*' */ + YYSYMBOL_135_ = 135, /* '/' */ + YYSYMBOL_136_ = 136, /* '%' */ + YYSYMBOL_137_ = 137, /* '!' */ + YYSYMBOL_138_ = 138, /* '^' */ + YYSYMBOL_139_ = 139, /* ';' */ + YYSYMBOL_140_ = 140, /* '=' */ + YYSYMBOL_141_ = 141, /* ':' */ + YYSYMBOL_142_ = 142, /* '{' */ + YYSYMBOL_143_ = 143, /* '}' */ + YYSYMBOL_144_ = 144, /* ']' */ + YYSYMBOL_145_ = 145, /* ')' */ + YYSYMBOL_YYACCEPT = 146, /* $accept */ + YYSYMBOL_top = 147, /* top */ + YYSYMBOL_element_list = 148, /* element_list */ + YYSYMBOL_middle_element_list = 149, /* middle_element_list */ + YYSYMBOL_optional_separator = 150, /* optional_separator */ + YYSYMBOL_separator = 151, /* separator */ + YYSYMBOL_placeless_element = 152, /* placeless_element */ + YYSYMBOL_153_1 = 153, /* $@1 */ + YYSYMBOL_154_2 = 154, /* $@2 */ + YYSYMBOL_155_3 = 155, /* $@3 */ + YYSYMBOL_156_4 = 156, /* $@4 */ + YYSYMBOL_157_5 = 157, /* $@5 */ + YYSYMBOL_158_6 = 158, /* $@6 */ + YYSYMBOL_159_7 = 159, /* $@7 */ + YYSYMBOL_macro_name = 160, /* macro_name */ + YYSYMBOL_reset_variables = 161, /* reset_variables */ + YYSYMBOL_print_args = 162, /* print_args */ + YYSYMBOL_print_arg = 163, /* print_arg */ + YYSYMBOL_simple_if = 164, /* simple_if */ + YYSYMBOL_165_8 = 165, /* $@8 */ + YYSYMBOL_until = 166, /* until */ + YYSYMBOL_any_expr = 167, /* any_expr */ + YYSYMBOL_text_expr = 168, /* text_expr */ + YYSYMBOL_optional_by = 169, /* optional_by */ + YYSYMBOL_element = 170, /* element */ + YYSYMBOL_171_9 = 171, /* @9 */ + YYSYMBOL_172_10 = 172, /* $@10 */ + YYSYMBOL_optional_element = 173, /* optional_element */ + YYSYMBOL_object_spec = 174, /* object_spec */ + YYSYMBOL_175_11 = 175, /* @11 */ + YYSYMBOL_text = 176, /* text */ + YYSYMBOL_sprintf_args = 177, /* sprintf_args */ + YYSYMBOL_position = 178, /* position */ + YYSYMBOL_position_not_place = 179, /* position_not_place */ + YYSYMBOL_between = 180, /* between */ + YYSYMBOL_expr_pair = 181, /* expr_pair */ + YYSYMBOL_place = 182, /* place */ + YYSYMBOL_label = 183, /* label */ + YYSYMBOL_ordinal = 184, /* ordinal */ + YYSYMBOL_optional_ordinal_last = 185, /* optional_ordinal_last */ + YYSYMBOL_nth_primitive = 186, /* nth_primitive */ + YYSYMBOL_object_type = 187, /* object_type */ + YYSYMBOL_label_path = 188, /* label_path */ + YYSYMBOL_relative_path = 189, /* relative_path */ + YYSYMBOL_path = 190, /* path */ + YYSYMBOL_corner = 191, /* corner */ + YYSYMBOL_expr = 192, /* expr */ + YYSYMBOL_expr_lower_than = 193, /* expr_lower_than */ + YYSYMBOL_expr_not_lower_than = 194 /* expr_not_lower_than */ +}; +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_int16 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 6 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 2438 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 146 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 49 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 260 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 454 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 379 + + +/* 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_uint8 yytranslate[] = +{ + 0, 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, 137, 2, 2, 2, 136, 2, 2, + 126, 145, 134, 132, 129, 133, 125, 135, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 141, 139, + 130, 140, 131, 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, 128, 2, 144, 138, 2, 127, 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, 142, 2, 143, 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, 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, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124 +}; + +#if YYDEBUG +/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_int16 yyrline[] = +{ + 0, 275, 275, 276, 285, 290, 292, 296, 298, 302, + 303, 307, 315, 320, 332, 334, 336, 338, 340, 345, + 350, 357, 356, 372, 380, 382, 379, 393, 395, 392, + 405, 404, 413, 422, 421, 435, 436, 441, 442, 446, + 451, 456, 464, 466, 485, 492, 494, 505, 504, 516, + 517, 522, 524, 529, 535, 541, 543, 545, 547, 549, + 551, 553, 560, 564, 569, 577, 591, 597, 605, 612, + 618, 611, 627, 637, 638, 643, 645, 647, 649, 654, + 661, 668, 675, 682, 687, 694, 702, 701, 728, 734, + 740, 746, 752, 771, 778, 785, 792, 799, 806, 813, + 820, 827, 834, 849, 861, 867, 876, 883, 908, 912, + 918, 924, 930, 936, 941, 947, 953, 959, 966, 975, + 982, 998, 1015, 1020, 1025, 1030, 1035, 1040, 1045, 1050, + 1058, 1068, 1078, 1088, 1098, 1104, 1112, 1114, 1126, 1131, + 1161, 1163, 1169, 1178, 1180, 1185, 1190, 1195, 1200, 1205, + 1210, 1216, 1221, 1229, 1230, 1234, 1239, 1245, 1247, 1253, + 1259, 1265, 1274, 1284, 1286, 1295, 1297, 1305, 1307, 1312, + 1327, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359, 1361, + 1366, 1368, 1376, 1380, 1382, 1390, 1392, 1398, 1404, 1410, + 1416, 1425, 1427, 1429, 1431, 1433, 1435, 1437, 1439, 1441, + 1443, 1445, 1447, 1449, 1451, 1453, 1455, 1457, 1459, 1461, + 1463, 1465, 1467, 1469, 1471, 1473, 1475, 1477, 1479, 1481, + 1483, 1485, 1487, 1492, 1494, 1499, 1504, 1512, 1514, 1521, + 1528, 1535, 1542, 1549, 1551, 1553, 1555, 1563, 1571, 1584, + 1586, 1588, 1597, 1606, 1619, 1628, 1637, 1646, 1648, 1650, + 1652, 1659, 1665, 1670, 1672, 1674, 1676, 1678, 1680, 1682, + 1684 +}; +#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\"", "LABEL", "VARIABLE", + "NUMBER", "TEXT", "COMMAND_LINE", "DELIMITED", "ORDINAL", "TH", + "LEFT_ARROW_HEAD", "RIGHT_ARROW_HEAD", "DOUBLE_ARROW_HEAD", "LAST", + "BOX", "CIRCLE", "ELLIPSE", "ARC", "LINE", "ARROW", "MOVE", "SPLINE", + "HEIGHT", "RADIUS", "FIGNAME", "WIDTH", "DIAMETER", "UP", "DOWN", + "RIGHT", "LEFT", "FROM", "TO", "AT", "WITH", "BY", "THEN", "SOLID", + "DOTTED", "DASHED", "CHOP", "SAME", "INVISIBLE", "LJUST", "RJUST", + "ABOVE", "BELOW", "OF", "THE", "WAY", "BETWEEN", "AND", "HERE", "DOT_N", + "DOT_E", "DOT_W", "DOT_S", "DOT_NE", "DOT_SE", "DOT_NW", "DOT_SW", + "DOT_C", "DOT_START", "DOT_END", "DOT_X", "DOT_Y", "DOT_HT", "DOT_WID", + "DOT_RAD", "SIN", "COS", "ATAN2", "LOG", "EXP", "SQRT", "K_MAX", "K_MIN", + "INT", "RAND", "SRAND", "COPY", "THRU", "TOP", "BOTTOM", "UPPER", + "LOWER", "SH", "PRINT", "CW", "CCW", "FOR", "DO", "IF", "ELSE", "ANDAND", + "OROR", "NOTEQUAL", "EQUALEQUAL", "LESSEQUAL", "GREATEREQUAL", + "LEFT_CORNER", "RIGHT_CORNER", "NORTH", "SOUTH", "EAST", "WEST", + "CENTER", "END", "START", "RESET", "UNTIL", "PLOT", "THICKNESS", "FILL", + "COLORED", "OUTLINED", "SHADED", "XSLANTED", "YSLANTED", "ALIGNED", + "SPRINTF", "COMMAND", "DEFINE", "UNDEF", "'.'", "'('", "'`'", "'['", + "','", "'<'", "'>'", "'+'", "'-'", "'*'", "'/'", "'%'", "'!'", "'^'", + "';'", "'='", "':'", "'{'", "'}'", "']'", "')'", "$accept", "top", + "element_list", "middle_element_list", "optional_separator", "separator", + "placeless_element", "$@1", "$@2", "$@3", "$@4", "$@5", "$@6", "$@7", + "macro_name", "reset_variables", "print_args", "print_arg", "simple_if", + "$@8", "until", "any_expr", "text_expr", "optional_by", "element", "@9", + "$@10", "optional_element", "object_spec", "@11", "text", "sprintf_args", + "position", "position_not_place", "between", "expr_pair", "place", + "label", "ordinal", "optional_ordinal_last", "nth_primitive", + "object_type", "label_path", "relative_path", "path", "corner", "expr", + "expr_lower_than", "expr_not_lower_than", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#define YYPACT_NINF (-240) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-206) + +#define yytable_value_is_error(Yyn) \ + 0 + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + -114, -240, 20, -240, 757, -107, -240, -98, -123, -240, + -240, -240, -240, -240, -240, -240, -240, -240, -240, -106, + -240, -240, -240, -240, 9, -240, 1087, 46, 1172, 49, + 1597, -70, 1087, -240, -240, -114, -240, 3, -33, -240, + 877, -240, -240, -114, 1172, -60, 36, -14, -240, 74, + -240, -240, -240, -240, -240, -240, -240, -240, -240, -240, + -240, -240, -240, -240, -240, -240, -240, -240, -240, -34, + -18, 8, 38, 47, 51, 65, 101, 102, 112, 122, + -240, -240, 21, 150, -240, -240, -240, -240, -240, -240, + -240, -240, -240, 1257, 1172, 1597, 1597, 1087, -240, -240, + -43, -240, -240, 357, 2242, 59, 258, -240, 10, 2147, + -240, 1, 6, 1172, 1172, 145, -1, 2, 357, 2273, + -240, -240, 220, 249, 1087, -114, -114, -240, 721, -240, + 252, -240, -240, -240, -240, 1597, 1597, 1597, 1597, 2024, + 2024, 1853, 1939, 1682, 1682, 1682, 1427, 1767, -240, -240, + 2024, 2024, 2024, -240, -240, -240, -240, -240, -240, -240, + -240, 1597, 2024, 23, 23, 23, 1597, 1597, -240, -240, + 2282, 593, -240, 1172, -240, -240, -240, -240, 250, -240, + 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 458, + 1172, -240, -240, -240, -240, -240, -240, -240, -240, 121, + 107, 123, 256, 2157, 137, 261, 134, 134, -240, 1767, + 1767, -240, -240, -240, -240, -240, 276, -240, -240, -240, + -240, -240, -240, -240, -240, -240, -240, 138, -240, -240, + 24, 156, 235, -240, 1597, 1597, 1597, 1597, 1597, 1597, + 1597, 1597, 1597, 1597, 1597, 1597, 1597, 1597, 1597, 1682, + 1682, 1597, -240, 134, -240, 1172, 1172, 23, 23, 1172, + 1172, -240, -240, 143, 757, 153, -240, -240, 280, 2282, + 2282, 2282, 2282, 2282, 2282, 2282, 2282, -43, 2147, -43, + -43, 2253, 275, 275, 295, 1002, -43, 2081, -240, -240, + 10, 1342, -240, 694, 2282, 2282, 2282, 2282, 2282, -240, + -240, -240, 2282, 2282, -98, -123, 16, 28, -240, -43, + 56, 302, -240, 291, -240, 155, 160, 172, 161, 164, + 167, 184, 185, 181, -240, 186, 188, -240, 1682, 1767, + 1767, -240, -240, 1682, 1682, -240, -240, -240, -240, -240, + 156, 279, 314, 2291, 440, 440, 413, 413, 2282, 413, + 413, -72, -72, 134, 134, 134, 134, -49, 117, 343, + 322, -240, 314, 239, 2300, -240, -240, -240, 314, 239, + 2300, -119, -240, -240, -240, -240, -240, 2116, 2116, -240, + 206, 333, -240, 123, 2131, -240, 228, -240, -240, 1172, + -240, -240, -240, 1172, 1172, -240, -240, -240, -110, 195, + 197, -47, 128, 292, 1682, 1682, 1597, -240, 1597, -240, + 757, -240, -240, 2116, -240, 228, 338, -240, 200, 202, + 212, -240, -240, -240, 1682, 1682, -240, -43, -27, 360, + 2282, -240, -240, 214, -240, -240, -240, -240, -240, -73, + 30, -240, 1512, 268, -240, -240, 216, 1597, 2282, -240, + -240, 2282, 354, -240 +}; + +/* 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_int16 yydefact[] = +{ + 7, 9, 0, 3, 2, 8, 1, 0, 0, 136, + 18, 75, 76, 77, 78, 79, 80, 81, 82, 0, + 14, 15, 17, 16, 0, 21, 0, 0, 0, 36, + 0, 0, 0, 86, 69, 7, 72, 35, 32, 5, + 65, 83, 10, 7, 0, 0, 0, 23, 27, 0, + 162, 226, 227, 165, 167, 205, 204, 161, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 202, 203, 0, 0, 210, 211, 216, 217, 218, 219, + 220, 222, 221, 0, 0, 0, 0, 20, 42, 45, + 46, 140, 143, 141, 157, 0, 0, 163, 0, 44, + 223, 224, 0, 0, 0, 0, 52, 0, 0, 51, + 224, 39, 84, 0, 19, 7, 7, 4, 8, 40, + 0, 33, 124, 125, 126, 0, 0, 0, 0, 93, + 95, 97, 99, 0, 0, 0, 0, 0, 107, 108, + 109, 111, 120, 122, 123, 130, 131, 132, 133, 127, + 128, 0, 113, 0, 0, 0, 0, 0, 135, 129, + 92, 0, 12, 0, 38, 37, 11, 24, 0, 22, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 208, 206, 212, 214, 209, 207, 213, 215, 0, + 0, 143, 141, 51, 224, 0, 239, 260, 43, 0, + 0, 228, 229, 230, 231, 232, 0, 158, 179, 168, + 171, 172, 173, 174, 175, 176, 177, 0, 169, 170, + 0, 159, 0, 153, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 61, 260, 47, 0, 0, 0, 0, 0, + 0, 85, 138, 0, 0, 0, 6, 41, 0, 88, + 89, 90, 91, 94, 96, 98, 100, 101, 0, 102, + 103, 162, 165, 167, 0, 0, 105, 183, 185, 104, + 182, 0, 106, 0, 110, 112, 121, 134, 114, 118, + 119, 117, 115, 116, 162, 226, 205, 204, 66, 0, + 67, 68, 13, 0, 28, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 251, 0, 0, 240, 0, 0, + 0, 156, 142, 0, 0, 166, 144, 146, 164, 178, + 160, 0, 258, 259, 257, 256, 253, 255, 155, 225, + 254, 233, 234, 235, 236, 237, 238, 0, 0, 0, + 0, 55, 56, 58, 59, 54, 53, 57, 258, 60, + 259, 0, 87, 70, 34, 190, 182, 0, 0, 180, + 0, 0, 184, 0, 51, 25, 49, 241, 242, 0, + 244, 245, 246, 0, 0, 249, 250, 252, 0, 144, + 146, 0, 0, 0, 0, 0, 0, 48, 0, 137, + 73, 189, 188, 0, 181, 49, 0, 29, 0, 0, + 0, 148, 145, 147, 0, 0, 154, 149, 0, 62, + 139, 74, 71, 0, 26, 50, 243, 247, 248, 149, + 0, 151, 0, 0, 186, 150, 151, 0, 63, 30, + 152, 64, 0, 31 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -240, -240, 17, -240, 12, 329, -240, -240, -240, -240, + -240, -240, -240, -240, -240, -240, 334, -76, -240, -240, + -42, 13, -103, -240, -127, -240, -240, -240, -240, -240, + 5, -240, 99, 194, 169, -44, 4, -100, -240, -240, + -240, -104, -240, -239, -240, -50, -26, -240, 61 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + 0, 2, 3, 35, 264, 5, 36, 49, 313, 415, + 178, 386, 452, 268, 176, 37, 97, 98, 38, 360, + 417, 199, 116, 443, 39, 126, 410, 432, 40, 125, + 117, 371, 100, 101, 249, 102, 118, 104, 105, 106, + 107, 228, 287, 288, 289, 108, 119, 110, 120 +}; + +/* 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_int16 yytable[] = +{ + 109, 266, 229, 404, 122, 424, 109, 129, 231, 41, + 408, 252, 4, 50, 170, 47, -17, 44, 45, 53, + 6, 208, 209, 210, 54, 1, 409, 50, -16, 9, + 103, 99, 42, 53, 46, 421, 103, 99, 54, 174, + 175, 115, 375, 43, 308, 169, 380, 127, 208, 201, + 112, 191, 192, 121, 217, 171, 123, 172, 230, 209, + 210, 131, 245, 246, 247, 218, 248, 203, 177, 206, + 207, 109, 445, 219, 220, 221, 222, 223, 224, 225, + 173, 226, 179, 209, 210, 209, 210, 111, 253, 209, + 210, 48, 180, 111, 255, 256, 290, 202, 109, 257, + 258, 103, 99, 292, 441, 209, 210, 205, 181, 269, + 270, 271, 272, 273, 274, 275, 276, 278, 278, 278, + 278, 293, 193, 194, 294, 295, 296, 261, 103, 99, + 340, 250, 130, 41, 182, 297, 298, 94, 411, 412, + 302, 303, 263, 265, 31, 278, 251, 103, 103, 103, + 103, 94, 361, 363, 204, -17, 367, 369, 111, -17, + -17, 446, 209, 210, 183, 336, 337, -16, 299, 300, + 301, -16, -16, 184, 433, 311, 41, 185, 377, 378, + 195, 196, 254, 293, 293, 111, 312, 227, -140, -140, + 231, 186, 200, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 325, 326, 111, 111, 111, 111, 342, 343, + 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, + 354, 355, 356, 278, 278, 359, 9, 187, 188, 362, + 364, 376, 111, 368, 370, 290, 328, 382, 189, 329, + 330, 201, 277, 279, 280, 286, 405, 383, 190, 209, + 210, 197, 198, 103, 103, 262, 267, 425, 314, 203, + 209, 210, 365, 366, 218, 384, 327, 334, 331, 41, + 309, 335, 248, 220, 221, 222, 223, 224, 225, 338, + 226, 216, 339, 431, 341, 399, 400, 372, 374, 202, + 220, 221, 222, 223, 224, 225, 373, 226, 379, 385, + 387, 389, 278, 293, 293, 388, 390, 278, 278, 391, + 111, 111, 392, 393, 394, 234, 235, 236, 237, 238, + 239, 211, 212, 213, 214, 215, 395, 376, 376, 403, + 407, 396, 103, 397, 255, 413, 414, 103, 103, 416, + 422, 31, 423, 426, 435, 436, 204, 437, 357, 358, + 241, 242, 243, 244, 245, 246, 247, 438, 248, 444, + 449, 450, 453, 376, 128, 310, 124, 211, 212, 213, + 214, 215, 333, 434, 0, 0, 406, 0, 278, 278, + 429, 0, 430, 0, 200, 0, 227, 0, 0, 111, + 0, 0, 0, 0, 111, 111, 442, 0, 278, 278, + 0, 332, 418, 227, 0, 0, 419, 420, 103, 103, + 0, 236, 237, 238, 239, 41, 448, 0, 0, 0, + 0, 451, 211, 212, 213, 214, 215, 398, 103, 103, + 0, 0, 401, 402, -141, -141, 0, 0, 234, 235, + 236, 237, 238, 239, 241, 242, 243, 244, 245, 246, + 247, 0, 248, 0, 0, 234, 235, 236, 237, 238, + 239, 50, 51, 52, 9, 111, 111, 53, 0, 0, + 0, 0, 54, 241, 242, 243, 244, 245, 246, 247, + 0, 248, 0, 0, 0, 111, 111, 0, 55, 56, + 241, 242, 243, 244, 245, 246, 247, 0, 248, 0, + 0, 0, 0, 427, 428, 0, 0, 0, 0, 0, + 0, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 439, 440, 0, 0, 0, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 238, + 239, 80, 81, 82, 83, 243, 244, 245, 246, 247, + 0, 248, 0, 0, 0, 0, 0, 0, 0, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 0, 0, + 241, 242, 243, 244, 245, 246, 247, 0, 248, 31, + 0, 0, 0, 0, 113, 94, 0, 0, 0, 0, + 0, 95, 0, 0, 0, 114, 304, 305, 52, 9, + 10, 0, 53, 324, 0, 0, 0, 54, 11, 12, + 13, 14, 15, 16, 17, 18, 0, 0, 19, 0, + 0, 20, 21, 306, 307, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 0, 0, + 0, 0, 0, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 24, 0, 80, 81, 82, 83, + 25, 26, 0, 0, 27, 0, 28, 0, 0, 0, + 0, 0, 0, 0, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 29, 0, 30, 0, 0, 0, 0, + 0, 0, 0, 0, 31, 32, 0, 0, 0, 93, + 94, 33, 0, 0, 7, 8, 95, 9, 10, 0, + 96, 0, 0, 0, 0, 34, 11, 12, 13, 14, + 15, 16, 17, 18, 0, 0, 19, 0, 0, 20, + 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, + 7, 8, 0, 9, 10, 0, 0, 0, 0, 0, + 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, + 0, 0, 19, 0, 0, 20, 21, 22, 23, 234, + 235, 236, 237, 238, 239, 0, 0, 0, 0, 0, + 0, 0, 24, 0, 0, 0, 0, 0, 25, 26, + 0, 0, 27, 0, 28, 0, 0, 0, 0, 0, + 0, 0, 0, 240, 241, 242, 243, 244, 245, 246, + 247, 29, 248, 30, 0, 0, 0, 0, 24, 0, + 0, 0, 31, 32, 25, 26, 0, 0, 27, 33, + 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 42, 0, 0, 34, 0, 0, 0, 29, 0, 30, + 0, 0, 0, 0, 0, 0, 0, 0, 31, 32, + 50, 51, 52, 9, 0, 33, 53, 0, 132, 133, + 134, 54, 0, 0, 0, 0, 0, 0, 0, 34, + 135, 136, 0, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + 154, 155, 156, 157, 158, 0, 0, 0, 0, 0, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 0, 0, 0, 0, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, + 80, 81, 82, 83, 0, 0, 159, 160, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 0, 0, 0, + 161, 162, 163, 164, 165, 166, 167, 168, 31, 0, + 0, 0, 0, 113, 94, 50, 51, 52, 9, 0, + 95, 53, 0, 0, 96, 0, 54, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 55, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 0, 0, 0, + 0, 0, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 0, 0, 80, 81, 82, 83, 0, + 50, 51, 52, 9, 0, 0, 53, 0, 0, 0, + 0, 54, 0, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 0, 0, 0, 0, 0, 55, 56, 0, + 0, 0, 0, 31, 0, 0, 0, 284, 93, 94, + 0, 0, 0, 0, 0, 95, 0, 0, 0, 114, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 0, 0, 0, 0, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, + 80, 81, 82, 83, 0, 50, 51, 52, 9, 0, + 0, 53, 0, 0, 0, 0, 54, 0, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 0, 0, 0, + 0, 0, 55, 56, 0, 0, 0, 0, 31, 0, + 0, 0, 0, 93, 94, 0, 0, 0, 0, 0, + 95, 0, 0, 0, 96, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 0, 0, 0, + 0, 0, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 0, 0, 80, 81, 82, 83, 0, + 50, 51, 52, 9, 0, 0, 53, 0, 0, 0, + 0, 54, 0, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 0, 0, 0, 0, 0, 55, 56, 0, + 0, 0, 0, 31, 0, 0, 0, 0, 113, 94, + 0, 0, 0, 0, 0, 95, 0, 0, 0, 114, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 0, 0, 0, 0, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, + 80, 81, 82, 83, 0, 50, 51, 52, 9, 0, + 0, 53, 0, 0, 0, 0, 54, 0, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 0, 0, 0, + 0, 0, 55, 56, 0, 0, 0, 0, 31, 0, + 0, 0, 0, 93, 94, 0, 0, 0, 0, 0, + 95, 0, 0, 0, 114, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 0, 0, 0, + 0, 0, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 0, 0, 80, 81, 82, 83, 0, + 281, 51, 52, 0, 0, 0, 282, 0, 0, 0, + 0, 283, 0, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 0, 0, 0, 0, 0, 55, 56, 0, + 0, 0, 0, 31, 0, 0, 0, 0, 291, 94, + 0, 0, 0, 0, 0, 95, 0, 0, 0, 114, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 0, 0, 0, 0, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, + 80, 81, 82, 83, 0, 50, 51, 52, 0, 0, + 0, 53, 0, 0, 0, 0, 54, 0, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 0, 0, 0, + 0, 0, 55, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 284, 285, 94, 0, 0, 0, 0, 0, + 95, 0, 0, 0, 96, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 0, 0, 0, + 0, 0, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 0, 0, 80, 81, 82, 83, 0, + 50, 51, 52, 0, 0, 0, 53, 0, 0, 0, + 0, 54, 0, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 0, 0, 0, 0, 0, 55, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 113, 94, + 0, 0, 0, 0, 0, 95, 447, 0, 0, 96, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 0, 0, 0, 0, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, + 80, 81, 82, 83, 0, 50, 51, 52, 0, 0, + 0, 53, 0, 0, 0, 0, 54, 0, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 0, 0, 0, + 0, 0, 55, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 113, 94, 0, 0, 0, 0, 0, + 95, 0, 0, 0, 96, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 0, 0, 0, + 0, 0, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 0, 0, 80, 81, 82, 83, 0, + 50, 51, 52, 0, 0, 0, 53, 0, 0, 0, + 0, 54, 0, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 0, 0, 0, 0, 0, 55, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 93, 94, + 0, 0, 0, 0, 0, 95, 0, 0, 0, 96, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 0, 0, 0, 0, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, + 80, 81, 82, 83, 0, 0, 50, 51, 52, 0, + 0, 0, 53, 0, 0, 0, 0, 54, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 291, 94, 0, 0, 0, 0, 0, + 95, -205, 0, 0, 96, 0, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 0, 0, + 0, 0, 0, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 0, 0, 80, 81, 82, 83, + 0, 0, 50, 51, 52, 0, 0, 0, 53, 0, + 0, 0, 0, 54, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, + 94, 0, 0, 0, 0, 0, 95, -204, 0, 0, + 96, 0, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 0, 0, 0, 0, 0, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 0, 0, 80, 81, 82, 83, 0, 50, 51, 52, + 0, 0, 0, 53, 0, 0, 0, 0, 54, 0, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 113, 94, 0, 0, 0, + 0, 0, 95, 0, 0, 0, 96, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 0, + 0, 0, 0, 0, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 0, 0, 80, 81, 82, + 83, 55, 56, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 0, 58, 59, 60, 61, 62, + 63, 64, 65, 66, 67, 68, 55, 56, 0, 0, + 113, 94, 0, 0, 0, 0, 0, 95, 0, 0, + 0, 96, 0, 0, 80, 81, 82, 83, 0, 0, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 0, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 0, 0, 0, 0, 232, 0, 0, 233, 80, + 81, 82, 83, 0, 0, 232, 381, 0, 233, 0, + 0, 0, 0, 0, 0, 0, 0, 84, 85, 86, + 87, 88, 89, 90, 91, 92, 259, 260, 236, 237, + 238, 239, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 284, 234, 235, 236, 237, 238, 239, 0, 0, + 0, 0, 259, 260, 236, 237, 238, 239, 0, 0, + 240, 241, 242, 243, 244, 245, 246, 247, 0, 248, + 0, 0, 0, 0, 0, 0, 240, 241, 242, 243, + 244, 245, 246, 247, 0, 248, 240, 241, 242, 243, + 244, 245, 246, 247, 0, 248, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 0, 0, + 0, 0, 0, 0, 0, 80, 81, 82, 83, 0, + 0, 0, 0, 0, 0, 0, 80, 81, 82, 83, + 0, 0, 0, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 0, 0, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 0, 0, 0, 0, 216, 259, 260, + 236, 237, 238, 239, 0, 0, 0, 234, 235, 236, + 237, 238, 239, 0, 0, 0, 234, 0, 236, 237, + 238, 239, 0, 0, 0, 259, 0, 236, 237, 238, + 239, 0, 0, 241, 242, 243, 244, 245, 246, 247, + 0, 248, 241, 242, 243, 244, 245, 246, 247, 0, + 248, 241, 242, 243, 244, 245, 246, 247, 0, 248, + 241, 242, 243, 244, 245, 246, 247, 0, 248 +}; + +static const yytype_int16 yycheck[] = +{ + 26, 128, 106, 52, 30, 52, 32, 4, 108, 4, + 129, 114, 0, 3, 40, 6, 0, 140, 141, 9, + 0, 97, 132, 133, 14, 139, 145, 3, 0, 6, + 26, 26, 139, 9, 140, 145, 32, 32, 14, 3, + 4, 28, 281, 141, 171, 40, 285, 35, 124, 93, + 4, 30, 31, 4, 104, 43, 126, 44, 48, 132, + 133, 94, 134, 135, 136, 6, 138, 93, 82, 95, + 96, 97, 145, 14, 15, 16, 17, 18, 19, 20, + 140, 22, 8, 132, 133, 132, 133, 26, 114, 132, + 133, 82, 126, 32, 95, 96, 146, 93, 124, 97, + 98, 97, 97, 147, 131, 132, 133, 94, 126, 135, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, + 146, 147, 101, 102, 150, 151, 152, 122, 124, 124, + 230, 130, 129, 128, 126, 161, 162, 127, 377, 378, + 166, 167, 125, 126, 121, 171, 140, 143, 144, 145, + 146, 127, 255, 256, 93, 139, 259, 260, 97, 143, + 144, 131, 132, 133, 126, 209, 210, 139, 163, 164, + 165, 143, 144, 126, 413, 171, 171, 126, 282, 283, + 30, 31, 37, 209, 210, 124, 173, 128, 132, 133, + 290, 126, 93, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 190, 143, 144, 145, 146, 234, 235, + 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, + 246, 247, 248, 249, 250, 251, 6, 126, 126, 255, + 256, 281, 171, 259, 260, 285, 129, 287, 126, 132, + 133, 285, 143, 144, 145, 146, 129, 291, 126, 132, + 133, 101, 102, 249, 250, 6, 4, 129, 8, 285, + 132, 133, 257, 258, 6, 291, 145, 130, 145, 264, + 171, 10, 138, 15, 16, 17, 18, 19, 20, 3, + 22, 125, 144, 410, 49, 329, 330, 144, 8, 285, + 15, 16, 17, 18, 19, 20, 143, 22, 3, 8, + 145, 129, 328, 329, 330, 145, 145, 333, 334, 145, + 249, 250, 145, 129, 129, 95, 96, 97, 98, 99, + 100, 65, 66, 67, 68, 69, 145, 377, 378, 50, + 8, 145, 328, 145, 95, 129, 3, 333, 334, 111, + 145, 121, 145, 51, 6, 145, 285, 145, 249, 250, + 130, 131, 132, 133, 134, 135, 136, 145, 138, 145, + 92, 145, 8, 413, 35, 171, 32, 65, 66, 67, + 68, 69, 203, 415, -1, -1, 33, -1, 404, 405, + 406, -1, 408, -1, 285, -1, 128, -1, -1, 328, + -1, -1, -1, -1, 333, 334, 36, -1, 424, 425, + -1, 145, 389, 128, -1, -1, 393, 394, 404, 405, + -1, 97, 98, 99, 100, 410, 442, -1, -1, -1, + -1, 447, 65, 66, 67, 68, 69, 328, 424, 425, + -1, -1, 333, 334, 132, 133, -1, -1, 95, 96, + 97, 98, 99, 100, 130, 131, 132, 133, 134, 135, + 136, -1, 138, -1, -1, 95, 96, 97, 98, 99, + 100, 3, 4, 5, 6, 404, 405, 9, -1, -1, + -1, -1, 14, 130, 131, 132, 133, 134, 135, 136, + -1, 138, -1, -1, -1, 424, 425, -1, 30, 31, + 130, 131, 132, 133, 134, 135, 136, -1, 138, -1, + -1, -1, -1, 404, 405, -1, -1, -1, -1, -1, + -1, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 424, 425, -1, -1, -1, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 99, + 100, 83, 84, 85, 86, 132, 133, 134, 135, 136, + -1, 138, -1, -1, -1, -1, -1, -1, -1, 101, + 102, 103, 104, 105, 106, 107, 108, 109, -1, -1, + 130, 131, 132, 133, 134, 135, 136, -1, 138, 121, + -1, -1, -1, -1, 126, 127, -1, -1, -1, -1, + -1, 133, -1, -1, -1, 137, 3, 4, 5, 6, + 7, -1, 9, 145, -1, -1, -1, 14, 15, 16, + 17, 18, 19, 20, 21, 22, -1, -1, 25, -1, + -1, 28, 29, 30, 31, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, -1, -1, + -1, -1, -1, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 80, 81, -1, 83, 84, 85, 86, + 87, 88, -1, -1, 91, -1, 93, -1, -1, -1, + -1, -1, -1, -1, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, -1, 112, -1, -1, -1, -1, + -1, -1, -1, -1, 121, 122, -1, -1, -1, 126, + 127, 128, -1, -1, 3, 4, 133, 6, 7, -1, + 137, -1, -1, -1, -1, 142, 15, 16, 17, 18, + 19, 20, 21, 22, -1, -1, 25, -1, -1, 28, + 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, + 3, 4, -1, 6, 7, -1, -1, -1, -1, -1, + -1, -1, 15, 16, 17, 18, 19, 20, 21, 22, + -1, -1, 25, -1, -1, 28, 29, 30, 31, 95, + 96, 97, 98, 99, 100, -1, -1, -1, -1, -1, + -1, -1, 81, -1, -1, -1, -1, -1, 87, 88, + -1, -1, 91, -1, 93, -1, -1, -1, -1, -1, + -1, -1, -1, 129, 130, 131, 132, 133, 134, 135, + 136, 110, 138, 112, -1, -1, -1, -1, 81, -1, + -1, -1, 121, 122, 87, 88, -1, -1, 91, 128, + 93, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 139, -1, -1, 142, -1, -1, -1, 110, -1, 112, + -1, -1, -1, -1, -1, -1, -1, -1, 121, 122, + 3, 4, 5, 6, -1, 128, 9, -1, 11, 12, + 13, 14, -1, -1, -1, -1, -1, -1, -1, 142, + 23, 24, -1, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, -1, -1, -1, -1, -1, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, -1, -1, -1, -1, -1, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, -1, -1, + 83, 84, 85, 86, -1, -1, 89, 90, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 101, 102, + 103, 104, 105, 106, 107, 108, 109, -1, -1, -1, + 113, 114, 115, 116, 117, 118, 119, 120, 121, -1, + -1, -1, -1, 126, 127, 3, 4, 5, 6, -1, + 133, 9, -1, -1, 137, -1, 14, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 30, 31, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, -1, -1, -1, + -1, -1, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, -1, -1, 83, 84, 85, 86, -1, + 3, 4, 5, 6, -1, -1, 9, -1, -1, -1, + -1, 14, -1, 101, 102, 103, 104, 105, 106, 107, + 108, 109, -1, -1, -1, -1, -1, 30, 31, -1, + -1, -1, -1, 121, -1, -1, -1, 125, 126, 127, + -1, -1, -1, -1, -1, 133, -1, -1, -1, 137, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, -1, -1, -1, -1, -1, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, -1, -1, + 83, 84, 85, 86, -1, 3, 4, 5, 6, -1, + -1, 9, -1, -1, -1, -1, 14, -1, 101, 102, + 103, 104, 105, 106, 107, 108, 109, -1, -1, -1, + -1, -1, 30, 31, -1, -1, -1, -1, 121, -1, + -1, -1, -1, 126, 127, -1, -1, -1, -1, -1, + 133, -1, -1, -1, 137, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, -1, -1, -1, + -1, -1, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, -1, -1, 83, 84, 85, 86, -1, + 3, 4, 5, 6, -1, -1, 9, -1, -1, -1, + -1, 14, -1, 101, 102, 103, 104, 105, 106, 107, + 108, 109, -1, -1, -1, -1, -1, 30, 31, -1, + -1, -1, -1, 121, -1, -1, -1, -1, 126, 127, + -1, -1, -1, -1, -1, 133, -1, -1, -1, 137, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, -1, -1, -1, -1, -1, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, -1, -1, + 83, 84, 85, 86, -1, 3, 4, 5, 6, -1, + -1, 9, -1, -1, -1, -1, 14, -1, 101, 102, + 103, 104, 105, 106, 107, 108, 109, -1, -1, -1, + -1, -1, 30, 31, -1, -1, -1, -1, 121, -1, + -1, -1, -1, 126, 127, -1, -1, -1, -1, -1, + 133, -1, -1, -1, 137, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, -1, -1, -1, + -1, -1, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, -1, -1, 83, 84, 85, 86, -1, + 3, 4, 5, -1, -1, -1, 9, -1, -1, -1, + -1, 14, -1, 101, 102, 103, 104, 105, 106, 107, + 108, 109, -1, -1, -1, -1, -1, 30, 31, -1, + -1, -1, -1, 121, -1, -1, -1, -1, 126, 127, + -1, -1, -1, -1, -1, 133, -1, -1, -1, 137, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, -1, -1, -1, -1, -1, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, -1, -1, + 83, 84, 85, 86, -1, 3, 4, 5, -1, -1, + -1, 9, -1, -1, -1, -1, 14, -1, 101, 102, + 103, 104, 105, 106, 107, 108, 109, -1, -1, -1, + -1, -1, 30, 31, -1, -1, -1, -1, -1, -1, + -1, -1, 125, 126, 127, -1, -1, -1, -1, -1, + 133, -1, -1, -1, 137, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, -1, -1, -1, + -1, -1, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, -1, -1, 83, 84, 85, 86, -1, + 3, 4, 5, -1, -1, -1, 9, -1, -1, -1, + -1, 14, -1, 101, 102, 103, 104, 105, 106, 107, + 108, 109, -1, -1, -1, -1, -1, 30, 31, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 126, 127, + -1, -1, -1, -1, -1, 133, 134, -1, -1, 137, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, -1, -1, -1, -1, -1, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, -1, -1, + 83, 84, 85, 86, -1, 3, 4, 5, -1, -1, + -1, 9, -1, -1, -1, -1, 14, -1, 101, 102, + 103, 104, 105, 106, 107, 108, 109, -1, -1, -1, + -1, -1, 30, 31, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 126, 127, -1, -1, -1, -1, -1, + 133, -1, -1, -1, 137, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, -1, -1, -1, + -1, -1, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, -1, -1, 83, 84, 85, 86, -1, + 3, 4, 5, -1, -1, -1, 9, -1, -1, -1, + -1, 14, -1, 101, 102, 103, 104, 105, 106, 107, + 108, 109, -1, -1, -1, -1, -1, 30, 31, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 126, 127, + -1, -1, -1, -1, -1, 133, -1, -1, -1, 137, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, -1, -1, -1, -1, -1, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, -1, -1, + 83, 84, 85, 86, -1, -1, 3, 4, 5, -1, + -1, -1, 9, -1, -1, -1, -1, 14, 101, 102, + 103, 104, 105, 106, 107, 108, 109, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 126, 127, -1, -1, -1, -1, -1, + 133, 48, -1, -1, 137, -1, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, -1, -1, + -1, -1, -1, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 80, -1, -1, 83, 84, 85, 86, + -1, -1, 3, 4, 5, -1, -1, -1, 9, -1, + -1, -1, -1, 14, 101, 102, 103, 104, 105, 106, + 107, 108, 109, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 126, + 127, -1, -1, -1, -1, -1, 133, 48, -1, -1, + 137, -1, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, -1, -1, -1, -1, -1, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + -1, -1, 83, 84, 85, 86, -1, 3, 4, 5, + -1, -1, -1, 9, -1, -1, -1, -1, 14, -1, + 101, 102, 103, 104, 105, 106, 107, 108, 109, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 126, 127, -1, -1, -1, + -1, -1, 133, -1, -1, -1, 137, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, -1, + -1, -1, -1, -1, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, -1, -1, 83, 84, 85, + 86, 30, 31, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 101, 102, 103, 104, 105, + 106, 107, 108, 109, -1, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 30, 31, -1, -1, + 126, 127, -1, -1, -1, -1, -1, 133, -1, -1, + -1, 137, -1, -1, 83, 84, 85, 86, -1, -1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, -1, 101, 102, 103, 104, 105, 106, 107, 108, + 109, -1, -1, -1, -1, 48, -1, -1, 51, 83, + 84, 85, 86, -1, -1, 48, 125, -1, 51, -1, + -1, -1, -1, -1, -1, -1, -1, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 95, 96, 97, 98, + 99, 100, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 125, 95, 96, 97, 98, 99, 100, -1, -1, + -1, -1, 95, 96, 97, 98, 99, 100, -1, -1, + 129, 130, 131, 132, 133, 134, 135, 136, -1, 138, + -1, -1, -1, -1, -1, -1, 129, 130, 131, 132, + 133, 134, 135, 136, -1, 138, 129, 130, 131, 132, + 133, 134, 135, 136, -1, 138, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, -1, -1, + -1, -1, -1, -1, -1, 83, 84, 85, 86, -1, + -1, -1, -1, -1, -1, -1, 83, 84, 85, 86, + -1, -1, -1, 101, 102, 103, 104, 105, 106, 107, + 108, 109, -1, -1, 101, 102, 103, 104, 105, 106, + 107, 108, 109, -1, -1, -1, -1, 125, 95, 96, + 97, 98, 99, 100, -1, -1, -1, 95, 96, 97, + 98, 99, 100, -1, -1, -1, 95, -1, 97, 98, + 99, 100, -1, -1, -1, 95, -1, 97, 98, 99, + 100, -1, -1, 130, 131, 132, 133, 134, 135, 136, + -1, 138, 130, 131, 132, 133, 134, 135, 136, -1, + 138, 130, 131, 132, 133, 134, 135, 136, -1, 138, + 130, 131, 132, 133, 134, 135, 136, -1, 138 +}; + +/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of + state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 139, 147, 148, 150, 151, 0, 3, 4, 6, + 7, 15, 16, 17, 18, 19, 20, 21, 22, 25, + 28, 29, 30, 31, 81, 87, 88, 91, 93, 110, + 112, 121, 122, 128, 142, 149, 152, 161, 164, 170, + 174, 176, 139, 141, 140, 141, 140, 6, 82, 153, + 3, 4, 5, 9, 14, 30, 31, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 83, 84, 85, 86, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 126, 127, 133, 137, 162, 163, 176, + 178, 179, 181, 182, 183, 184, 185, 186, 191, 192, + 193, 194, 4, 126, 137, 167, 168, 176, 182, 192, + 194, 4, 192, 126, 162, 175, 171, 150, 151, 4, + 129, 94, 11, 12, 13, 23, 24, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 89, + 90, 113, 114, 115, 116, 117, 118, 119, 120, 176, + 192, 150, 167, 140, 3, 4, 160, 82, 156, 8, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 30, 31, 101, 102, 30, 31, 101, 102, 167, + 178, 181, 182, 192, 194, 167, 192, 192, 163, 132, + 133, 65, 66, 67, 68, 69, 125, 191, 6, 14, + 15, 16, 17, 18, 19, 20, 22, 128, 187, 187, + 48, 183, 48, 51, 95, 96, 97, 98, 99, 100, + 129, 130, 131, 132, 133, 134, 135, 136, 138, 180, + 130, 140, 168, 192, 37, 95, 96, 97, 98, 95, + 96, 176, 6, 148, 150, 148, 170, 4, 159, 192, + 192, 192, 192, 192, 192, 192, 192, 178, 192, 178, + 178, 3, 9, 14, 125, 126, 178, 188, 189, 190, + 191, 126, 181, 192, 192, 192, 192, 192, 192, 176, + 176, 176, 192, 192, 3, 4, 30, 31, 170, 178, + 179, 182, 167, 154, 8, 167, 167, 167, 167, 167, + 167, 167, 167, 167, 145, 167, 167, 145, 129, 132, + 133, 145, 145, 180, 130, 10, 181, 181, 3, 144, + 183, 49, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 178, 178, 192, + 165, 168, 192, 168, 192, 176, 176, 168, 192, 168, + 192, 177, 144, 143, 8, 189, 191, 187, 187, 3, + 189, 125, 191, 181, 192, 8, 157, 145, 145, 129, + 145, 145, 145, 129, 129, 145, 145, 145, 178, 181, + 181, 178, 178, 50, 52, 129, 33, 8, 129, 145, + 172, 189, 189, 129, 3, 155, 111, 166, 167, 167, + 167, 145, 145, 145, 52, 129, 51, 178, 178, 192, + 192, 170, 173, 189, 166, 6, 145, 145, 145, 178, + 178, 131, 36, 169, 145, 145, 131, 134, 192, 92, + 145, 192, 158, 8 +}; + +/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ +static const yytype_uint8 yyr1[] = +{ + 0, 146, 147, 147, 148, 149, 149, 150, 150, 151, + 151, 152, 152, 152, 152, 152, 152, 152, 152, 152, + 152, 153, 152, 152, 154, 155, 152, 156, 157, 152, + 158, 152, 152, 159, 152, 152, 152, 160, 160, 161, + 161, 161, 162, 162, 163, 163, 163, 165, 164, 166, + 166, 167, 167, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 169, 169, 169, 170, 170, 170, 170, 171, + 172, 170, 170, 173, 173, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 175, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 176, 176, 177, 177, + 178, 178, 178, 179, 179, 179, 179, 179, 179, 179, + 179, 179, 179, 180, 180, 181, 181, 182, 182, 182, + 182, 182, 183, 183, 183, 184, 184, 185, 185, 186, + 186, 187, 187, 187, 187, 187, 187, 187, 187, 187, + 188, 188, 189, 189, 189, 190, 190, 190, 190, 190, + 190, 191, 191, 191, 191, 191, 191, 191, 191, 191, + 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, + 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, + 191, 191, 191, 192, 192, 193, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194 +}; + +/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 1, 1, 3, 1, 3, 0, 1, 1, + 2, 3, 3, 4, 1, 1, 1, 1, 1, 2, + 2, 0, 3, 2, 0, 0, 7, 0, 0, 6, + 0, 10, 1, 0, 4, 1, 1, 1, 1, 2, + 2, 3, 1, 2, 1, 1, 1, 0, 5, 0, + 2, 1, 1, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 0, 2, 3, 1, 4, 4, 4, 0, + 0, 6, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 3, 0, 4, 3, 3, + 3, 3, 2, 2, 3, 2, 3, 2, 3, 2, + 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, + 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, + 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 2, 1, 5, 0, 3, + 1, 1, 3, 1, 3, 5, 3, 5, 5, 5, + 7, 6, 8, 1, 4, 3, 3, 1, 2, 2, + 3, 1, 1, 1, 3, 1, 3, 1, 2, 2, + 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 1, 1, 2, 1, 5, 4, 3, 3, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 1, 1, 2, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 3, 2, + 3, 4, 4, 6, 4, 4, 4, 6, 6, 4, + 4, 3, 4, 3, 3, 3, 3, 3, 3, 3, + 2 +}; + + +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: element_list */ +#line 277 "../src/preproc/pic/pic.ypp" + { + if (olist.head) + print_picture(olist.head); + } +#line 2343 "src/preproc/pic/pic.cpp" + break; + + case 4: /* element_list: optional_separator middle_element_list optional_separator */ +#line 286 "../src/preproc/pic/pic.ypp" + { (yyval.pl) = (yyvsp[-1].pl); } +#line 2349 "src/preproc/pic/pic.cpp" + break; + + case 5: /* middle_element_list: element */ +#line 291 "../src/preproc/pic/pic.ypp" + { (yyval.pl) = (yyvsp[0].pl); } +#line 2355 "src/preproc/pic/pic.cpp" + break; + + case 6: /* middle_element_list: middle_element_list separator element */ +#line 293 "../src/preproc/pic/pic.ypp" + { (yyval.pl) = (yyvsp[-2].pl); } +#line 2361 "src/preproc/pic/pic.cpp" + break; + + case 11: /* placeless_element: FIGNAME '=' macro_name */ +#line 308 "../src/preproc/pic/pic.ypp" + { + delete[] graphname; + graphname = new char[strlen((yyvsp[0].str)) + 1]; + strcpy(graphname, (yyvsp[0].str)); + delete[] (yyvsp[0].str); + } +#line 2372 "src/preproc/pic/pic.cpp" + break; + + case 12: /* placeless_element: VARIABLE '=' any_expr */ +#line 316 "../src/preproc/pic/pic.ypp" + { + define_variable((yyvsp[-2].str), (yyvsp[0].x)); + free((yyvsp[-2].str)); + } +#line 2381 "src/preproc/pic/pic.cpp" + break; + + case 13: /* placeless_element: VARIABLE ':' '=' any_expr */ +#line 321 "../src/preproc/pic/pic.ypp" + { + place *p = lookup_label((yyvsp[-3].str)); + if (!p) { + lex_error("variable '%1' not defined", (yyvsp[-3].str)); + YYABORT; + } + p->obj = 0; + p->x = (yyvsp[0].x); + p->y = 0.0; + free((yyvsp[-3].str)); + } +#line 2397 "src/preproc/pic/pic.cpp" + break; + + case 14: /* placeless_element: UP */ +#line 333 "../src/preproc/pic/pic.ypp" + { current_direction = UP_DIRECTION; } +#line 2403 "src/preproc/pic/pic.cpp" + break; + + case 15: /* placeless_element: DOWN */ +#line 335 "../src/preproc/pic/pic.ypp" + { current_direction = DOWN_DIRECTION; } +#line 2409 "src/preproc/pic/pic.cpp" + break; + + case 16: /* placeless_element: LEFT */ +#line 337 "../src/preproc/pic/pic.ypp" + { current_direction = LEFT_DIRECTION; } +#line 2415 "src/preproc/pic/pic.cpp" + break; + + case 17: /* placeless_element: RIGHT */ +#line 339 "../src/preproc/pic/pic.ypp" + { current_direction = RIGHT_DIRECTION; } +#line 2421 "src/preproc/pic/pic.cpp" + break; + + case 18: /* placeless_element: COMMAND_LINE */ +#line 341 "../src/preproc/pic/pic.ypp" + { + olist.append(make_command_object((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename, + (yyvsp[0].lstr).lineno)); + } +#line 2430 "src/preproc/pic/pic.cpp" + break; + + case 19: /* placeless_element: COMMAND print_args */ +#line 346 "../src/preproc/pic/pic.ypp" + { + olist.append(make_command_object((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename, + (yyvsp[0].lstr).lineno)); + } +#line 2439 "src/preproc/pic/pic.cpp" + break; + + case 20: /* placeless_element: PRINT print_args */ +#line 351 "../src/preproc/pic/pic.ypp" + { + fprintf(stderr, "%s\n", (yyvsp[0].lstr).str); + delete[] (yyvsp[0].lstr).str; + fflush(stderr); + } +#line 2449 "src/preproc/pic/pic.cpp" + break; + + case 21: /* $@1: %empty */ +#line 357 "../src/preproc/pic/pic.ypp" + { delim_flag = 1; } +#line 2455 "src/preproc/pic/pic.cpp" + break; + + case 22: /* placeless_element: SH $@1 DELIMITED */ +#line 359 "../src/preproc/pic/pic.ypp" + { + delim_flag = 0; + if (safer_flag) + lex_error("unsafe to run command '%1'; ignoring", + (yyvsp[0].str)); + else { + int retval = system((yyvsp[0].str)); + if (retval < 0) + lex_error("error running command '%1': system()" + " returned %2", (yyvsp[0].str), retval); + } + delete[] (yyvsp[0].str); + } +#line 2473 "src/preproc/pic/pic.cpp" + break; + + case 23: /* placeless_element: COPY TEXT */ +#line 373 "../src/preproc/pic/pic.ypp" + { + if (yychar < 0) + do_lookahead(); + do_copy((yyvsp[0].lstr).str); + // do not delete the filename + } +#line 2484 "src/preproc/pic/pic.cpp" + break; + + case 24: /* $@2: %empty */ +#line 380 "../src/preproc/pic/pic.ypp" + { delim_flag = 2; } +#line 2490 "src/preproc/pic/pic.cpp" + break; + + case 25: /* $@3: %empty */ +#line 382 "../src/preproc/pic/pic.ypp" + { delim_flag = 0; } +#line 2496 "src/preproc/pic/pic.cpp" + break; + + case 26: /* placeless_element: COPY TEXT THRU $@2 DELIMITED $@3 until */ +#line 384 "../src/preproc/pic/pic.ypp" + { + if (yychar < 0) + do_lookahead(); + copy_file_thru((yyvsp[-5].lstr).str, (yyvsp[-2].str), (yyvsp[0].str)); + // do not delete the filename + delete[] (yyvsp[-2].str); + delete[] (yyvsp[0].str); + } +#line 2509 "src/preproc/pic/pic.cpp" + break; + + case 27: /* $@4: %empty */ +#line 393 "../src/preproc/pic/pic.ypp" + { delim_flag = 2; } +#line 2515 "src/preproc/pic/pic.cpp" + break; + + case 28: /* $@5: %empty */ +#line 395 "../src/preproc/pic/pic.ypp" + { delim_flag = 0; } +#line 2521 "src/preproc/pic/pic.cpp" + break; + + case 29: /* placeless_element: COPY THRU $@4 DELIMITED $@5 until */ +#line 397 "../src/preproc/pic/pic.ypp" + { + if (yychar < 0) + do_lookahead(); + copy_rest_thru((yyvsp[-2].str), (yyvsp[0].str)); + delete[] (yyvsp[-2].str); + delete[] (yyvsp[0].str); + } +#line 2533 "src/preproc/pic/pic.cpp" + break; + + case 30: /* $@6: %empty */ +#line 405 "../src/preproc/pic/pic.ypp" + { delim_flag = 1; } +#line 2539 "src/preproc/pic/pic.cpp" + break; + + case 31: /* placeless_element: FOR VARIABLE '=' expr TO expr optional_by DO $@6 DELIMITED */ +#line 407 "../src/preproc/pic/pic.ypp" + { + delim_flag = 0; + if (yychar < 0) + do_lookahead(); + do_for((yyvsp[-8].str), (yyvsp[-6].x), (yyvsp[-4].x), (yyvsp[-3].by).is_multiplicative, (yyvsp[-3].by).val, (yyvsp[0].str)); + } +#line 2550 "src/preproc/pic/pic.cpp" + break; + + case 32: /* placeless_element: simple_if */ +#line 414 "../src/preproc/pic/pic.ypp" + { + if (yychar < 0) + do_lookahead(); + if ((yyvsp[0].if_data).x != 0.0) + push_body((yyvsp[0].if_data).body); + delete[] (yyvsp[0].if_data).body; + } +#line 2562 "src/preproc/pic/pic.cpp" + break; + + case 33: /* $@7: %empty */ +#line 422 "../src/preproc/pic/pic.ypp" + { delim_flag = 1; } +#line 2568 "src/preproc/pic/pic.cpp" + break; + + case 34: /* placeless_element: simple_if ELSE $@7 DELIMITED */ +#line 424 "../src/preproc/pic/pic.ypp" + { + delim_flag = 0; + if (yychar < 0) + do_lookahead(); + if ((yyvsp[-3].if_data).x != 0.0) + push_body((yyvsp[-3].if_data).body); + else + push_body((yyvsp[0].str)); + free((yyvsp[-3].if_data).body); + free((yyvsp[0].str)); + } +#line 2584 "src/preproc/pic/pic.cpp" + break; + + case 36: /* placeless_element: RESET */ +#line 437 "../src/preproc/pic/pic.ypp" + { define_variable("scale", 1.0); } +#line 2590 "src/preproc/pic/pic.cpp" + break; + + case 39: /* reset_variables: RESET VARIABLE */ +#line 447 "../src/preproc/pic/pic.ypp" + { + reset((yyvsp[0].str)); + delete[] (yyvsp[0].str); + } +#line 2599 "src/preproc/pic/pic.cpp" + break; + + case 40: /* reset_variables: reset_variables VARIABLE */ +#line 452 "../src/preproc/pic/pic.ypp" + { + reset((yyvsp[0].str)); + delete[] (yyvsp[0].str); + } +#line 2608 "src/preproc/pic/pic.cpp" + break; + + case 41: /* reset_variables: reset_variables ',' VARIABLE */ +#line 457 "../src/preproc/pic/pic.ypp" + { + reset((yyvsp[0].str)); + delete[] (yyvsp[0].str); + } +#line 2617 "src/preproc/pic/pic.cpp" + break; + + case 42: /* print_args: print_arg */ +#line 465 "../src/preproc/pic/pic.ypp" + { (yyval.lstr) = (yyvsp[0].lstr); } +#line 2623 "src/preproc/pic/pic.cpp" + break; + + case 43: /* print_args: print_args print_arg */ +#line 467 "../src/preproc/pic/pic.ypp" + { + (yyval.lstr).str = new char[strlen((yyvsp[-1].lstr).str) + strlen((yyvsp[0].lstr).str) + 1]; + strcpy((yyval.lstr).str, (yyvsp[-1].lstr).str); + strcat((yyval.lstr).str, (yyvsp[0].lstr).str); + delete[] (yyvsp[-1].lstr).str; + delete[] (yyvsp[0].lstr).str; + if ((yyvsp[-1].lstr).filename) { + (yyval.lstr).filename = (yyvsp[-1].lstr).filename; + (yyval.lstr).lineno = (yyvsp[-1].lstr).lineno; + } + else if ((yyvsp[0].lstr).filename) { + (yyval.lstr).filename = (yyvsp[0].lstr).filename; + (yyval.lstr).lineno = (yyvsp[0].lstr).lineno; + } + } +#line 2643 "src/preproc/pic/pic.cpp" + break; + + case 44: /* print_arg: expr */ +#line 486 "../src/preproc/pic/pic.ypp" + { + (yyval.lstr).str = new char[GDIGITS + 1]; + sprintf((yyval.lstr).str, "%g", (yyvsp[0].x)); + (yyval.lstr).filename = 0; + (yyval.lstr).lineno = 0; + } +#line 2654 "src/preproc/pic/pic.cpp" + break; + + case 45: /* print_arg: text */ +#line 493 "../src/preproc/pic/pic.ypp" + { (yyval.lstr) = (yyvsp[0].lstr); } +#line 2660 "src/preproc/pic/pic.cpp" + break; + + case 46: /* print_arg: position */ +#line 495 "../src/preproc/pic/pic.ypp" + { + (yyval.lstr).str = new char[GDIGITS + 2 + GDIGITS + 1]; + sprintf((yyval.lstr).str, "%g, %g", (yyvsp[0].pair).x, (yyvsp[0].pair).y); + (yyval.lstr).filename = 0; + (yyval.lstr).lineno = 0; + } +#line 2671 "src/preproc/pic/pic.cpp" + break; + + case 47: /* $@8: %empty */ +#line 505 "../src/preproc/pic/pic.ypp" + { delim_flag = 1; } +#line 2677 "src/preproc/pic/pic.cpp" + break; + + case 48: /* simple_if: IF any_expr THEN $@8 DELIMITED */ +#line 507 "../src/preproc/pic/pic.ypp" + { + delim_flag = 0; + (yyval.if_data).x = (yyvsp[-3].x); + (yyval.if_data).body = (yyvsp[0].str); + } +#line 2687 "src/preproc/pic/pic.cpp" + break; + + case 49: /* until: %empty */ +#line 516 "../src/preproc/pic/pic.ypp" + { (yyval.str) = 0; } +#line 2693 "src/preproc/pic/pic.cpp" + break; + + case 50: /* until: UNTIL TEXT */ +#line 518 "../src/preproc/pic/pic.ypp" + { (yyval.str) = (yyvsp[0].lstr).str; } +#line 2699 "src/preproc/pic/pic.cpp" + break; + + case 51: /* any_expr: expr */ +#line 523 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[0].x); } +#line 2705 "src/preproc/pic/pic.cpp" + break; + + case 52: /* any_expr: text_expr */ +#line 525 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[0].x); } +#line 2711 "src/preproc/pic/pic.cpp" + break; + + case 53: /* text_expr: text EQUALEQUAL text */ +#line 530 "../src/preproc/pic/pic.ypp" + { + (yyval.x) = strcmp((yyvsp[-2].lstr).str, (yyvsp[0].lstr).str) == 0; + delete[] (yyvsp[-2].lstr).str; + delete[] (yyvsp[0].lstr).str; + } +#line 2721 "src/preproc/pic/pic.cpp" + break; + + case 54: /* text_expr: text NOTEQUAL text */ +#line 536 "../src/preproc/pic/pic.ypp" + { + (yyval.x) = strcmp((yyvsp[-2].lstr).str, (yyvsp[0].lstr).str) != 0; + delete[] (yyvsp[-2].lstr).str; + delete[] (yyvsp[0].lstr).str; + } +#line 2731 "src/preproc/pic/pic.cpp" + break; + + case 55: /* text_expr: text_expr ANDAND text_expr */ +#line 542 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); } +#line 2737 "src/preproc/pic/pic.cpp" + break; + + case 56: /* text_expr: text_expr ANDAND expr */ +#line 544 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); } +#line 2743 "src/preproc/pic/pic.cpp" + break; + + case 57: /* text_expr: expr ANDAND text_expr */ +#line 546 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); } +#line 2749 "src/preproc/pic/pic.cpp" + break; + + case 58: /* text_expr: text_expr OROR text_expr */ +#line 548 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); } +#line 2755 "src/preproc/pic/pic.cpp" + break; + + case 59: /* text_expr: text_expr OROR expr */ +#line 550 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); } +#line 2761 "src/preproc/pic/pic.cpp" + break; + + case 60: /* text_expr: expr OROR text_expr */ +#line 552 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); } +#line 2767 "src/preproc/pic/pic.cpp" + break; + + case 61: /* text_expr: '!' text_expr */ +#line 554 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[0].x) == 0.0); } +#line 2773 "src/preproc/pic/pic.cpp" + break; + + case 62: /* optional_by: %empty */ +#line 560 "../src/preproc/pic/pic.ypp" + { + (yyval.by).val = 1.0; + (yyval.by).is_multiplicative = 0; + } +#line 2782 "src/preproc/pic/pic.cpp" + break; + + case 63: /* optional_by: BY expr */ +#line 565 "../src/preproc/pic/pic.ypp" + { + (yyval.by).val = (yyvsp[0].x); + (yyval.by).is_multiplicative = 0; + } +#line 2791 "src/preproc/pic/pic.cpp" + break; + + case 64: /* optional_by: BY '*' expr */ +#line 570 "../src/preproc/pic/pic.ypp" + { + (yyval.by).val = (yyvsp[0].x); + (yyval.by).is_multiplicative = 1; + } +#line 2800 "src/preproc/pic/pic.cpp" + break; + + case 65: /* element: object_spec */ +#line 578 "../src/preproc/pic/pic.ypp" + { + (yyval.pl).obj = (yyvsp[0].spec)->make_object(¤t_position, + ¤t_direction); + if ((yyval.pl).obj == 0) + YYABORT; + delete (yyvsp[0].spec); + if ((yyval.pl).obj) + olist.append((yyval.pl).obj); + else { + (yyval.pl).x = current_position.x; + (yyval.pl).y = current_position.y; + } + } +#line 2818 "src/preproc/pic/pic.cpp" + break; + + case 66: /* element: LABEL ':' optional_separator element */ +#line 592 "../src/preproc/pic/pic.ypp" + { + (yyval.pl) = (yyvsp[0].pl); + define_label((yyvsp[-3].str), & (yyval.pl)); + free((yyvsp[-3].str)); + } +#line 2828 "src/preproc/pic/pic.cpp" + break; + + case 67: /* element: LABEL ':' optional_separator position_not_place */ +#line 598 "../src/preproc/pic/pic.ypp" + { + (yyval.pl).obj = 0; + (yyval.pl).x = (yyvsp[0].pair).x; + (yyval.pl).y = (yyvsp[0].pair).y; + define_label((yyvsp[-3].str), & (yyval.pl)); + free((yyvsp[-3].str)); + } +#line 2840 "src/preproc/pic/pic.cpp" + break; + + case 68: /* element: LABEL ':' optional_separator place */ +#line 606 "../src/preproc/pic/pic.ypp" + { + (yyval.pl) = (yyvsp[0].pl); + define_label((yyvsp[-3].str), & (yyval.pl)); + free((yyvsp[-3].str)); + } +#line 2850 "src/preproc/pic/pic.cpp" + break; + + case 69: /* @9: %empty */ +#line 612 "../src/preproc/pic/pic.ypp" + { + (yyval.state).x = current_position.x; + (yyval.state).y = current_position.y; + (yyval.state).dir = current_direction; + } +#line 2860 "src/preproc/pic/pic.cpp" + break; + + case 70: /* $@10: %empty */ +#line 618 "../src/preproc/pic/pic.ypp" + { + current_position.x = (yyvsp[-2].state).x; + current_position.y = (yyvsp[-2].state).y; + current_direction = (yyvsp[-2].state).dir; + } +#line 2870 "src/preproc/pic/pic.cpp" + break; + + case 71: /* element: '{' @9 element_list '}' $@10 optional_element */ +#line 624 "../src/preproc/pic/pic.ypp" + { + (yyval.pl) = (yyvsp[-3].pl); + } +#line 2878 "src/preproc/pic/pic.cpp" + break; + + case 72: /* element: placeless_element */ +#line 628 "../src/preproc/pic/pic.ypp" + { + (yyval.pl).obj = 0; + (yyval.pl).x = current_position.x; + (yyval.pl).y = current_position.y; + } +#line 2888 "src/preproc/pic/pic.cpp" + break; + + case 73: /* optional_element: %empty */ +#line 637 "../src/preproc/pic/pic.ypp" + {} +#line 2894 "src/preproc/pic/pic.cpp" + break; + + case 74: /* optional_element: element */ +#line 639 "../src/preproc/pic/pic.ypp" + {} +#line 2900 "src/preproc/pic/pic.cpp" + break; + + case 75: /* object_spec: BOX */ +#line 644 "../src/preproc/pic/pic.ypp" + { (yyval.spec) = new object_spec(BOX_OBJECT); } +#line 2906 "src/preproc/pic/pic.cpp" + break; + + case 76: /* object_spec: CIRCLE */ +#line 646 "../src/preproc/pic/pic.ypp" + { (yyval.spec) = new object_spec(CIRCLE_OBJECT); } +#line 2912 "src/preproc/pic/pic.cpp" + break; + + case 77: /* object_spec: ELLIPSE */ +#line 648 "../src/preproc/pic/pic.ypp" + { (yyval.spec) = new object_spec(ELLIPSE_OBJECT); } +#line 2918 "src/preproc/pic/pic.cpp" + break; + + case 78: /* object_spec: ARC */ +#line 650 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(ARC_OBJECT); + (yyval.spec)->dir = current_direction; + } +#line 2927 "src/preproc/pic/pic.cpp" + break; + + case 79: /* object_spec: LINE */ +#line 655 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(LINE_OBJECT); + lookup_variable("lineht", & (yyval.spec)->segment_height); + lookup_variable("linewid", & (yyval.spec)->segment_width); + (yyval.spec)->dir = current_direction; + } +#line 2938 "src/preproc/pic/pic.cpp" + break; + + case 80: /* object_spec: ARROW */ +#line 662 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(ARROW_OBJECT); + lookup_variable("lineht", & (yyval.spec)->segment_height); + lookup_variable("linewid", & (yyval.spec)->segment_width); + (yyval.spec)->dir = current_direction; + } +#line 2949 "src/preproc/pic/pic.cpp" + break; + + case 81: /* object_spec: MOVE */ +#line 669 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(MOVE_OBJECT); + lookup_variable("moveht", & (yyval.spec)->segment_height); + lookup_variable("movewid", & (yyval.spec)->segment_width); + (yyval.spec)->dir = current_direction; + } +#line 2960 "src/preproc/pic/pic.cpp" + break; + + case 82: /* object_spec: SPLINE */ +#line 676 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(SPLINE_OBJECT); + lookup_variable("lineht", & (yyval.spec)->segment_height); + lookup_variable("linewid", & (yyval.spec)->segment_width); + (yyval.spec)->dir = current_direction; + } +#line 2971 "src/preproc/pic/pic.cpp" + break; + + case 83: /* object_spec: text */ +#line 683 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(TEXT_OBJECT); + (yyval.spec)->text = new text_item((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename, (yyvsp[0].lstr).lineno); + } +#line 2980 "src/preproc/pic/pic.cpp" + break; + + case 84: /* object_spec: PLOT expr */ +#line 688 "../src/preproc/pic/pic.ypp" + { + lex_warning("'plot' is deprecated; use 'sprintf'" + " instead"); + (yyval.spec) = new object_spec(TEXT_OBJECT); + (yyval.spec)->text = new text_item(format_number(0, (yyvsp[0].x)), 0, -1); + } +#line 2991 "src/preproc/pic/pic.cpp" + break; + + case 85: /* object_spec: PLOT expr text */ +#line 695 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = new object_spec(TEXT_OBJECT); + (yyval.spec)->text = new text_item(format_number((yyvsp[0].lstr).str, (yyvsp[-1].x)), + (yyvsp[0].lstr).filename, (yyvsp[0].lstr).lineno); + delete[] (yyvsp[0].lstr).str; + } +#line 3002 "src/preproc/pic/pic.cpp" + break; + + case 86: /* @11: %empty */ +#line 702 "../src/preproc/pic/pic.ypp" + { + saved_state *p = new saved_state; + (yyval.pstate) = p; + p->x = current_position.x; + p->y = current_position.y; + p->dir = current_direction; + p->tbl = current_table; + p->prev = current_saved_state; + current_position.x = 0.0; + current_position.y = 0.0; + current_table = new PTABLE(place); + current_saved_state = p; + olist.append(make_mark_object()); + } +#line 3021 "src/preproc/pic/pic.cpp" + break; + + case 87: /* object_spec: '[' @11 element_list ']' */ +#line 717 "../src/preproc/pic/pic.ypp" + { + current_position.x = (yyvsp[-2].pstate)->x; + current_position.y = (yyvsp[-2].pstate)->y; + current_direction = (yyvsp[-2].pstate)->dir; + (yyval.spec) = new object_spec(BLOCK_OBJECT); + olist.wrap_up_block(& (yyval.spec)->oblist); + (yyval.spec)->tbl = current_table; + current_table = (yyvsp[-2].pstate)->tbl; + current_saved_state = (yyvsp[-2].pstate)->prev; + delete (yyvsp[-2].pstate); + } +#line 3037 "src/preproc/pic/pic.cpp" + break; + + case 88: /* object_spec: object_spec HEIGHT expr */ +#line 729 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->height = (yyvsp[0].x); + (yyval.spec)->flags |= HAS_HEIGHT; + } +#line 3047 "src/preproc/pic/pic.cpp" + break; + + case 89: /* object_spec: object_spec RADIUS expr */ +#line 735 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->radius = (yyvsp[0].x); + (yyval.spec)->flags |= HAS_RADIUS; + } +#line 3057 "src/preproc/pic/pic.cpp" + break; + + case 90: /* object_spec: object_spec WIDTH expr */ +#line 741 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->width = (yyvsp[0].x); + (yyval.spec)->flags |= HAS_WIDTH; + } +#line 3067 "src/preproc/pic/pic.cpp" + break; + + case 91: /* object_spec: object_spec DIAMETER expr */ +#line 747 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->radius = (yyvsp[0].x)/2.0; + (yyval.spec)->flags |= HAS_RADIUS; + } +#line 3077 "src/preproc/pic/pic.cpp" + break; + + case 92: /* object_spec: object_spec expr */ +#line 753 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= HAS_SEGMENT; + switch ((yyval.spec)->dir) { + case UP_DIRECTION: + (yyval.spec)->segment_pos.y += (yyvsp[0].x); + break; + case DOWN_DIRECTION: + (yyval.spec)->segment_pos.y -= (yyvsp[0].x); + break; + case RIGHT_DIRECTION: + (yyval.spec)->segment_pos.x += (yyvsp[0].x); + break; + case LEFT_DIRECTION: + (yyval.spec)->segment_pos.x -= (yyvsp[0].x); + break; + } + } +#line 3100 "src/preproc/pic/pic.cpp" + break; + + case 93: /* object_spec: object_spec UP */ +#line 772 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->dir = UP_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.y += (yyval.spec)->segment_height; + } +#line 3111 "src/preproc/pic/pic.cpp" + break; + + case 94: /* object_spec: object_spec UP expr */ +#line 779 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->dir = UP_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.y += (yyvsp[0].x); + } +#line 3122 "src/preproc/pic/pic.cpp" + break; + + case 95: /* object_spec: object_spec DOWN */ +#line 786 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->dir = DOWN_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.y -= (yyval.spec)->segment_height; + } +#line 3133 "src/preproc/pic/pic.cpp" + break; + + case 96: /* object_spec: object_spec DOWN expr */ +#line 793 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->dir = DOWN_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.y -= (yyvsp[0].x); + } +#line 3144 "src/preproc/pic/pic.cpp" + break; + + case 97: /* object_spec: object_spec RIGHT */ +#line 800 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->dir = RIGHT_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.x += (yyval.spec)->segment_width; + } +#line 3155 "src/preproc/pic/pic.cpp" + break; + + case 98: /* object_spec: object_spec RIGHT expr */ +#line 807 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->dir = RIGHT_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.x += (yyvsp[0].x); + } +#line 3166 "src/preproc/pic/pic.cpp" + break; + + case 99: /* object_spec: object_spec LEFT */ +#line 814 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->dir = LEFT_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.x -= (yyval.spec)->segment_width; + } +#line 3177 "src/preproc/pic/pic.cpp" + break; + + case 100: /* object_spec: object_spec LEFT expr */ +#line 821 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->dir = LEFT_DIRECTION; + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.x -= (yyvsp[0].x); + } +#line 3188 "src/preproc/pic/pic.cpp" + break; + + case 101: /* object_spec: object_spec FROM position */ +#line 828 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= HAS_FROM; + (yyval.spec)->from.x = (yyvsp[0].pair).x; + (yyval.spec)->from.y = (yyvsp[0].pair).y; + } +#line 3199 "src/preproc/pic/pic.cpp" + break; + + case 102: /* object_spec: object_spec TO position */ +#line 835 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + if ((yyval.spec)->flags & HAS_SEGMENT) + (yyval.spec)->segment_list = new segment((yyval.spec)->segment_pos, + (yyval.spec)->segment_is_absolute, + (yyval.spec)->segment_list); + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.x = (yyvsp[0].pair).x; + (yyval.spec)->segment_pos.y = (yyvsp[0].pair).y; + (yyval.spec)->segment_is_absolute = 1; + (yyval.spec)->flags |= HAS_TO; + (yyval.spec)->to.x = (yyvsp[0].pair).x; + (yyval.spec)->to.y = (yyvsp[0].pair).y; + } +#line 3218 "src/preproc/pic/pic.cpp" + break; + + case 103: /* object_spec: object_spec AT position */ +#line 850 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= HAS_AT; + (yyval.spec)->at.x = (yyvsp[0].pair).x; + (yyval.spec)->at.y = (yyvsp[0].pair).y; + if ((yyval.spec)->type != ARC_OBJECT) { + (yyval.spec)->flags |= HAS_FROM; + (yyval.spec)->from.x = (yyvsp[0].pair).x; + (yyval.spec)->from.y = (yyvsp[0].pair).y; + } + } +#line 3234 "src/preproc/pic/pic.cpp" + break; + + case 104: /* object_spec: object_spec WITH path */ +#line 862 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= HAS_WITH; + (yyval.spec)->with = (yyvsp[0].pth); + } +#line 3244 "src/preproc/pic/pic.cpp" + break; + + case 105: /* object_spec: object_spec WITH position */ +#line 868 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= HAS_WITH; + position pos; + pos.x = (yyvsp[0].pair).x; + pos.y = (yyvsp[0].pair).y; + (yyval.spec)->with = new path(pos); + } +#line 3257 "src/preproc/pic/pic.cpp" + break; + + case 106: /* object_spec: object_spec BY expr_pair */ +#line 877 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= HAS_SEGMENT; + (yyval.spec)->segment_pos.x += (yyvsp[0].pair).x; + (yyval.spec)->segment_pos.y += (yyvsp[0].pair).y; + } +#line 3268 "src/preproc/pic/pic.cpp" + break; + + case 107: /* object_spec: object_spec THEN */ +#line 884 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + if (!((yyval.spec)->flags & HAS_SEGMENT)) + switch ((yyval.spec)->dir) { + case UP_DIRECTION: + (yyval.spec)->segment_pos.y += (yyval.spec)->segment_width; + break; + case DOWN_DIRECTION: + (yyval.spec)->segment_pos.y -= (yyval.spec)->segment_width; + break; + case RIGHT_DIRECTION: + (yyval.spec)->segment_pos.x += (yyval.spec)->segment_width; + break; + case LEFT_DIRECTION: + (yyval.spec)->segment_pos.x -= (yyval.spec)->segment_width; + break; + } + (yyval.spec)->segment_list = new segment((yyval.spec)->segment_pos, + (yyval.spec)->segment_is_absolute, + (yyval.spec)->segment_list); + (yyval.spec)->flags &= ~HAS_SEGMENT; + (yyval.spec)->segment_pos.x = (yyval.spec)->segment_pos.y = 0.0; + (yyval.spec)->segment_is_absolute = 0; + } +#line 3297 "src/preproc/pic/pic.cpp" + break; + + case 108: /* object_spec: object_spec SOLID */ +#line 909 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); // nothing + } +#line 3305 "src/preproc/pic/pic.cpp" + break; + + case 109: /* object_spec: object_spec DOTTED */ +#line 913 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_DOTTED; + lookup_variable("dashwid", & (yyval.spec)->dash_width); + } +#line 3315 "src/preproc/pic/pic.cpp" + break; + + case 110: /* object_spec: object_spec DOTTED expr */ +#line 919 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= IS_DOTTED; + (yyval.spec)->dash_width = (yyvsp[0].x); + } +#line 3325 "src/preproc/pic/pic.cpp" + break; + + case 111: /* object_spec: object_spec DASHED */ +#line 925 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_DASHED; + lookup_variable("dashwid", & (yyval.spec)->dash_width); + } +#line 3335 "src/preproc/pic/pic.cpp" + break; + + case 112: /* object_spec: object_spec DASHED expr */ +#line 931 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= IS_DASHED; + (yyval.spec)->dash_width = (yyvsp[0].x); + } +#line 3345 "src/preproc/pic/pic.cpp" + break; + + case 113: /* object_spec: object_spec FILL */ +#line 937 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_DEFAULT_FILLED; + } +#line 3354 "src/preproc/pic/pic.cpp" + break; + + case 114: /* object_spec: object_spec FILL expr */ +#line 942 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= IS_FILLED; + (yyval.spec)->fill = (yyvsp[0].x); + } +#line 3364 "src/preproc/pic/pic.cpp" + break; + + case 115: /* object_spec: object_spec XSLANTED expr */ +#line 948 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= IS_XSLANTED; + (yyval.spec)->xslanted = (yyvsp[0].x); + } +#line 3374 "src/preproc/pic/pic.cpp" + break; + + case 116: /* object_spec: object_spec YSLANTED expr */ +#line 954 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= IS_YSLANTED; + (yyval.spec)->yslanted = (yyvsp[0].x); + } +#line 3384 "src/preproc/pic/pic.cpp" + break; + + case 117: /* object_spec: object_spec SHADED text */ +#line 960 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= (IS_SHADED | IS_FILLED); + (yyval.spec)->shaded = new char[strlen((yyvsp[0].lstr).str)+1]; + strcpy((yyval.spec)->shaded, (yyvsp[0].lstr).str); + } +#line 3395 "src/preproc/pic/pic.cpp" + break; + + case 118: /* object_spec: object_spec COLORED text */ +#line 967 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= (IS_SHADED | IS_OUTLINED | IS_FILLED); + (yyval.spec)->shaded = new char[strlen((yyvsp[0].lstr).str)+1]; + strcpy((yyval.spec)->shaded, (yyvsp[0].lstr).str); + (yyval.spec)->outlined = new char[strlen((yyvsp[0].lstr).str)+1]; + strcpy((yyval.spec)->outlined, (yyvsp[0].lstr).str); + } +#line 3408 "src/preproc/pic/pic.cpp" + break; + + case 119: /* object_spec: object_spec OUTLINED text */ +#line 976 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= IS_OUTLINED; + (yyval.spec)->outlined = new char[strlen((yyvsp[0].lstr).str)+1]; + strcpy((yyval.spec)->outlined, (yyvsp[0].lstr).str); + } +#line 3419 "src/preproc/pic/pic.cpp" + break; + + case 120: /* object_spec: object_spec CHOP */ +#line 983 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + // line chop chop means line chop 0 chop 0 + if ((yyval.spec)->flags & IS_DEFAULT_CHOPPED) { + (yyval.spec)->flags |= IS_CHOPPED; + (yyval.spec)->flags &= ~IS_DEFAULT_CHOPPED; + (yyval.spec)->start_chop = (yyval.spec)->end_chop = 0.0; + } + else if ((yyval.spec)->flags & IS_CHOPPED) { + (yyval.spec)->end_chop = 0.0; + } + else { + (yyval.spec)->flags |= IS_DEFAULT_CHOPPED; + } + } +#line 3439 "src/preproc/pic/pic.cpp" + break; + + case 121: /* object_spec: object_spec CHOP expr */ +#line 999 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + if ((yyval.spec)->flags & IS_DEFAULT_CHOPPED) { + (yyval.spec)->flags |= IS_CHOPPED; + (yyval.spec)->flags &= ~IS_DEFAULT_CHOPPED; + (yyval.spec)->start_chop = 0.0; + (yyval.spec)->end_chop = (yyvsp[0].x); + } + else if ((yyval.spec)->flags & IS_CHOPPED) { + (yyval.spec)->end_chop = (yyvsp[0].x); + } + else { + (yyval.spec)->start_chop = (yyval.spec)->end_chop = (yyvsp[0].x); + (yyval.spec)->flags |= IS_CHOPPED; + } + } +#line 3460 "src/preproc/pic/pic.cpp" + break; + + case 122: /* object_spec: object_spec SAME */ +#line 1016 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_SAME; + } +#line 3469 "src/preproc/pic/pic.cpp" + break; + + case 123: /* object_spec: object_spec INVISIBLE */ +#line 1021 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_INVISIBLE; + } +#line 3478 "src/preproc/pic/pic.cpp" + break; + + case 124: /* object_spec: object_spec LEFT_ARROW_HEAD */ +#line 1026 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= HAS_LEFT_ARROW_HEAD; + } +#line 3487 "src/preproc/pic/pic.cpp" + break; + + case 125: /* object_spec: object_spec RIGHT_ARROW_HEAD */ +#line 1031 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= HAS_RIGHT_ARROW_HEAD; + } +#line 3496 "src/preproc/pic/pic.cpp" + break; + + case 126: /* object_spec: object_spec DOUBLE_ARROW_HEAD */ +#line 1036 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD); + } +#line 3505 "src/preproc/pic/pic.cpp" + break; + + case 127: /* object_spec: object_spec CW */ +#line 1041 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_CLOCKWISE; + } +#line 3514 "src/preproc/pic/pic.cpp" + break; + + case 128: /* object_spec: object_spec CCW */ +#line 1046 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags &= ~IS_CLOCKWISE; + } +#line 3523 "src/preproc/pic/pic.cpp" + break; + + case 129: /* object_spec: object_spec text */ +#line 1051 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + text_item **p; + for (p = & (yyval.spec)->text; *p; p = &(*p)->next) + ; + *p = new text_item((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename, (yyvsp[0].lstr).lineno); + } +#line 3535 "src/preproc/pic/pic.cpp" + break; + + case 130: /* object_spec: object_spec LJUST */ +#line 1059 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + if ((yyval.spec)->text) { + text_item *p; + for (p = (yyval.spec)->text; p->next; p = p->next) + ; + p->adj.h = LEFT_ADJUST; + } + } +#line 3549 "src/preproc/pic/pic.cpp" + break; + + case 131: /* object_spec: object_spec RJUST */ +#line 1069 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + if ((yyval.spec)->text) { + text_item *p; + for (p = (yyval.spec)->text; p->next; p = p->next) + ; + p->adj.h = RIGHT_ADJUST; + } + } +#line 3563 "src/preproc/pic/pic.cpp" + break; + + case 132: /* object_spec: object_spec ABOVE */ +#line 1079 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + if ((yyval.spec)->text) { + text_item *p; + for (p = (yyval.spec)->text; p->next; p = p->next) + ; + p->adj.v = ABOVE_ADJUST; + } + } +#line 3577 "src/preproc/pic/pic.cpp" + break; + + case 133: /* object_spec: object_spec BELOW */ +#line 1089 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + if ((yyval.spec)->text) { + text_item *p; + for (p = (yyval.spec)->text; p->next; p = p->next) + ; + p->adj.v = BELOW_ADJUST; + } + } +#line 3591 "src/preproc/pic/pic.cpp" + break; + + case 134: /* object_spec: object_spec THICKNESS expr */ +#line 1099 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-2].spec); + (yyval.spec)->flags |= HAS_THICKNESS; + (yyval.spec)->thickness = (yyvsp[0].x); + } +#line 3601 "src/preproc/pic/pic.cpp" + break; + + case 135: /* object_spec: object_spec ALIGNED */ +#line 1105 "../src/preproc/pic/pic.ypp" + { + (yyval.spec) = (yyvsp[-1].spec); + (yyval.spec)->flags |= IS_ALIGNED; + } +#line 3610 "src/preproc/pic/pic.cpp" + break; + + case 136: /* text: TEXT */ +#line 1113 "../src/preproc/pic/pic.ypp" + { (yyval.lstr) = (yyvsp[0].lstr); } +#line 3616 "src/preproc/pic/pic.cpp" + break; + + case 137: /* text: SPRINTF '(' TEXT sprintf_args ')' */ +#line 1115 "../src/preproc/pic/pic.ypp" + { + (yyval.lstr).filename = (yyvsp[-2].lstr).filename; + (yyval.lstr).lineno = (yyvsp[-2].lstr).lineno; + (yyval.lstr).str = do_sprintf((yyvsp[-2].lstr).str, (yyvsp[-1].dv).v, (yyvsp[-1].dv).nv); + delete[] (yyvsp[-1].dv).v; + free((yyvsp[-2].lstr).str); + } +#line 3628 "src/preproc/pic/pic.cpp" + break; + + case 138: /* sprintf_args: %empty */ +#line 1126 "../src/preproc/pic/pic.ypp" + { + (yyval.dv).v = 0; + (yyval.dv).nv = 0; + (yyval.dv).maxv = 0; + } +#line 3638 "src/preproc/pic/pic.cpp" + break; + + case 139: /* sprintf_args: sprintf_args ',' expr */ +#line 1132 "../src/preproc/pic/pic.ypp" + { + (yyval.dv) = (yyvsp[-2].dv); + if ((yyval.dv).nv >= (yyval.dv).maxv) { + if ((yyval.dv).nv == 0) { + (yyval.dv).v = new double[4]; + (yyval.dv).maxv = 4; + } + else { + double *oldv = (yyval.dv).v; + (yyval.dv).maxv *= 2; +#if 0 + (yyval.dv).v = new double[(yyval.dv).maxv]; + memcpy((yyval.dv).v, oldv, (yyval.dv).nv*sizeof(double)); +#else + // workaround for bug in Compaq C++ V6.5-033 + // for Compaq Tru64 UNIX V5.1A (Rev. 1885) + double *foo = new double[(yyval.dv).maxv]; + memcpy(foo, oldv, (yyval.dv).nv*sizeof(double)); + (yyval.dv).v = foo; +#endif + delete[] oldv; + } + } + (yyval.dv).v[(yyval.dv).nv] = (yyvsp[0].x); + (yyval.dv).nv += 1; + } +#line 3669 "src/preproc/pic/pic.cpp" + break; + + case 140: /* position: position_not_place */ +#line 1162 "../src/preproc/pic/pic.ypp" + { (yyval.pair) = (yyvsp[0].pair); } +#line 3675 "src/preproc/pic/pic.cpp" + break; + + case 141: /* position: place */ +#line 1164 "../src/preproc/pic/pic.ypp" + { + position pos = (yyvsp[0].pl); + (yyval.pair).x = pos.x; + (yyval.pair).y = pos.y; + } +#line 3685 "src/preproc/pic/pic.cpp" + break; + + case 142: /* position: '(' place ')' */ +#line 1170 "../src/preproc/pic/pic.ypp" + { + position pos = (yyvsp[-1].pl); + (yyval.pair).x = pos.x; + (yyval.pair).y = pos.y; + } +#line 3695 "src/preproc/pic/pic.cpp" + break; + + case 143: /* position_not_place: expr_pair */ +#line 1179 "../src/preproc/pic/pic.ypp" + { (yyval.pair) = (yyvsp[0].pair); } +#line 3701 "src/preproc/pic/pic.cpp" + break; + + case 144: /* position_not_place: position '+' expr_pair */ +#line 1181 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (yyvsp[-2].pair).x + (yyvsp[0].pair).x; + (yyval.pair).y = (yyvsp[-2].pair).y + (yyvsp[0].pair).y; + } +#line 3710 "src/preproc/pic/pic.cpp" + break; + + case 145: /* position_not_place: '(' position '+' expr_pair ')' */ +#line 1186 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (yyvsp[-3].pair).x + (yyvsp[-1].pair).x; + (yyval.pair).y = (yyvsp[-3].pair).y + (yyvsp[-1].pair).y; + } +#line 3719 "src/preproc/pic/pic.cpp" + break; + + case 146: /* position_not_place: position '-' expr_pair */ +#line 1191 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (yyvsp[-2].pair).x - (yyvsp[0].pair).x; + (yyval.pair).y = (yyvsp[-2].pair).y - (yyvsp[0].pair).y; + } +#line 3728 "src/preproc/pic/pic.cpp" + break; + + case 147: /* position_not_place: '(' position '-' expr_pair ')' */ +#line 1196 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (yyvsp[-3].pair).x - (yyvsp[-1].pair).x; + (yyval.pair).y = (yyvsp[-3].pair).y - (yyvsp[-1].pair).y; + } +#line 3737 "src/preproc/pic/pic.cpp" + break; + + case 148: /* position_not_place: '(' position ',' position ')' */ +#line 1201 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (yyvsp[-3].pair).x; + (yyval.pair).y = (yyvsp[-1].pair).y; + } +#line 3746 "src/preproc/pic/pic.cpp" + break; + + case 149: /* position_not_place: expr between position AND position */ +#line 1206 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (1.0 - (yyvsp[-4].x))*(yyvsp[-2].pair).x + (yyvsp[-4].x)*(yyvsp[0].pair).x; + (yyval.pair).y = (1.0 - (yyvsp[-4].x))*(yyvsp[-2].pair).y + (yyvsp[-4].x)*(yyvsp[0].pair).y; + } +#line 3755 "src/preproc/pic/pic.cpp" + break; + + case 150: /* position_not_place: '(' expr between position AND position ')' */ +#line 1211 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).x + (yyvsp[-5].x)*(yyvsp[-1].pair).x; + (yyval.pair).y = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).y + (yyvsp[-5].x)*(yyvsp[-1].pair).y; + } +#line 3764 "src/preproc/pic/pic.cpp" + break; + + case 151: /* position_not_place: expr_not_lower_than '<' position ',' position '>' */ +#line 1217 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).x + (yyvsp[-5].x)*(yyvsp[-1].pair).x; + (yyval.pair).y = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).y + (yyvsp[-5].x)*(yyvsp[-1].pair).y; + } +#line 3773 "src/preproc/pic/pic.cpp" + break; + + case 152: /* position_not_place: '(' expr_not_lower_than '<' position ',' position '>' ')' */ +#line 1222 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (1.0 - (yyvsp[-6].x))*(yyvsp[-4].pair).x + (yyvsp[-6].x)*(yyvsp[-2].pair).x; + (yyval.pair).y = (1.0 - (yyvsp[-6].x))*(yyvsp[-4].pair).y + (yyvsp[-6].x)*(yyvsp[-2].pair).y; + } +#line 3782 "src/preproc/pic/pic.cpp" + break; + + case 155: /* expr_pair: expr ',' expr */ +#line 1235 "../src/preproc/pic/pic.ypp" + { + (yyval.pair).x = (yyvsp[-2].x); + (yyval.pair).y = (yyvsp[0].x); + } +#line 3791 "src/preproc/pic/pic.cpp" + break; + + case 156: /* expr_pair: '(' expr_pair ')' */ +#line 1240 "../src/preproc/pic/pic.ypp" + { (yyval.pair) = (yyvsp[-1].pair); } +#line 3797 "src/preproc/pic/pic.cpp" + break; + + case 157: /* place: label */ +#line 1246 "../src/preproc/pic/pic.ypp" + { (yyval.pl) = (yyvsp[0].pl); } +#line 3803 "src/preproc/pic/pic.cpp" + break; + + case 158: /* place: label corner */ +#line 1248 "../src/preproc/pic/pic.ypp" + { + path pth((yyvsp[0].crn)); + if (!pth.follow((yyvsp[-1].pl), & (yyval.pl))) + YYABORT; + } +#line 3813 "src/preproc/pic/pic.cpp" + break; + + case 159: /* place: corner label */ +#line 1254 "../src/preproc/pic/pic.ypp" + { + path pth((yyvsp[-1].crn)); + if (!pth.follow((yyvsp[0].pl), & (yyval.pl))) + YYABORT; + } +#line 3823 "src/preproc/pic/pic.cpp" + break; + + case 160: /* place: corner OF label */ +#line 1260 "../src/preproc/pic/pic.ypp" + { + path pth((yyvsp[-2].crn)); + if (!pth.follow((yyvsp[0].pl), & (yyval.pl))) + YYABORT; + } +#line 3833 "src/preproc/pic/pic.cpp" + break; + + case 161: /* place: HERE */ +#line 1266 "../src/preproc/pic/pic.ypp" + { + (yyval.pl).x = current_position.x; + (yyval.pl).y = current_position.y; + (yyval.pl).obj = 0; + } +#line 3843 "src/preproc/pic/pic.cpp" + break; + + case 162: /* label: LABEL */ +#line 1275 "../src/preproc/pic/pic.ypp" + { + place *p = lookup_label((yyvsp[0].str)); + if (!p) { + lex_error("there is no place '%1'", (yyvsp[0].str)); + YYABORT; + } + (yyval.pl) = *p; + free((yyvsp[0].str)); + } +#line 3857 "src/preproc/pic/pic.cpp" + break; + + case 163: /* label: nth_primitive */ +#line 1285 "../src/preproc/pic/pic.ypp" + { (yyval.pl).obj = (yyvsp[0].obj); } +#line 3863 "src/preproc/pic/pic.cpp" + break; + + case 164: /* label: label '.' LABEL */ +#line 1287 "../src/preproc/pic/pic.ypp" + { + path pth((yyvsp[0].str)); + if (!pth.follow((yyvsp[-2].pl), & (yyval.pl))) + YYABORT; + } +#line 3873 "src/preproc/pic/pic.cpp" + break; + + case 165: /* ordinal: ORDINAL */ +#line 1296 "../src/preproc/pic/pic.ypp" + { (yyval.n) = (yyvsp[0].n); } +#line 3879 "src/preproc/pic/pic.cpp" + break; + + case 166: /* ordinal: '`' any_expr TH */ +#line 1298 "../src/preproc/pic/pic.ypp" + { + // XXX Check for overflow (and non-integers?). + (yyval.n) = (int)(yyvsp[-1].x); + } +#line 3888 "src/preproc/pic/pic.cpp" + break; + + case 167: /* optional_ordinal_last: LAST */ +#line 1306 "../src/preproc/pic/pic.ypp" + { (yyval.n) = 1; } +#line 3894 "src/preproc/pic/pic.cpp" + break; + + case 168: /* optional_ordinal_last: ordinal LAST */ +#line 1308 "../src/preproc/pic/pic.ypp" + { (yyval.n) = (yyvsp[-1].n); } +#line 3900 "src/preproc/pic/pic.cpp" + break; + + case 169: /* nth_primitive: ordinal object_type */ +#line 1313 "../src/preproc/pic/pic.ypp" + { + int count = 0; + object *p; + for (p = olist.head; p != 0; p = p->next) + if (p->type() == (yyvsp[0].obtype) && ++count == (yyvsp[-1].n)) { + (yyval.obj) = p; + break; + } + if (p == 0) { + lex_error("there is no %1%2 %3", (yyvsp[-1].n), ordinal_postfix((yyvsp[-1].n)), + object_type_name((yyvsp[0].obtype))); + YYABORT; + } + } +#line 3919 "src/preproc/pic/pic.cpp" + break; + + case 170: /* nth_primitive: optional_ordinal_last object_type */ +#line 1328 "../src/preproc/pic/pic.ypp" + { + int count = 0; + object *p; + for (p = olist.tail; p != 0; p = p->prev) + if (p->type() == (yyvsp[0].obtype) && ++count == (yyvsp[-1].n)) { + (yyval.obj) = p; + break; + } + if (p == 0) { + lex_error("there is no %1%2 last %3", (yyvsp[-1].n), + ordinal_postfix((yyvsp[-1].n)), object_type_name((yyvsp[0].obtype))); + YYABORT; + } + } +#line 3938 "src/preproc/pic/pic.cpp" + break; + + case 171: /* object_type: BOX */ +#line 1346 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = BOX_OBJECT; } +#line 3944 "src/preproc/pic/pic.cpp" + break; + + case 172: /* object_type: CIRCLE */ +#line 1348 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = CIRCLE_OBJECT; } +#line 3950 "src/preproc/pic/pic.cpp" + break; + + case 173: /* object_type: ELLIPSE */ +#line 1350 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = ELLIPSE_OBJECT; } +#line 3956 "src/preproc/pic/pic.cpp" + break; + + case 174: /* object_type: ARC */ +#line 1352 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = ARC_OBJECT; } +#line 3962 "src/preproc/pic/pic.cpp" + break; + + case 175: /* object_type: LINE */ +#line 1354 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = LINE_OBJECT; } +#line 3968 "src/preproc/pic/pic.cpp" + break; + + case 176: /* object_type: ARROW */ +#line 1356 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = ARROW_OBJECT; } +#line 3974 "src/preproc/pic/pic.cpp" + break; + + case 177: /* object_type: SPLINE */ +#line 1358 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = SPLINE_OBJECT; } +#line 3980 "src/preproc/pic/pic.cpp" + break; + + case 178: /* object_type: '[' ']' */ +#line 1360 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = BLOCK_OBJECT; } +#line 3986 "src/preproc/pic/pic.cpp" + break; + + case 179: /* object_type: TEXT */ +#line 1362 "../src/preproc/pic/pic.ypp" + { (yyval.obtype) = TEXT_OBJECT; } +#line 3992 "src/preproc/pic/pic.cpp" + break; + + case 180: /* label_path: '.' LABEL */ +#line 1367 "../src/preproc/pic/pic.ypp" + { (yyval.pth) = new path((yyvsp[0].str)); } +#line 3998 "src/preproc/pic/pic.cpp" + break; + + case 181: /* label_path: label_path '.' LABEL */ +#line 1369 "../src/preproc/pic/pic.ypp" + { + (yyval.pth) = (yyvsp[-2].pth); + (yyval.pth)->append((yyvsp[0].str)); + } +#line 4007 "src/preproc/pic/pic.cpp" + break; + + case 182: /* relative_path: corner */ +#line 1377 "../src/preproc/pic/pic.ypp" + { (yyval.pth) = new path((yyvsp[0].crn)); } +#line 4013 "src/preproc/pic/pic.cpp" + break; + + case 183: /* relative_path: label_path */ +#line 1381 "../src/preproc/pic/pic.ypp" + { (yyval.pth) = (yyvsp[0].pth); } +#line 4019 "src/preproc/pic/pic.cpp" + break; + + case 184: /* relative_path: label_path corner */ +#line 1383 "../src/preproc/pic/pic.ypp" + { + (yyval.pth) = (yyvsp[-1].pth); + (yyval.pth)->append((yyvsp[0].crn)); + } +#line 4028 "src/preproc/pic/pic.cpp" + break; + + case 185: /* path: relative_path */ +#line 1391 "../src/preproc/pic/pic.ypp" + { (yyval.pth) = (yyvsp[0].pth); } +#line 4034 "src/preproc/pic/pic.cpp" + break; + + case 186: /* path: '(' relative_path ',' relative_path ')' */ +#line 1393 "../src/preproc/pic/pic.ypp" + { + (yyval.pth) = (yyvsp[-3].pth); + (yyval.pth)->set_ypath((yyvsp[-1].pth)); + } +#line 4043 "src/preproc/pic/pic.cpp" + break; + + case 187: /* path: ORDINAL LAST object_type relative_path */ +#line 1399 "../src/preproc/pic/pic.ypp" + { + lex_warning("'%1%2 last %3' in 'with' argument ignored", + (yyvsp[-3].n), ordinal_postfix((yyvsp[-3].n)), object_type_name((yyvsp[-1].obtype))); + (yyval.pth) = (yyvsp[0].pth); + } +#line 4053 "src/preproc/pic/pic.cpp" + break; + + case 188: /* path: LAST object_type relative_path */ +#line 1405 "../src/preproc/pic/pic.ypp" + { + lex_warning("'last %1' in 'with' argument ignored", + object_type_name((yyvsp[-1].obtype))); + (yyval.pth) = (yyvsp[0].pth); + } +#line 4063 "src/preproc/pic/pic.cpp" + break; + + case 189: /* path: ORDINAL object_type relative_path */ +#line 1411 "../src/preproc/pic/pic.ypp" + { + lex_warning("'%1%2 %3' in 'with' argument ignored", + (yyvsp[-2].n), ordinal_postfix((yyvsp[-2].n)), object_type_name((yyvsp[-1].obtype))); + (yyval.pth) = (yyvsp[0].pth); + } +#line 4073 "src/preproc/pic/pic.cpp" + break; + + case 190: /* path: LABEL relative_path */ +#line 1417 "../src/preproc/pic/pic.ypp" + { + lex_warning("initial '%1' in 'with' argument ignored", (yyvsp[-1].str)); + delete[] (yyvsp[-1].str); + (yyval.pth) = (yyvsp[0].pth); + } +#line 4083 "src/preproc/pic/pic.cpp" + break; + + case 191: /* corner: DOT_N */ +#line 1426 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north; } +#line 4089 "src/preproc/pic/pic.cpp" + break; + + case 192: /* corner: DOT_E */ +#line 1428 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::east; } +#line 4095 "src/preproc/pic/pic.cpp" + break; + + case 193: /* corner: DOT_W */ +#line 1430 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::west; } +#line 4101 "src/preproc/pic/pic.cpp" + break; + + case 194: /* corner: DOT_S */ +#line 1432 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south; } +#line 4107 "src/preproc/pic/pic.cpp" + break; + + case 195: /* corner: DOT_NE */ +#line 1434 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north_east; } +#line 4113 "src/preproc/pic/pic.cpp" + break; + + case 196: /* corner: DOT_SE */ +#line 1436 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object:: south_east; } +#line 4119 "src/preproc/pic/pic.cpp" + break; + + case 197: /* corner: DOT_NW */ +#line 1438 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north_west; } +#line 4125 "src/preproc/pic/pic.cpp" + break; + + case 198: /* corner: DOT_SW */ +#line 1440 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south_west; } +#line 4131 "src/preproc/pic/pic.cpp" + break; + + case 199: /* corner: DOT_C */ +#line 1442 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::center; } +#line 4137 "src/preproc/pic/pic.cpp" + break; + + case 200: /* corner: DOT_START */ +#line 1444 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::start; } +#line 4143 "src/preproc/pic/pic.cpp" + break; + + case 201: /* corner: DOT_END */ +#line 1446 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::end; } +#line 4149 "src/preproc/pic/pic.cpp" + break; + + case 202: /* corner: TOP */ +#line 1448 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north; } +#line 4155 "src/preproc/pic/pic.cpp" + break; + + case 203: /* corner: BOTTOM */ +#line 1450 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south; } +#line 4161 "src/preproc/pic/pic.cpp" + break; + + case 204: /* corner: LEFT */ +#line 1452 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::west; } +#line 4167 "src/preproc/pic/pic.cpp" + break; + + case 205: /* corner: RIGHT */ +#line 1454 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::east; } +#line 4173 "src/preproc/pic/pic.cpp" + break; + + case 206: /* corner: UPPER LEFT */ +#line 1456 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north_west; } +#line 4179 "src/preproc/pic/pic.cpp" + break; + + case 207: /* corner: LOWER LEFT */ +#line 1458 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south_west; } +#line 4185 "src/preproc/pic/pic.cpp" + break; + + case 208: /* corner: UPPER RIGHT */ +#line 1460 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north_east; } +#line 4191 "src/preproc/pic/pic.cpp" + break; + + case 209: /* corner: LOWER RIGHT */ +#line 1462 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south_east; } +#line 4197 "src/preproc/pic/pic.cpp" + break; + + case 210: /* corner: LEFT_CORNER */ +#line 1464 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::west; } +#line 4203 "src/preproc/pic/pic.cpp" + break; + + case 211: /* corner: RIGHT_CORNER */ +#line 1466 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::east; } +#line 4209 "src/preproc/pic/pic.cpp" + break; + + case 212: /* corner: UPPER LEFT_CORNER */ +#line 1468 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north_west; } +#line 4215 "src/preproc/pic/pic.cpp" + break; + + case 213: /* corner: LOWER LEFT_CORNER */ +#line 1470 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south_west; } +#line 4221 "src/preproc/pic/pic.cpp" + break; + + case 214: /* corner: UPPER RIGHT_CORNER */ +#line 1472 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north_east; } +#line 4227 "src/preproc/pic/pic.cpp" + break; + + case 215: /* corner: LOWER RIGHT_CORNER */ +#line 1474 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south_east; } +#line 4233 "src/preproc/pic/pic.cpp" + break; + + case 216: /* corner: NORTH */ +#line 1476 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::north; } +#line 4239 "src/preproc/pic/pic.cpp" + break; + + case 217: /* corner: SOUTH */ +#line 1478 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::south; } +#line 4245 "src/preproc/pic/pic.cpp" + break; + + case 218: /* corner: EAST */ +#line 1480 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::east; } +#line 4251 "src/preproc/pic/pic.cpp" + break; + + case 219: /* corner: WEST */ +#line 1482 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::west; } +#line 4257 "src/preproc/pic/pic.cpp" + break; + + case 220: /* corner: CENTER */ +#line 1484 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::center; } +#line 4263 "src/preproc/pic/pic.cpp" + break; + + case 221: /* corner: START */ +#line 1486 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::start; } +#line 4269 "src/preproc/pic/pic.cpp" + break; + + case 222: /* corner: END */ +#line 1488 "../src/preproc/pic/pic.ypp" + { (yyval.crn) = &object::end; } +#line 4275 "src/preproc/pic/pic.cpp" + break; + + case 223: /* expr: expr_lower_than */ +#line 1493 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[0].x); } +#line 4281 "src/preproc/pic/pic.cpp" + break; + + case 224: /* expr: expr_not_lower_than */ +#line 1495 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[0].x); } +#line 4287 "src/preproc/pic/pic.cpp" + break; + + case 225: /* expr_lower_than: expr '<' expr */ +#line 1500 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) < (yyvsp[0].x)); } +#line 4293 "src/preproc/pic/pic.cpp" + break; + + case 226: /* expr_not_lower_than: VARIABLE */ +#line 1505 "../src/preproc/pic/pic.ypp" + { + if (!lookup_variable((yyvsp[0].str), & (yyval.x))) { + lex_error("there is no variable '%1'", (yyvsp[0].str)); + YYABORT; + } + free((yyvsp[0].str)); + } +#line 4305 "src/preproc/pic/pic.cpp" + break; + + case 227: /* expr_not_lower_than: NUMBER */ +#line 1513 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[0].x); } +#line 4311 "src/preproc/pic/pic.cpp" + break; + + case 228: /* expr_not_lower_than: place DOT_X */ +#line 1515 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[-1].pl).obj != 0) + (yyval.x) = (yyvsp[-1].pl).obj->origin().x; + else + (yyval.x) = (yyvsp[-1].pl).x; + } +#line 4322 "src/preproc/pic/pic.cpp" + break; + + case 229: /* expr_not_lower_than: place DOT_Y */ +#line 1522 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[-1].pl).obj != 0) + (yyval.x) = (yyvsp[-1].pl).obj->origin().y; + else + (yyval.x) = (yyvsp[-1].pl).y; + } +#line 4333 "src/preproc/pic/pic.cpp" + break; + + case 230: /* expr_not_lower_than: place DOT_HT */ +#line 1529 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[-1].pl).obj != 0) + (yyval.x) = (yyvsp[-1].pl).obj->height(); + else + (yyval.x) = 0.0; + } +#line 4344 "src/preproc/pic/pic.cpp" + break; + + case 231: /* expr_not_lower_than: place DOT_WID */ +#line 1536 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[-1].pl).obj != 0) + (yyval.x) = (yyvsp[-1].pl).obj->width(); + else + (yyval.x) = 0.0; + } +#line 4355 "src/preproc/pic/pic.cpp" + break; + + case 232: /* expr_not_lower_than: place DOT_RAD */ +#line 1543 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[-1].pl).obj != 0) + (yyval.x) = (yyvsp[-1].pl).obj->radius(); + else + (yyval.x) = 0.0; + } +#line 4366 "src/preproc/pic/pic.cpp" + break; + + case 233: /* expr_not_lower_than: expr '+' expr */ +#line 1550 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-2].x) + (yyvsp[0].x); } +#line 4372 "src/preproc/pic/pic.cpp" + break; + + case 234: /* expr_not_lower_than: expr '-' expr */ +#line 1552 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-2].x) - (yyvsp[0].x); } +#line 4378 "src/preproc/pic/pic.cpp" + break; + + case 235: /* expr_not_lower_than: expr '*' expr */ +#line 1554 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-2].x) * (yyvsp[0].x); } +#line 4384 "src/preproc/pic/pic.cpp" + break; + + case 236: /* expr_not_lower_than: expr '/' expr */ +#line 1556 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[0].x) == 0.0) { + lex_error("division by zero"); + YYABORT; + } + (yyval.x) = (yyvsp[-2].x)/(yyvsp[0].x); + } +#line 4396 "src/preproc/pic/pic.cpp" + break; + + case 237: /* expr_not_lower_than: expr '%' expr */ +#line 1564 "../src/preproc/pic/pic.ypp" + { + if ((yyvsp[0].x) == 0.0) { + lex_error("modulus by zero"); + YYABORT; + } + (yyval.x) = fmod((yyvsp[-2].x), (yyvsp[0].x)); + } +#line 4408 "src/preproc/pic/pic.cpp" + break; + + case 238: /* expr_not_lower_than: expr '^' expr */ +#line 1572 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = pow((yyvsp[-2].x), (yyvsp[0].x)); + if (errno == EDOM) { + lex_error("arguments to '^' operator out of domain"); + YYABORT; + } + if (errno == ERANGE) { + lex_error("result of '^' operator out of range"); + YYABORT; + } + } +#line 4425 "src/preproc/pic/pic.cpp" + break; + + case 239: /* expr_not_lower_than: '-' expr */ +#line 1585 "../src/preproc/pic/pic.ypp" + { (yyval.x) = -(yyvsp[0].x); } +#line 4431 "src/preproc/pic/pic.cpp" + break; + + case 240: /* expr_not_lower_than: '(' any_expr ')' */ +#line 1587 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-1].x); } +#line 4437 "src/preproc/pic/pic.cpp" + break; + + case 241: /* expr_not_lower_than: SIN '(' any_expr ')' */ +#line 1589 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = sin((yyvsp[-1].x)); + if (errno == ERANGE) { + lex_error("sin result out of range"); + YYABORT; + } + } +#line 4450 "src/preproc/pic/pic.cpp" + break; + + case 242: /* expr_not_lower_than: COS '(' any_expr ')' */ +#line 1598 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = cos((yyvsp[-1].x)); + if (errno == ERANGE) { + lex_error("cos result out of range"); + YYABORT; + } + } +#line 4463 "src/preproc/pic/pic.cpp" + break; + + case 243: /* expr_not_lower_than: ATAN2 '(' any_expr ',' any_expr ')' */ +#line 1607 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = atan2((yyvsp[-3].x), (yyvsp[-1].x)); + if (errno == EDOM) { + lex_error("atan2 argument out of domain"); + YYABORT; + } + if (errno == ERANGE) { + lex_error("atan2 result out of range"); + YYABORT; + } + } +#line 4480 "src/preproc/pic/pic.cpp" + break; + + case 244: /* expr_not_lower_than: LOG '(' any_expr ')' */ +#line 1620 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = log10((yyvsp[-1].x)); + if (errno == ERANGE) { + lex_error("log result out of range"); + YYABORT; + } + } +#line 4493 "src/preproc/pic/pic.cpp" + break; + + case 245: /* expr_not_lower_than: EXP '(' any_expr ')' */ +#line 1629 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = pow(10.0, (yyvsp[-1].x)); + if (errno == ERANGE) { + lex_error("exp result out of range"); + YYABORT; + } + } +#line 4506 "src/preproc/pic/pic.cpp" + break; + + case 246: /* expr_not_lower_than: SQRT '(' any_expr ')' */ +#line 1638 "../src/preproc/pic/pic.ypp" + { + errno = 0; + (yyval.x) = sqrt((yyvsp[-1].x)); + if (errno == EDOM) { + lex_error("sqrt argument out of domain"); + YYABORT; + } + } +#line 4519 "src/preproc/pic/pic.cpp" + break; + + case 247: /* expr_not_lower_than: K_MAX '(' any_expr ',' any_expr ')' */ +#line 1647 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-3].x) > (yyvsp[-1].x) ? (yyvsp[-3].x) : (yyvsp[-1].x); } +#line 4525 "src/preproc/pic/pic.cpp" + break; + + case 248: /* expr_not_lower_than: K_MIN '(' any_expr ',' any_expr ')' */ +#line 1649 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-3].x) < (yyvsp[-1].x) ? (yyvsp[-3].x) : (yyvsp[-1].x); } +#line 4531 "src/preproc/pic/pic.cpp" + break; + + case 249: /* expr_not_lower_than: INT '(' any_expr ')' */ +#line 1651 "../src/preproc/pic/pic.ypp" + { (yyval.x) = (yyvsp[-1].x) < 0 ? -floor(-(yyvsp[-1].x)) : floor((yyvsp[-1].x)); } +#line 4537 "src/preproc/pic/pic.cpp" + break; + + case 250: /* expr_not_lower_than: RAND '(' any_expr ')' */ +#line 1653 "../src/preproc/pic/pic.ypp" + { + lex_error("use of 'rand' with an argument is" + " deprecated; shift and scale 'rand()' with" + " arithmetic instead"); + (yyval.x) = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*(yyvsp[-1].x)); + } +#line 4548 "src/preproc/pic/pic.cpp" + break; + + case 251: /* expr_not_lower_than: RAND '(' ')' */ +#line 1660 "../src/preproc/pic/pic.ypp" + { + /* return a random number in the range [0,1) */ + /* portable, but not very random */ + (yyval.x) = (rand() & 0x7fff) / double(0x8000); + } +#line 4558 "src/preproc/pic/pic.cpp" + break; + + case 252: /* expr_not_lower_than: SRAND '(' any_expr ')' */ +#line 1666 "../src/preproc/pic/pic.ypp" + { + (yyval.x) = 0; + srand((unsigned int)(yyvsp[-1].x)); + } +#line 4567 "src/preproc/pic/pic.cpp" + break; + + case 253: /* expr_not_lower_than: expr LESSEQUAL expr */ +#line 1671 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) <= (yyvsp[0].x)); } +#line 4573 "src/preproc/pic/pic.cpp" + break; + + case 254: /* expr_not_lower_than: expr '>' expr */ +#line 1673 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) > (yyvsp[0].x)); } +#line 4579 "src/preproc/pic/pic.cpp" + break; + + case 255: /* expr_not_lower_than: expr GREATEREQUAL expr */ +#line 1675 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) >= (yyvsp[0].x)); } +#line 4585 "src/preproc/pic/pic.cpp" + break; + + case 256: /* expr_not_lower_than: expr EQUALEQUAL expr */ +#line 1677 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) == (yyvsp[0].x)); } +#line 4591 "src/preproc/pic/pic.cpp" + break; + + case 257: /* expr_not_lower_than: expr NOTEQUAL expr */ +#line 1679 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != (yyvsp[0].x)); } +#line 4597 "src/preproc/pic/pic.cpp" + break; + + case 258: /* expr_not_lower_than: expr ANDAND expr */ +#line 1681 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); } +#line 4603 "src/preproc/pic/pic.cpp" + break; + + case 259: /* expr_not_lower_than: expr OROR expr */ +#line 1683 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); } +#line 4609 "src/preproc/pic/pic.cpp" + break; + + case 260: /* expr_not_lower_than: '!' expr */ +#line 1685 "../src/preproc/pic/pic.ypp" + { (yyval.x) = ((yyvsp[0].x) == 0.0); } +#line 4615 "src/preproc/pic/pic.cpp" + break; + + +#line 4619 "src/preproc/pic/pic.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 1689 "../src/preproc/pic/pic.ypp" + + +/* bison defines const to be empty unless __STDC__ is defined, which it +isn't under cfront */ + +#ifdef const +#undef const +#endif + +static struct { + const char *name; + double val; + int scaled; // non-zero if val should be multiplied by scale +} defaults_table[] = { + { "arcrad", .25, 1 }, + { "arrowht", .1, 1 }, + { "arrowwid", .05, 1 }, + { "circlerad", .25, 1 }, + { "boxht", .5, 1 }, + { "boxwid", .75, 1 }, + { "boxrad", 0.0, 1 }, + { "dashwid", .05, 1 }, + { "ellipseht", .5, 1 }, + { "ellipsewid", .75, 1 }, + { "moveht", .5, 1 }, + { "movewid", .5, 1 }, + { "lineht", .5, 1 }, + { "linewid", .5, 1 }, + { "textht", 0.0, 1 }, + { "textwid", 0.0, 1 }, + { "scale", 1.0, 0 }, + { "linethick", -1.0, 0 }, // in points + { "fillval", .5, 0 }, + { "arrowhead", 1.0, 0 }, + { "maxpswid", 8.5, 0 }, + { "maxpsht", 11.0, 0 }, +}; + +place *lookup_label(const char *label) +{ + saved_state *state = current_saved_state; + PTABLE(place) *tbl = current_table; + for (;;) { + place *pl = tbl->lookup(label); + if (pl) + return pl; + if (!state) + return 0; + tbl = state->tbl; + state = state->prev; + } +} + +void define_label(const char *label, const place *pl) +{ + place *p = new place[1]; + *p = *pl; + current_table->define(label, p); +} + +int lookup_variable(const char *name, double *val) +{ + place *pl = lookup_label(name); + if (pl) { + *val = pl->x; + return 1; + } + return 0; +} + +void define_variable(const char *name, double val) +{ + place *p = new place[1]; + p->obj = 0; + p->x = val; + p->y = 0.0; + current_table->define(name, p); + if (strcmp(name, "scale") == 0) { + // When the scale changes, reset all scaled predefined variables to + // their default values. + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (defaults_table[i].scaled) + define_variable(defaults_table[i].name, val*defaults_table[i].val); + } +} + +// called once only (not once per parse) + +void parse_init() +{ + current_direction = RIGHT_DIRECTION; + current_position.x = 0.0; + current_position.y = 0.0; + // This resets everything to its default value. + reset_all(); +} + +void reset(const char *nm) +{ + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (strcmp(nm, defaults_table[i].name) == 0) { + double val = defaults_table[i].val; + if (defaults_table[i].scaled) { + double scale; + lookup_variable("scale", &scale); + val *= scale; + } + define_variable(defaults_table[i].name, val); + return; + } + lex_error("'%1' is not a predefined variable", nm); +} + +void reset_all() +{ + // We only have to explicitly reset the predefined variables that + // aren't scaled because 'scale' is not scaled, and changing the + // value of 'scale' will reset all the predefined variables that + // are scaled. + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (!defaults_table[i].scaled) + define_variable(defaults_table[i].name, defaults_table[i].val); +} + +// called after each parse + +void parse_cleanup() +{ + while (current_saved_state != 0) { + delete current_table; + current_table = current_saved_state->tbl; + saved_state *tem = current_saved_state; + current_saved_state = current_saved_state->prev; + delete tem; + } + assert(current_table == &top_table); + PTABLE_ITERATOR(place) iter(current_table); + const char *key; + place *pl; + while (iter.next(&key, &pl)) + if (pl->obj != 0) { + position pos = pl->obj->origin(); + pl->obj = 0; + pl->x = pos.x; + pl->y = pos.y; + } + while (olist.head != 0) { + object *tem = olist.head; + olist.head = olist.head->next; + delete tem; + } + olist.tail = 0; + current_direction = RIGHT_DIRECTION; + current_position.x = 0.0; + current_position.y = 0.0; +} + +const char *ordinal_postfix(int n) +{ + if (n < 10 || n > 20) + switch (n % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + } + return "th"; +} + +const char *object_type_name(object_type type) +{ + switch (type) { + case BOX_OBJECT: + return "box"; + case CIRCLE_OBJECT: + return "circle"; + case ELLIPSE_OBJECT: + return "ellipse"; + case ARC_OBJECT: + return "arc"; + case SPLINE_OBJECT: + return "spline"; + case LINE_OBJECT: + return "line"; + case ARROW_OBJECT: + return "arrow"; + case MOVE_OBJECT: + return "move"; + case TEXT_OBJECT: + return "\"\""; + case BLOCK_OBJECT: + return "[]"; + case OTHER_OBJECT: + case MARK_OBJECT: + default: + break; + } + return "object"; +} + +static char sprintf_buf[1024]; + +char *format_number(const char *fmt, double n) +{ + if (0 /* nullptr */ == fmt) + fmt = "%g"; + return do_sprintf(fmt, &n, 1); +} + +char *do_sprintf(const char *fmt, const double *v, int nv) +{ + // Define valid conversion specifiers and modifiers. + static const char spcs[] = "eEfgG%"; + static const char mods[] = "#-+ 0123456789."; + string result; + int i = 0; + string one_format; + while (*fmt) { + if ('%' == *fmt) { + one_format += *fmt++; + for (; *fmt != '\0' && strchr(mods, *fmt) != 0; fmt++) + one_format += *fmt; + if ('\0' == *fmt || strchr(spcs, *fmt) == 0) { + lex_error("invalid sprintf conversion specifier '%1'", *fmt); + result += one_format; + result += fmt; + break; + } + if ('%' == *fmt) { + fmt++; + snprintf(sprintf_buf, sizeof(sprintf_buf), "%%"); + } + else { + if (i >= nv) { + lex_error("too few arguments to sprintf"); + result += one_format; + result += fmt; + break; + } + one_format += *fmt++; + one_format += '\0'; +// We validated the format string above. Most conversion specifiers are +// rejected, including `n`. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + snprintf(sprintf_buf, sizeof(sprintf_buf), + one_format.contents(), v[i++]); +#pragma GCC diagnostic pop + } + one_format.clear(); + result += sprintf_buf; + } + else + result += *fmt++; + } + result += '\0'; + return strsave(result.contents()); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/pic/pic.h b/src/preproc/pic/pic.h new file mode 100644 index 0000000..1a99498 --- /dev/null +++ b/src/preproc/pic/pic.h @@ -0,0 +1,122 @@ +/* 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 <assert.h> +#include <errno.h> +#include <math.h> +#include <stdlib.h> + +#ifdef NEED_DECLARATION_RAND +#undef rand +extern "C" { + int rand(); +} +#endif /* NEED_DECLARATION_RAND */ + +#ifdef NEED_DECLARATION_SRAND +#undef srand +extern "C" { +#ifdef RET_TYPE_SRAND_IS_VOID + void srand(unsigned int); +#else + int srand(unsigned int); +#endif +} +#endif /* NEED_DECLARATION_SRAND */ + +#ifndef HAVE_FMOD +extern "C" { + double fmod(double, double); +} +#endif + +#include "cset.h" +#include "stringclass.h" +#include "lf.h" +#include "errarg.h" +#include "error.h" +#include "position.h" +#include "text.h" +#include "output.h" + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +class input { + input *next; +public: + input(); + virtual ~input(); + virtual int get() = 0; + virtual int peek() = 0; + virtual int get_location(const char **, int *); + friend class input_stack; + friend class copy_rest_thru_input; +}; + +class file_input : public input { + FILE *fp; + const char *filename; + int lineno; + string line; + const char *ptr; + int read_line(); +public: + file_input(FILE *, const char *); + ~file_input(); + int get(); + int peek(); + int get_location(const char **, int *); +}; + +void lex_init(input *); +int get_location(char **, int *); + +void do_copy(const char *file); +void parse_init(); +void parse_cleanup(); + +void lex_error(const char *message, + const errarg &arg1 = empty_errarg, + const errarg &arg2 = empty_errarg, + const errarg &arg3 = empty_errarg); + +void lex_warning(const char *message, + const errarg &arg1 = empty_errarg, + const errarg &arg2 = empty_errarg, + const errarg &arg3 = empty_errarg); + +void lex_cleanup(); + +extern bool want_flyback; +extern bool want_alternate_flyback; +extern int command_char; +// zero_length_line_flag is non-zero if zero-length lines are drawn +// as dots by the output device +extern int zero_length_line_flag; +extern int driver_extension_flag; +extern int compatible_flag; +extern int safer_flag; +extern char *graphname; diff --git a/src/preproc/pic/pic.hpp b/src/preproc/pic/pic.hpp new file mode 100644 index 0000000..c162496 --- /dev/null +++ b/src/preproc/pic/pic.hpp @@ -0,0 +1,348 @@ +/* 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_PIC_PIC_HPP_INCLUDED +# define YY_YY_SRC_PREPROC_PIC_PIC_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" */ + LABEL = 258, /* LABEL */ + VARIABLE = 259, /* VARIABLE */ + NUMBER = 260, /* NUMBER */ + TEXT = 261, /* TEXT */ + COMMAND_LINE = 262, /* COMMAND_LINE */ + DELIMITED = 263, /* DELIMITED */ + ORDINAL = 264, /* ORDINAL */ + TH = 265, /* TH */ + LEFT_ARROW_HEAD = 266, /* LEFT_ARROW_HEAD */ + RIGHT_ARROW_HEAD = 267, /* RIGHT_ARROW_HEAD */ + DOUBLE_ARROW_HEAD = 268, /* DOUBLE_ARROW_HEAD */ + LAST = 269, /* LAST */ + BOX = 270, /* BOX */ + CIRCLE = 271, /* CIRCLE */ + ELLIPSE = 272, /* ELLIPSE */ + ARC = 273, /* ARC */ + LINE = 274, /* LINE */ + ARROW = 275, /* ARROW */ + MOVE = 276, /* MOVE */ + SPLINE = 277, /* SPLINE */ + HEIGHT = 278, /* HEIGHT */ + RADIUS = 279, /* RADIUS */ + FIGNAME = 280, /* FIGNAME */ + WIDTH = 281, /* WIDTH */ + DIAMETER = 282, /* DIAMETER */ + UP = 283, /* UP */ + DOWN = 284, /* DOWN */ + RIGHT = 285, /* RIGHT */ + LEFT = 286, /* LEFT */ + FROM = 287, /* FROM */ + TO = 288, /* TO */ + AT = 289, /* AT */ + WITH = 290, /* WITH */ + BY = 291, /* BY */ + THEN = 292, /* THEN */ + SOLID = 293, /* SOLID */ + DOTTED = 294, /* DOTTED */ + DASHED = 295, /* DASHED */ + CHOP = 296, /* CHOP */ + SAME = 297, /* SAME */ + INVISIBLE = 298, /* INVISIBLE */ + LJUST = 299, /* LJUST */ + RJUST = 300, /* RJUST */ + ABOVE = 301, /* ABOVE */ + BELOW = 302, /* BELOW */ + OF = 303, /* OF */ + THE = 304, /* THE */ + WAY = 305, /* WAY */ + BETWEEN = 306, /* BETWEEN */ + AND = 307, /* AND */ + HERE = 308, /* HERE */ + DOT_N = 309, /* DOT_N */ + DOT_E = 310, /* DOT_E */ + DOT_W = 311, /* DOT_W */ + DOT_S = 312, /* DOT_S */ + DOT_NE = 313, /* DOT_NE */ + DOT_SE = 314, /* DOT_SE */ + DOT_NW = 315, /* DOT_NW */ + DOT_SW = 316, /* DOT_SW */ + DOT_C = 317, /* DOT_C */ + DOT_START = 318, /* DOT_START */ + DOT_END = 319, /* DOT_END */ + DOT_X = 320, /* DOT_X */ + DOT_Y = 321, /* DOT_Y */ + DOT_HT = 322, /* DOT_HT */ + DOT_WID = 323, /* DOT_WID */ + DOT_RAD = 324, /* DOT_RAD */ + SIN = 325, /* SIN */ + COS = 326, /* COS */ + ATAN2 = 327, /* ATAN2 */ + LOG = 328, /* LOG */ + EXP = 329, /* EXP */ + SQRT = 330, /* SQRT */ + K_MAX = 331, /* K_MAX */ + K_MIN = 332, /* K_MIN */ + INT = 333, /* INT */ + RAND = 334, /* RAND */ + SRAND = 335, /* SRAND */ + COPY = 336, /* COPY */ + THRU = 337, /* THRU */ + TOP = 338, /* TOP */ + BOTTOM = 339, /* BOTTOM */ + UPPER = 340, /* UPPER */ + LOWER = 341, /* LOWER */ + SH = 342, /* SH */ + PRINT = 343, /* PRINT */ + CW = 344, /* CW */ + CCW = 345, /* CCW */ + FOR = 346, /* FOR */ + DO = 347, /* DO */ + IF = 348, /* IF */ + ELSE = 349, /* ELSE */ + ANDAND = 350, /* ANDAND */ + OROR = 351, /* OROR */ + NOTEQUAL = 352, /* NOTEQUAL */ + EQUALEQUAL = 353, /* EQUALEQUAL */ + LESSEQUAL = 354, /* LESSEQUAL */ + GREATEREQUAL = 355, /* GREATEREQUAL */ + LEFT_CORNER = 356, /* LEFT_CORNER */ + RIGHT_CORNER = 357, /* RIGHT_CORNER */ + NORTH = 358, /* NORTH */ + SOUTH = 359, /* SOUTH */ + EAST = 360, /* EAST */ + WEST = 361, /* WEST */ + CENTER = 362, /* CENTER */ + END = 363, /* END */ + START = 364, /* START */ + RESET = 365, /* RESET */ + UNTIL = 366, /* UNTIL */ + PLOT = 367, /* PLOT */ + THICKNESS = 368, /* THICKNESS */ + FILL = 369, /* FILL */ + COLORED = 370, /* COLORED */ + OUTLINED = 371, /* OUTLINED */ + SHADED = 372, /* SHADED */ + XSLANTED = 373, /* XSLANTED */ + YSLANTED = 374, /* YSLANTED */ + ALIGNED = 375, /* ALIGNED */ + SPRINTF = 376, /* SPRINTF */ + COMMAND = 377, /* COMMAND */ + DEFINE = 378, /* DEFINE */ + UNDEF = 379 /* UNDEF */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define LABEL 258 +#define VARIABLE 259 +#define NUMBER 260 +#define TEXT 261 +#define COMMAND_LINE 262 +#define DELIMITED 263 +#define ORDINAL 264 +#define TH 265 +#define LEFT_ARROW_HEAD 266 +#define RIGHT_ARROW_HEAD 267 +#define DOUBLE_ARROW_HEAD 268 +#define LAST 269 +#define BOX 270 +#define CIRCLE 271 +#define ELLIPSE 272 +#define ARC 273 +#define LINE 274 +#define ARROW 275 +#define MOVE 276 +#define SPLINE 277 +#define HEIGHT 278 +#define RADIUS 279 +#define FIGNAME 280 +#define WIDTH 281 +#define DIAMETER 282 +#define UP 283 +#define DOWN 284 +#define RIGHT 285 +#define LEFT 286 +#define FROM 287 +#define TO 288 +#define AT 289 +#define WITH 290 +#define BY 291 +#define THEN 292 +#define SOLID 293 +#define DOTTED 294 +#define DASHED 295 +#define CHOP 296 +#define SAME 297 +#define INVISIBLE 298 +#define LJUST 299 +#define RJUST 300 +#define ABOVE 301 +#define BELOW 302 +#define OF 303 +#define THE 304 +#define WAY 305 +#define BETWEEN 306 +#define AND 307 +#define HERE 308 +#define DOT_N 309 +#define DOT_E 310 +#define DOT_W 311 +#define DOT_S 312 +#define DOT_NE 313 +#define DOT_SE 314 +#define DOT_NW 315 +#define DOT_SW 316 +#define DOT_C 317 +#define DOT_START 318 +#define DOT_END 319 +#define DOT_X 320 +#define DOT_Y 321 +#define DOT_HT 322 +#define DOT_WID 323 +#define DOT_RAD 324 +#define SIN 325 +#define COS 326 +#define ATAN2 327 +#define LOG 328 +#define EXP 329 +#define SQRT 330 +#define K_MAX 331 +#define K_MIN 332 +#define INT 333 +#define RAND 334 +#define SRAND 335 +#define COPY 336 +#define THRU 337 +#define TOP 338 +#define BOTTOM 339 +#define UPPER 340 +#define LOWER 341 +#define SH 342 +#define PRINT 343 +#define CW 344 +#define CCW 345 +#define FOR 346 +#define DO 347 +#define IF 348 +#define ELSE 349 +#define ANDAND 350 +#define OROR 351 +#define NOTEQUAL 352 +#define EQUALEQUAL 353 +#define LESSEQUAL 354 +#define GREATEREQUAL 355 +#define LEFT_CORNER 356 +#define RIGHT_CORNER 357 +#define NORTH 358 +#define SOUTH 359 +#define EAST 360 +#define WEST 361 +#define CENTER 362 +#define END 363 +#define START 364 +#define RESET 365 +#define UNTIL 366 +#define PLOT 367 +#define THICKNESS 368 +#define FILL 369 +#define COLORED 370 +#define OUTLINED 371 +#define SHADED 372 +#define XSLANTED 373 +#define YSLANTED 374 +#define ALIGNED 375 +#define SPRINTF 376 +#define COMMAND 377 +#define DEFINE 378 +#define UNDEF 379 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 65 "../src/preproc/pic/pic.ypp" + + char *str; + int n; + double x; + struct { double x, y; } pair; + struct { double x; char *body; } if_data; + struct { char *str; const char *filename; int lineno; } lstr; + struct { double *v; int nv; int maxv; } dv; + struct { double val; int is_multiplicative; } by; + place pl; + object *obj; + corner crn; + path *pth; + object_spec *spec; + saved_state *pstate; + graphics_state state; + object_type obtype; + +#line 334 "src/preproc/pic/pic.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_PIC_PIC_HPP_INCLUDED */ diff --git a/src/preproc/pic/pic.ypp b/src/preproc/pic/pic.ypp new file mode 100644 index 0000000..b2fa6dc --- /dev/null +++ b/src/preproc/pic/pic.ypp @@ -0,0 +1,1957 @@ +/* Copyright (C) 1989-2022 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 "pic.h" +#include "ptable.h" +#include "object.h" + +extern int delim_flag; +extern void copy_rest_thru(const char *, const char *); +extern void copy_file_thru(const char *, const char *, const char *); +extern void push_body(const char *); +extern void do_for(char *var, double from, double to, + int by_is_multiplicative, double by, char *body); +extern void do_lookahead(); + +/* Maximum number of characters produced by printf("%g") */ +#define GDIGITS 14 + +int yylex(); +void yyerror(const char *); + +void reset(const char *nm); +void reset_all(); + +place *lookup_label(const char *); +void define_label(const char *label, const place *pl); + +direction current_direction; +position current_position; + +implement_ptable(place) + +PTABLE(place) top_table; + +PTABLE(place) *current_table = &top_table; +saved_state *current_saved_state = 0; + +object_list olist; + +const char *ordinal_postfix(int n); +const char *object_type_name(object_type type); +char *format_number(const char *fmt, double n); +char *do_sprintf(const char *fmt, const double *v, int nv); + +%} + +%expect 2 + +%union { + char *str; + int n; + double x; + struct { double x, y; } pair; + struct { double x; char *body; } if_data; + struct { char *str; const char *filename; int lineno; } lstr; + struct { double *v; int nv; int maxv; } dv; + struct { double val; int is_multiplicative; } by; + place pl; + object *obj; + corner crn; + path *pth; + object_spec *spec; + saved_state *pstate; + graphics_state state; + object_type obtype; +} + +%token <str> LABEL +%token <str> VARIABLE +%token <x> NUMBER +%token <lstr> TEXT +%token <lstr> COMMAND_LINE +%token <str> DELIMITED +%token <n> ORDINAL +%token TH +%token LEFT_ARROW_HEAD +%token RIGHT_ARROW_HEAD +%token DOUBLE_ARROW_HEAD +%token LAST +%token BOX +%token CIRCLE +%token ELLIPSE +%token ARC +%token LINE +%token ARROW +%token MOVE +%token SPLINE +%token HEIGHT +%token RADIUS +%token FIGNAME +%token WIDTH +%token DIAMETER +%token UP +%token DOWN +%token RIGHT +%token LEFT +%token FROM +%token TO +%token AT +%token WITH +%token BY +%token THEN +%token SOLID +%token DOTTED +%token DASHED +%token CHOP +%token SAME +%token INVISIBLE +%token LJUST +%token RJUST +%token ABOVE +%token BELOW +%token OF +%token THE +%token WAY +%token BETWEEN +%token AND +%token HERE +%token DOT_N +%token DOT_E +%token DOT_W +%token DOT_S +%token DOT_NE +%token DOT_SE +%token DOT_NW +%token DOT_SW +%token DOT_C +%token DOT_START +%token DOT_END +%token DOT_X +%token DOT_Y +%token DOT_HT +%token DOT_WID +%token DOT_RAD +%token SIN +%token COS +%token ATAN2 +%token LOG +%token EXP +%token SQRT +%token K_MAX +%token K_MIN +%token INT +%token RAND +%token SRAND +%token COPY +%token THRU +%token TOP +%token BOTTOM +%token UPPER +%token LOWER +%token SH +%token PRINT +%token CW +%token CCW +%token FOR +%token DO +%token IF +%token ELSE +%token ANDAND +%token OROR +%token NOTEQUAL +%token EQUALEQUAL +%token LESSEQUAL +%token GREATEREQUAL +%token LEFT_CORNER +%token RIGHT_CORNER +%token NORTH +%token SOUTH +%token EAST +%token WEST +%token CENTER +%token END +%token START +%token RESET +%token UNTIL +%token PLOT +%token THICKNESS +%token FILL +%token COLORED +%token OUTLINED +%token SHADED +%token XSLANTED +%token YSLANTED +%token ALIGNED +%token SPRINTF +%token COMMAND + +%token DEFINE +%token UNDEF + +%left '.' + +/* this ensures that plot 17 "%g" parses as (plot 17 "%g") */ +%left PLOT +%left TEXT SPRINTF + +/* give text adjustments higher precedence than TEXT, so that +box "foo" above ljust == box ("foo" above ljust) +*/ + +%left LJUST RJUST ABOVE BELOW + +%left LEFT RIGHT +/* Give attributes that take an optional expression a higher +precedence than left and right, so that, e.g., 'line chop left' +parses properly. */ +%left CHOP SOLID DASHED DOTTED UP DOWN FILL COLORED OUTLINED +%left XSLANTED YSLANTED +%left LABEL + +%left VARIABLE NUMBER '(' SIN COS ATAN2 LOG EXP SQRT K_MAX K_MIN INT RAND SRAND LAST +%left ORDINAL HERE '`' + +%left BOX CIRCLE ELLIPSE ARC LINE ARROW SPLINE '[' + +/* these need to be lower than '-' */ +%left HEIGHT RADIUS WIDTH DIAMETER FROM TO AT THICKNESS + +/* these must have higher precedence than CHOP so that 'label %prec CHOP' +works */ +%left DOT_N DOT_E DOT_W DOT_S DOT_NE DOT_SE DOT_NW DOT_SW DOT_C +%left DOT_START DOT_END TOP BOTTOM LEFT_CORNER RIGHT_CORNER +%left UPPER LOWER NORTH SOUTH EAST WEST CENTER START END + +%left ',' +%left OROR +%left ANDAND +%left EQUALEQUAL NOTEQUAL +%left '<' '>' LESSEQUAL GREATEREQUAL + +%left BETWEEN OF +%left AND + +%left '+' '-' +%left '*' '/' '%' +%right '!' +%right '^' + +%type <x> expr expr_lower_than expr_not_lower_than any_expr text_expr +%type <by> optional_by +%type <pair> expr_pair position_not_place +%type <if_data> simple_if +%type <obj> nth_primitive +%type <crn> corner +%type <pth> path label_path relative_path +%type <pl> place label element element_list middle_element_list +%type <spec> object_spec +%type <pair> position +%type <obtype> object_type +%type <n> optional_ordinal_last ordinal +%type <str> macro_name until +%type <dv> sprintf_args +%type <lstr> text print_args print_arg + +%% + +top: + optional_separator + | element_list + { + if (olist.head) + print_picture(olist.head); + } + ; + + +element_list: + optional_separator middle_element_list optional_separator + { $$ = $2; } + ; + +middle_element_list: + element + { $$ = $1; } + | middle_element_list separator element + { $$ = $1; } + ; + +optional_separator: + /* empty */ + | separator + ; + +separator: + ';' + | separator ';' + ; + +placeless_element: + FIGNAME '=' macro_name + { + delete[] graphname; + graphname = new char[strlen($3) + 1]; + strcpy(graphname, $3); + delete[] $3; + } + | + VARIABLE '=' any_expr + { + define_variable($1, $3); + free($1); + } + | VARIABLE ':' '=' any_expr + { + place *p = lookup_label($1); + if (!p) { + lex_error("variable '%1' not defined", $1); + YYABORT; + } + p->obj = 0; + p->x = $4; + p->y = 0.0; + free($1); + } + | UP + { current_direction = UP_DIRECTION; } + | DOWN + { current_direction = DOWN_DIRECTION; } + | LEFT + { current_direction = LEFT_DIRECTION; } + | RIGHT + { current_direction = RIGHT_DIRECTION; } + | COMMAND_LINE + { + olist.append(make_command_object($1.str, $1.filename, + $1.lineno)); + } + | COMMAND print_args + { + olist.append(make_command_object($2.str, $2.filename, + $2.lineno)); + } + | PRINT print_args + { + fprintf(stderr, "%s\n", $2.str); + delete[] $2.str; + fflush(stderr); + } + | SH + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + if (safer_flag) + lex_error("unsafe to run command '%1'; ignoring", + $3); + else { + int retval = system($3); + if (retval < 0) + lex_error("error running command '%1': system()" + " returned %2", $3, retval); + } + delete[] $3; + } + | COPY TEXT + { + if (yychar < 0) + do_lookahead(); + do_copy($2.str); + // do not delete the filename + } + | COPY TEXT THRU + { delim_flag = 2; } + DELIMITED + { delim_flag = 0; } + until + { + if (yychar < 0) + do_lookahead(); + copy_file_thru($2.str, $5, $7); + // do not delete the filename + delete[] $5; + delete[] $7; + } + | COPY THRU + { delim_flag = 2; } + DELIMITED + { delim_flag = 0; } + until + { + if (yychar < 0) + do_lookahead(); + copy_rest_thru($4, $6); + delete[] $4; + delete[] $6; + } + | FOR VARIABLE '=' expr TO expr optional_by DO + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + if (yychar < 0) + do_lookahead(); + do_for($2, $4, $6, $7.is_multiplicative, $7.val, $10); + } + | simple_if + { + if (yychar < 0) + do_lookahead(); + if ($1.x != 0.0) + push_body($1.body); + delete[] $1.body; + } + | simple_if ELSE + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + if (yychar < 0) + do_lookahead(); + if ($1.x != 0.0) + push_body($1.body); + else + push_body($4); + free($1.body); + free($4); + } + | reset_variables + | RESET + { define_variable("scale", 1.0); } + ; + +macro_name: + VARIABLE + | LABEL + ; + +reset_variables: + RESET VARIABLE + { + reset($2); + delete[] $2; + } + | reset_variables VARIABLE + { + reset($2); + delete[] $2; + } + | reset_variables ',' VARIABLE + { + reset($3); + delete[] $3; + } + ; + +print_args: + print_arg + { $$ = $1; } + | print_args print_arg + { + $$.str = new char[strlen($1.str) + strlen($2.str) + 1]; + strcpy($$.str, $1.str); + strcat($$.str, $2.str); + delete[] $1.str; + delete[] $2.str; + if ($1.filename) { + $$.filename = $1.filename; + $$.lineno = $1.lineno; + } + else if ($2.filename) { + $$.filename = $2.filename; + $$.lineno = $2.lineno; + } + } + ; + +print_arg: + expr %prec ',' + { + $$.str = new char[GDIGITS + 1]; + sprintf($$.str, "%g", $1); + $$.filename = 0; + $$.lineno = 0; + } + | text + { $$ = $1; } + | position %prec ',' + { + $$.str = new char[GDIGITS + 2 + GDIGITS + 1]; + sprintf($$.str, "%g, %g", $1.x, $1.y); + $$.filename = 0; + $$.lineno = 0; + } + ; + +simple_if: + IF any_expr THEN + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + $$.x = $2; + $$.body = $5; + } + ; + +until: + /* empty */ + { $$ = 0; } + | UNTIL TEXT + { $$ = $2.str; } + ; + +any_expr: + expr + { $$ = $1; } + | text_expr + { $$ = $1; } + ; + +text_expr: + text EQUALEQUAL text + { + $$ = strcmp($1.str, $3.str) == 0; + delete[] $1.str; + delete[] $3.str; + } + | text NOTEQUAL text + { + $$ = strcmp($1.str, $3.str) != 0; + delete[] $1.str; + delete[] $3.str; + } + | text_expr ANDAND text_expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | text_expr ANDAND expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | expr ANDAND text_expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | text_expr OROR text_expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | text_expr OROR expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | expr OROR text_expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | '!' text_expr + { $$ = ($2 == 0.0); } + ; + + +optional_by: + /* empty */ + { + $$.val = 1.0; + $$.is_multiplicative = 0; + } + | BY expr + { + $$.val = $2; + $$.is_multiplicative = 0; + } + | BY '*' expr + { + $$.val = $3; + $$.is_multiplicative = 1; + } + ; + +element: + object_spec + { + $$.obj = $1->make_object(¤t_position, + ¤t_direction); + if ($$.obj == 0) + YYABORT; + delete $1; + if ($$.obj) + olist.append($$.obj); + else { + $$.x = current_position.x; + $$.y = current_position.y; + } + } + | LABEL ':' optional_separator element + { + $$ = $4; + define_label($1, & $$); + free($1); + } + | LABEL ':' optional_separator position_not_place + { + $$.obj = 0; + $$.x = $4.x; + $$.y = $4.y; + define_label($1, & $$); + free($1); + } + | LABEL ':' optional_separator place + { + $$ = $4; + define_label($1, & $$); + free($1); + } + | '{' + { + $<state>$.x = current_position.x; + $<state>$.y = current_position.y; + $<state>$.dir = current_direction; + } + element_list '}' + { + current_position.x = $<state>2.x; + current_position.y = $<state>2.y; + current_direction = $<state>2.dir; + } + optional_element + { + $$ = $3; + } + | placeless_element + { + $$.obj = 0; + $$.x = current_position.x; + $$.y = current_position.y; + } + ; + +optional_element: + /* empty */ + {} + | element + {} + ; + +object_spec: + BOX + { $$ = new object_spec(BOX_OBJECT); } + | CIRCLE + { $$ = new object_spec(CIRCLE_OBJECT); } + | ELLIPSE + { $$ = new object_spec(ELLIPSE_OBJECT); } + | ARC + { + $$ = new object_spec(ARC_OBJECT); + $$->dir = current_direction; + } + | LINE + { + $$ = new object_spec(LINE_OBJECT); + lookup_variable("lineht", & $$->segment_height); + lookup_variable("linewid", & $$->segment_width); + $$->dir = current_direction; + } + | ARROW + { + $$ = new object_spec(ARROW_OBJECT); + lookup_variable("lineht", & $$->segment_height); + lookup_variable("linewid", & $$->segment_width); + $$->dir = current_direction; + } + | MOVE + { + $$ = new object_spec(MOVE_OBJECT); + lookup_variable("moveht", & $$->segment_height); + lookup_variable("movewid", & $$->segment_width); + $$->dir = current_direction; + } + | SPLINE + { + $$ = new object_spec(SPLINE_OBJECT); + lookup_variable("lineht", & $$->segment_height); + lookup_variable("linewid", & $$->segment_width); + $$->dir = current_direction; + } + | text %prec TEXT + { + $$ = new object_spec(TEXT_OBJECT); + $$->text = new text_item($1.str, $1.filename, $1.lineno); + } + | PLOT expr + { + lex_warning("'plot' is deprecated; use 'sprintf'" + " instead"); + $$ = new object_spec(TEXT_OBJECT); + $$->text = new text_item(format_number(0, $2), 0, -1); + } + | PLOT expr text + { + $$ = new object_spec(TEXT_OBJECT); + $$->text = new text_item(format_number($3.str, $2), + $3.filename, $3.lineno); + delete[] $3.str; + } + | '[' + { + saved_state *p = new saved_state; + $<pstate>$ = p; + p->x = current_position.x; + p->y = current_position.y; + p->dir = current_direction; + p->tbl = current_table; + p->prev = current_saved_state; + current_position.x = 0.0; + current_position.y = 0.0; + current_table = new PTABLE(place); + current_saved_state = p; + olist.append(make_mark_object()); + } + element_list ']' + { + current_position.x = $<pstate>2->x; + current_position.y = $<pstate>2->y; + current_direction = $<pstate>2->dir; + $$ = new object_spec(BLOCK_OBJECT); + olist.wrap_up_block(& $$->oblist); + $$->tbl = current_table; + current_table = $<pstate>2->tbl; + current_saved_state = $<pstate>2->prev; + delete $<pstate>2; + } + | object_spec HEIGHT expr + { + $$ = $1; + $$->height = $3; + $$->flags |= HAS_HEIGHT; + } + | object_spec RADIUS expr + { + $$ = $1; + $$->radius = $3; + $$->flags |= HAS_RADIUS; + } + | object_spec WIDTH expr + { + $$ = $1; + $$->width = $3; + $$->flags |= HAS_WIDTH; + } + | object_spec DIAMETER expr + { + $$ = $1; + $$->radius = $3/2.0; + $$->flags |= HAS_RADIUS; + } + | object_spec expr %prec HEIGHT + { + $$ = $1; + $$->flags |= HAS_SEGMENT; + switch ($$->dir) { + case UP_DIRECTION: + $$->segment_pos.y += $2; + break; + case DOWN_DIRECTION: + $$->segment_pos.y -= $2; + break; + case RIGHT_DIRECTION: + $$->segment_pos.x += $2; + break; + case LEFT_DIRECTION: + $$->segment_pos.x -= $2; + break; + } + } + | object_spec UP + { + $$ = $1; + $$->dir = UP_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y += $$->segment_height; + } + | object_spec UP expr + { + $$ = $1; + $$->dir = UP_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y += $3; + } + | object_spec DOWN + { + $$ = $1; + $$->dir = DOWN_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y -= $$->segment_height; + } + | object_spec DOWN expr + { + $$ = $1; + $$->dir = DOWN_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y -= $3; + } + | object_spec RIGHT + { + $$ = $1; + $$->dir = RIGHT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x += $$->segment_width; + } + | object_spec RIGHT expr + { + $$ = $1; + $$->dir = RIGHT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x += $3; + } + | object_spec LEFT + { + $$ = $1; + $$->dir = LEFT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x -= $$->segment_width; + } + | object_spec LEFT expr + { + $$ = $1; + $$->dir = LEFT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x -= $3; + } + | object_spec FROM position + { + $$ = $1; + $$->flags |= HAS_FROM; + $$->from.x = $3.x; + $$->from.y = $3.y; + } + | object_spec TO position + { + $$ = $1; + if ($$->flags & HAS_SEGMENT) + $$->segment_list = new segment($$->segment_pos, + $$->segment_is_absolute, + $$->segment_list); + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x = $3.x; + $$->segment_pos.y = $3.y; + $$->segment_is_absolute = 1; + $$->flags |= HAS_TO; + $$->to.x = $3.x; + $$->to.y = $3.y; + } + | object_spec AT position + { + $$ = $1; + $$->flags |= HAS_AT; + $$->at.x = $3.x; + $$->at.y = $3.y; + if ($$->type != ARC_OBJECT) { + $$->flags |= HAS_FROM; + $$->from.x = $3.x; + $$->from.y = $3.y; + } + } + | object_spec WITH path + { + $$ = $1; + $$->flags |= HAS_WITH; + $$->with = $3; + } + | object_spec WITH position %prec ',' + { + $$ = $1; + $$->flags |= HAS_WITH; + position pos; + pos.x = $3.x; + pos.y = $3.y; + $$->with = new path(pos); + } + | object_spec BY expr_pair + { + $$ = $1; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x += $3.x; + $$->segment_pos.y += $3.y; + } + | object_spec THEN + { + $$ = $1; + if (!($$->flags & HAS_SEGMENT)) + switch ($$->dir) { + case UP_DIRECTION: + $$->segment_pos.y += $$->segment_width; + break; + case DOWN_DIRECTION: + $$->segment_pos.y -= $$->segment_width; + break; + case RIGHT_DIRECTION: + $$->segment_pos.x += $$->segment_width; + break; + case LEFT_DIRECTION: + $$->segment_pos.x -= $$->segment_width; + break; + } + $$->segment_list = new segment($$->segment_pos, + $$->segment_is_absolute, + $$->segment_list); + $$->flags &= ~HAS_SEGMENT; + $$->segment_pos.x = $$->segment_pos.y = 0.0; + $$->segment_is_absolute = 0; + } + | object_spec SOLID + { + $$ = $1; // nothing + } + | object_spec DOTTED + { + $$ = $1; + $$->flags |= IS_DOTTED; + lookup_variable("dashwid", & $$->dash_width); + } + | object_spec DOTTED expr + { + $$ = $1; + $$->flags |= IS_DOTTED; + $$->dash_width = $3; + } + | object_spec DASHED + { + $$ = $1; + $$->flags |= IS_DASHED; + lookup_variable("dashwid", & $$->dash_width); + } + | object_spec DASHED expr + { + $$ = $1; + $$->flags |= IS_DASHED; + $$->dash_width = $3; + } + | object_spec FILL + { + $$ = $1; + $$->flags |= IS_DEFAULT_FILLED; + } + | object_spec FILL expr + { + $$ = $1; + $$->flags |= IS_FILLED; + $$->fill = $3; + } + | object_spec XSLANTED expr + { + $$ = $1; + $$->flags |= IS_XSLANTED; + $$->xslanted = $3; + } + | object_spec YSLANTED expr + { + $$ = $1; + $$->flags |= IS_YSLANTED; + $$->yslanted = $3; + } + | object_spec SHADED text + { + $$ = $1; + $$->flags |= (IS_SHADED | IS_FILLED); + $$->shaded = new char[strlen($3.str)+1]; + strcpy($$->shaded, $3.str); + } + | object_spec COLORED text + { + $$ = $1; + $$->flags |= (IS_SHADED | IS_OUTLINED | IS_FILLED); + $$->shaded = new char[strlen($3.str)+1]; + strcpy($$->shaded, $3.str); + $$->outlined = new char[strlen($3.str)+1]; + strcpy($$->outlined, $3.str); + } + | object_spec OUTLINED text + { + $$ = $1; + $$->flags |= IS_OUTLINED; + $$->outlined = new char[strlen($3.str)+1]; + strcpy($$->outlined, $3.str); + } + | object_spec CHOP + { + $$ = $1; + // line chop chop means line chop 0 chop 0 + if ($$->flags & IS_DEFAULT_CHOPPED) { + $$->flags |= IS_CHOPPED; + $$->flags &= ~IS_DEFAULT_CHOPPED; + $$->start_chop = $$->end_chop = 0.0; + } + else if ($$->flags & IS_CHOPPED) { + $$->end_chop = 0.0; + } + else { + $$->flags |= IS_DEFAULT_CHOPPED; + } + } + | object_spec CHOP expr + { + $$ = $1; + if ($$->flags & IS_DEFAULT_CHOPPED) { + $$->flags |= IS_CHOPPED; + $$->flags &= ~IS_DEFAULT_CHOPPED; + $$->start_chop = 0.0; + $$->end_chop = $3; + } + else if ($$->flags & IS_CHOPPED) { + $$->end_chop = $3; + } + else { + $$->start_chop = $$->end_chop = $3; + $$->flags |= IS_CHOPPED; + } + } + | object_spec SAME + { + $$ = $1; + $$->flags |= IS_SAME; + } + | object_spec INVISIBLE + { + $$ = $1; + $$->flags |= IS_INVISIBLE; + } + | object_spec LEFT_ARROW_HEAD + { + $$ = $1; + $$->flags |= HAS_LEFT_ARROW_HEAD; + } + | object_spec RIGHT_ARROW_HEAD + { + $$ = $1; + $$->flags |= HAS_RIGHT_ARROW_HEAD; + } + | object_spec DOUBLE_ARROW_HEAD + { + $$ = $1; + $$->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD); + } + | object_spec CW + { + $$ = $1; + $$->flags |= IS_CLOCKWISE; + } + | object_spec CCW + { + $$ = $1; + $$->flags &= ~IS_CLOCKWISE; + } + | object_spec text %prec TEXT + { + $$ = $1; + text_item **p; + for (p = & $$->text; *p; p = &(*p)->next) + ; + *p = new text_item($2.str, $2.filename, $2.lineno); + } + | object_spec LJUST + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.h = LEFT_ADJUST; + } + } + | object_spec RJUST + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.h = RIGHT_ADJUST; + } + } + | object_spec ABOVE + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.v = ABOVE_ADJUST; + } + } + | object_spec BELOW + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.v = BELOW_ADJUST; + } + } + | object_spec THICKNESS expr + { + $$ = $1; + $$->flags |= HAS_THICKNESS; + $$->thickness = $3; + } + | object_spec ALIGNED + { + $$ = $1; + $$->flags |= IS_ALIGNED; + } + ; + +text: + TEXT + { $$ = $1; } + | SPRINTF '(' TEXT sprintf_args ')' + { + $$.filename = $3.filename; + $$.lineno = $3.lineno; + $$.str = do_sprintf($3.str, $4.v, $4.nv); + delete[] $4.v; + free($3.str); + } + ; + +sprintf_args: + /* empty */ + { + $$.v = 0; + $$.nv = 0; + $$.maxv = 0; + } + | sprintf_args ',' expr + { + $$ = $1; + if ($$.nv >= $$.maxv) { + if ($$.nv == 0) { + $$.v = new double[4]; + $$.maxv = 4; + } + else { + double *oldv = $$.v; + $$.maxv *= 2; +#if 0 + $$.v = new double[$$.maxv]; + memcpy($$.v, oldv, $$.nv*sizeof(double)); +#else + // workaround for bug in Compaq C++ V6.5-033 + // for Compaq Tru64 UNIX V5.1A (Rev. 1885) + double *foo = new double[$$.maxv]; + memcpy(foo, oldv, $$.nv*sizeof(double)); + $$.v = foo; +#endif + delete[] oldv; + } + } + $$.v[$$.nv] = $3; + $$.nv += 1; + } + ; + +position: + position_not_place + { $$ = $1; } + | place + { + position pos = $1; + $$.x = pos.x; + $$.y = pos.y; + } + | '(' place ')' + { + position pos = $2; + $$.x = pos.x; + $$.y = pos.y; + } + ; + +position_not_place: + expr_pair + { $$ = $1; } + | position '+' expr_pair + { + $$.x = $1.x + $3.x; + $$.y = $1.y + $3.y; + } + | '(' position '+' expr_pair ')' + { + $$.x = $2.x + $4.x; + $$.y = $2.y + $4.y; + } + | position '-' expr_pair + { + $$.x = $1.x - $3.x; + $$.y = $1.y - $3.y; + } + | '(' position '-' expr_pair ')' + { + $$.x = $2.x - $4.x; + $$.y = $2.y - $4.y; + } + | '(' position ',' position ')' + { + $$.x = $2.x; + $$.y = $4.y; + } + | expr between position AND position + { + $$.x = (1.0 - $1)*$3.x + $1*$5.x; + $$.y = (1.0 - $1)*$3.y + $1*$5.y; + } + | '(' expr between position AND position ')' + { + $$.x = (1.0 - $2)*$4.x + $2*$6.x; + $$.y = (1.0 - $2)*$4.y + $2*$6.y; + } + /* the next two rules cause harmless shift/reduce warnings */ + | expr_not_lower_than '<' position ',' position '>' + { + $$.x = (1.0 - $1)*$3.x + $1*$5.x; + $$.y = (1.0 - $1)*$3.y + $1*$5.y; + } + | '(' expr_not_lower_than '<' position ',' position '>' ')' + { + $$.x = (1.0 - $2)*$4.x + $2*$6.x; + $$.y = (1.0 - $2)*$4.y + $2*$6.y; + } + ; + +between: + BETWEEN + | OF THE WAY BETWEEN + ; + +expr_pair: + expr ',' expr + { + $$.x = $1; + $$.y = $3; + } + | '(' expr_pair ')' + { $$ = $2; } + ; + +place: + /* line at A left == line (at A) left */ + label %prec CHOP + { $$ = $1; } + | label corner + { + path pth($2); + if (!pth.follow($1, & $$)) + YYABORT; + } + | corner label + { + path pth($1); + if (!pth.follow($2, & $$)) + YYABORT; + } + | corner OF label + { + path pth($1); + if (!pth.follow($3, & $$)) + YYABORT; + } + | HERE + { + $$.x = current_position.x; + $$.y = current_position.y; + $$.obj = 0; + } + ; + +label: + LABEL + { + place *p = lookup_label($1); + if (!p) { + lex_error("there is no place '%1'", $1); + YYABORT; + } + $$ = *p; + free($1); + } + | nth_primitive + { $$.obj = $1; } + | label '.' LABEL + { + path pth($3); + if (!pth.follow($1, & $$)) + YYABORT; + } + ; + +ordinal: + ORDINAL + { $$ = $1; } + | '`' any_expr TH + { + // XXX Check for overflow (and non-integers?). + $$ = (int)$2; + } + ; + +optional_ordinal_last: + LAST + { $$ = 1; } + | ordinal LAST + { $$ = $1; } + ; + +nth_primitive: + ordinal object_type + { + int count = 0; + object *p; + for (p = olist.head; p != 0; p = p->next) + if (p->type() == $2 && ++count == $1) { + $$ = p; + break; + } + if (p == 0) { + lex_error("there is no %1%2 %3", $1, ordinal_postfix($1), + object_type_name($2)); + YYABORT; + } + } + | optional_ordinal_last object_type + { + int count = 0; + object *p; + for (p = olist.tail; p != 0; p = p->prev) + if (p->type() == $2 && ++count == $1) { + $$ = p; + break; + } + if (p == 0) { + lex_error("there is no %1%2 last %3", $1, + ordinal_postfix($1), object_type_name($2)); + YYABORT; + } + } + ; + +object_type: + BOX + { $$ = BOX_OBJECT; } + | CIRCLE + { $$ = CIRCLE_OBJECT; } + | ELLIPSE + { $$ = ELLIPSE_OBJECT; } + | ARC + { $$ = ARC_OBJECT; } + | LINE + { $$ = LINE_OBJECT; } + | ARROW + { $$ = ARROW_OBJECT; } + | SPLINE + { $$ = SPLINE_OBJECT; } + | '[' ']' + { $$ = BLOCK_OBJECT; } + | TEXT + { $$ = TEXT_OBJECT; } + ; + +label_path: + '.' LABEL + { $$ = new path($2); } + | label_path '.' LABEL + { + $$ = $1; + $$->append($3); + } + ; + +relative_path: + corner %prec CHOP + { $$ = new path($1); } + /* give this a lower precedence than LEFT and RIGHT so that + [A: box] with .A left == [A: box] with (.A left) */ + | label_path %prec TEXT + { $$ = $1; } + | label_path corner + { + $$ = $1; + $$->append($2); + } + ; + +path: + relative_path + { $$ = $1; } + | '(' relative_path ',' relative_path ')' + { + $$ = $2; + $$->set_ypath($4); + } + /* The rest of these rules are a compatibility sop. */ + | ORDINAL LAST object_type relative_path + { + lex_warning("'%1%2 last %3' in 'with' argument ignored", + $1, ordinal_postfix($1), object_type_name($3)); + $$ = $4; + } + | LAST object_type relative_path + { + lex_warning("'last %1' in 'with' argument ignored", + object_type_name($2)); + $$ = $3; + } + | ORDINAL object_type relative_path + { + lex_warning("'%1%2 %3' in 'with' argument ignored", + $1, ordinal_postfix($1), object_type_name($2)); + $$ = $3; + } + | LABEL relative_path + { + lex_warning("initial '%1' in 'with' argument ignored", $1); + delete[] $1; + $$ = $2; + } + ; + +corner: + DOT_N + { $$ = &object::north; } + | DOT_E + { $$ = &object::east; } + | DOT_W + { $$ = &object::west; } + | DOT_S + { $$ = &object::south; } + | DOT_NE + { $$ = &object::north_east; } + | DOT_SE + { $$ = &object:: south_east; } + | DOT_NW + { $$ = &object::north_west; } + | DOT_SW + { $$ = &object::south_west; } + | DOT_C + { $$ = &object::center; } + | DOT_START + { $$ = &object::start; } + | DOT_END + { $$ = &object::end; } + | TOP + { $$ = &object::north; } + | BOTTOM + { $$ = &object::south; } + | LEFT + { $$ = &object::west; } + | RIGHT + { $$ = &object::east; } + | UPPER LEFT + { $$ = &object::north_west; } + | LOWER LEFT + { $$ = &object::south_west; } + | UPPER RIGHT + { $$ = &object::north_east; } + | LOWER RIGHT + { $$ = &object::south_east; } + | LEFT_CORNER + { $$ = &object::west; } + | RIGHT_CORNER + { $$ = &object::east; } + | UPPER LEFT_CORNER + { $$ = &object::north_west; } + | LOWER LEFT_CORNER + { $$ = &object::south_west; } + | UPPER RIGHT_CORNER + { $$ = &object::north_east; } + | LOWER RIGHT_CORNER + { $$ = &object::south_east; } + | NORTH + { $$ = &object::north; } + | SOUTH + { $$ = &object::south; } + | EAST + { $$ = &object::east; } + | WEST + { $$ = &object::west; } + | CENTER + { $$ = &object::center; } + | START + { $$ = &object::start; } + | END + { $$ = &object::end; } + ; + +expr: + expr_lower_than + { $$ = $1; } + | expr_not_lower_than + { $$ = $1; } + ; + +expr_lower_than: + expr '<' expr + { $$ = ($1 < $3); } + ; + +expr_not_lower_than: + VARIABLE + { + if (!lookup_variable($1, & $$)) { + lex_error("there is no variable '%1'", $1); + YYABORT; + } + free($1); + } + | NUMBER + { $$ = $1; } + | place DOT_X + { + if ($1.obj != 0) + $$ = $1.obj->origin().x; + else + $$ = $1.x; + } + | place DOT_Y + { + if ($1.obj != 0) + $$ = $1.obj->origin().y; + else + $$ = $1.y; + } + | place DOT_HT + { + if ($1.obj != 0) + $$ = $1.obj->height(); + else + $$ = 0.0; + } + | place DOT_WID + { + if ($1.obj != 0) + $$ = $1.obj->width(); + else + $$ = 0.0; + } + | place DOT_RAD + { + if ($1.obj != 0) + $$ = $1.obj->radius(); + else + $$ = 0.0; + } + | expr '+' expr + { $$ = $1 + $3; } + | expr '-' expr + { $$ = $1 - $3; } + | expr '*' expr + { $$ = $1 * $3; } + | expr '/' expr + { + if ($3 == 0.0) { + lex_error("division by zero"); + YYABORT; + } + $$ = $1/$3; + } + | expr '%' expr + { + if ($3 == 0.0) { + lex_error("modulus by zero"); + YYABORT; + } + $$ = fmod($1, $3); + } + | expr '^' expr + { + errno = 0; + $$ = pow($1, $3); + if (errno == EDOM) { + lex_error("arguments to '^' operator out of domain"); + YYABORT; + } + if (errno == ERANGE) { + lex_error("result of '^' operator out of range"); + YYABORT; + } + } + | '-' expr %prec '!' + { $$ = -$2; } + | '(' any_expr ')' + { $$ = $2; } + | SIN '(' any_expr ')' + { + errno = 0; + $$ = sin($3); + if (errno == ERANGE) { + lex_error("sin result out of range"); + YYABORT; + } + } + | COS '(' any_expr ')' + { + errno = 0; + $$ = cos($3); + if (errno == ERANGE) { + lex_error("cos result out of range"); + YYABORT; + } + } + | ATAN2 '(' any_expr ',' any_expr ')' + { + errno = 0; + $$ = atan2($3, $5); + if (errno == EDOM) { + lex_error("atan2 argument out of domain"); + YYABORT; + } + if (errno == ERANGE) { + lex_error("atan2 result out of range"); + YYABORT; + } + } + | LOG '(' any_expr ')' + { + errno = 0; + $$ = log10($3); + if (errno == ERANGE) { + lex_error("log result out of range"); + YYABORT; + } + } + | EXP '(' any_expr ')' + { + errno = 0; + $$ = pow(10.0, $3); + if (errno == ERANGE) { + lex_error("exp result out of range"); + YYABORT; + } + } + | SQRT '(' any_expr ')' + { + errno = 0; + $$ = sqrt($3); + if (errno == EDOM) { + lex_error("sqrt argument out of domain"); + YYABORT; + } + } + | K_MAX '(' any_expr ',' any_expr ')' + { $$ = $3 > $5 ? $3 : $5; } + | K_MIN '(' any_expr ',' any_expr ')' + { $$ = $3 < $5 ? $3 : $5; } + | INT '(' any_expr ')' + { $$ = $3 < 0 ? -floor(-$3) : floor($3); } + | RAND '(' any_expr ')' + { + lex_error("use of 'rand' with an argument is" + " deprecated; shift and scale 'rand()' with" + " arithmetic instead"); + $$ = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*$3); + } + | RAND '(' ')' + { + /* return a random number in the range [0,1) */ + /* portable, but not very random */ + $$ = (rand() & 0x7fff) / double(0x8000); + } + | SRAND '(' any_expr ')' + { + $$ = 0; + srand((unsigned int)$3); + } + | expr LESSEQUAL expr + { $$ = ($1 <= $3); } + | expr '>' expr + { $$ = ($1 > $3); } + | expr GREATEREQUAL expr + { $$ = ($1 >= $3); } + | expr EQUALEQUAL expr + { $$ = ($1 == $3); } + | expr NOTEQUAL expr + { $$ = ($1 != $3); } + | expr ANDAND expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | expr OROR expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | '!' expr + { $$ = ($2 == 0.0); } + + ; + +%% + +/* bison defines const to be empty unless __STDC__ is defined, which it +isn't under cfront */ + +#ifdef const +#undef const +#endif + +static struct { + const char *name; + double val; + int scaled; // non-zero if val should be multiplied by scale +} defaults_table[] = { + { "arcrad", .25, 1 }, + { "arrowht", .1, 1 }, + { "arrowwid", .05, 1 }, + { "circlerad", .25, 1 }, + { "boxht", .5, 1 }, + { "boxwid", .75, 1 }, + { "boxrad", 0.0, 1 }, + { "dashwid", .05, 1 }, + { "ellipseht", .5, 1 }, + { "ellipsewid", .75, 1 }, + { "moveht", .5, 1 }, + { "movewid", .5, 1 }, + { "lineht", .5, 1 }, + { "linewid", .5, 1 }, + { "textht", 0.0, 1 }, + { "textwid", 0.0, 1 }, + { "scale", 1.0, 0 }, + { "linethick", -1.0, 0 }, // in points + { "fillval", .5, 0 }, + { "arrowhead", 1.0, 0 }, + { "maxpswid", 8.5, 0 }, + { "maxpsht", 11.0, 0 }, +}; + +place *lookup_label(const char *label) +{ + saved_state *state = current_saved_state; + PTABLE(place) *tbl = current_table; + for (;;) { + place *pl = tbl->lookup(label); + if (pl) + return pl; + if (!state) + return 0; + tbl = state->tbl; + state = state->prev; + } +} + +void define_label(const char *label, const place *pl) +{ + place *p = new place[1]; + *p = *pl; + current_table->define(label, p); +} + +int lookup_variable(const char *name, double *val) +{ + place *pl = lookup_label(name); + if (pl) { + *val = pl->x; + return 1; + } + return 0; +} + +void define_variable(const char *name, double val) +{ + place *p = new place[1]; + p->obj = 0; + p->x = val; + p->y = 0.0; + current_table->define(name, p); + if (strcmp(name, "scale") == 0) { + // When the scale changes, reset all scaled predefined variables to + // their default values. + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (defaults_table[i].scaled) + define_variable(defaults_table[i].name, val*defaults_table[i].val); + } +} + +// called once only (not once per parse) + +void parse_init() +{ + current_direction = RIGHT_DIRECTION; + current_position.x = 0.0; + current_position.y = 0.0; + // This resets everything to its default value. + reset_all(); +} + +void reset(const char *nm) +{ + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (strcmp(nm, defaults_table[i].name) == 0) { + double val = defaults_table[i].val; + if (defaults_table[i].scaled) { + double scale; + lookup_variable("scale", &scale); + val *= scale; + } + define_variable(defaults_table[i].name, val); + return; + } + lex_error("'%1' is not a predefined variable", nm); +} + +void reset_all() +{ + // We only have to explicitly reset the predefined variables that + // aren't scaled because 'scale' is not scaled, and changing the + // value of 'scale' will reset all the predefined variables that + // are scaled. + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (!defaults_table[i].scaled) + define_variable(defaults_table[i].name, defaults_table[i].val); +} + +// called after each parse + +void parse_cleanup() +{ + while (current_saved_state != 0) { + delete current_table; + current_table = current_saved_state->tbl; + saved_state *tem = current_saved_state; + current_saved_state = current_saved_state->prev; + delete tem; + } + assert(current_table == &top_table); + PTABLE_ITERATOR(place) iter(current_table); + const char *key; + place *pl; + while (iter.next(&key, &pl)) + if (pl->obj != 0) { + position pos = pl->obj->origin(); + pl->obj = 0; + pl->x = pos.x; + pl->y = pos.y; + } + while (olist.head != 0) { + object *tem = olist.head; + olist.head = olist.head->next; + delete tem; + } + olist.tail = 0; + current_direction = RIGHT_DIRECTION; + current_position.x = 0.0; + current_position.y = 0.0; +} + +const char *ordinal_postfix(int n) +{ + if (n < 10 || n > 20) + switch (n % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + } + return "th"; +} + +const char *object_type_name(object_type type) +{ + switch (type) { + case BOX_OBJECT: + return "box"; + case CIRCLE_OBJECT: + return "circle"; + case ELLIPSE_OBJECT: + return "ellipse"; + case ARC_OBJECT: + return "arc"; + case SPLINE_OBJECT: + return "spline"; + case LINE_OBJECT: + return "line"; + case ARROW_OBJECT: + return "arrow"; + case MOVE_OBJECT: + return "move"; + case TEXT_OBJECT: + return "\"\""; + case BLOCK_OBJECT: + return "[]"; + case OTHER_OBJECT: + case MARK_OBJECT: + default: + break; + } + return "object"; +} + +static char sprintf_buf[1024]; + +char *format_number(const char *fmt, double n) +{ + if (0 /* nullptr */ == fmt) + fmt = "%g"; + return do_sprintf(fmt, &n, 1); +} + +char *do_sprintf(const char *fmt, const double *v, int nv) +{ + // Define valid conversion specifiers and modifiers. + static const char spcs[] = "eEfgG%"; + static const char mods[] = "#-+ 0123456789."; + string result; + int i = 0; + string one_format; + while (*fmt) { + if ('%' == *fmt) { + one_format += *fmt++; + for (; *fmt != '\0' && strchr(mods, *fmt) != 0; fmt++) + one_format += *fmt; + if ('\0' == *fmt || strchr(spcs, *fmt) == 0) { + lex_error("invalid sprintf conversion specifier '%1'", *fmt); + result += one_format; + result += fmt; + break; + } + if ('%' == *fmt) { + fmt++; + snprintf(sprintf_buf, sizeof(sprintf_buf), "%%"); + } + else { + if (i >= nv) { + lex_error("too few arguments to sprintf"); + result += one_format; + result += fmt; + break; + } + one_format += *fmt++; + one_format += '\0'; +// We validated the format string above. Most conversion specifiers are +// rejected, including `n`. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + snprintf(sprintf_buf, sizeof(sprintf_buf), + one_format.contents(), v[i++]); +#pragma GCC diagnostic pop + } + one_format.clear(); + result += sprintf_buf; + } + else + result += *fmt++; + } + result += '\0'; + return strsave(result.contents()); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/pic/position.h b/src/preproc/pic/position.h new file mode 100644 index 0000000..fb32737 --- /dev/null +++ b/src/preproc/pic/position.h @@ -0,0 +1,46 @@ +// -*- 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/>. */ + +struct place; +struct position { + double x; + double y; + position(double, double ); + position(); + position(const place &); + position &operator+=(const position &); + position &operator-=(const position &); + position &operator*=(double); + position &operator/=(double); +}; + +position operator-(const position &); +position operator+(const position &, const position &); +position operator-(const position &, const position &); +position operator/(const position &, double); +position operator*(const position &, double); +// dot product +double operator*(const position &, const position &); +int operator==(const position &, const position &); +int operator!=(const position &, const position &); + +double hypot(const position &a); + +typedef position distance; + diff --git a/src/preproc/pic/tex.cpp b/src/preproc/pic/tex.cpp new file mode 100644 index 0000000..c6071af --- /dev/null +++ b/src/preproc/pic/tex.cpp @@ -0,0 +1,458 @@ +// -*- 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 "pic.h" + +#ifdef TEX_SUPPORT + +#include "common.h" + +class tex_output : public common_output { +public: + tex_output(); + ~tex_output(); + void start_picture(double, const position &ll, const position &ur); + void finish_picture(); + void text(const position &, text_piece *, int, double); + void line(const position &, const position *, int n, + const line_type &); + void polygon(const position *, int n, + const line_type &, double); + void spline(const position &, const position *, int n, + const line_type &); + void arc(const position &, const position &, const position &, + const line_type &); + void circle(const position &, double rad, const line_type &, double); + void ellipse(const position &, const distance &, const line_type &, double); + void command(const char *, const char *, int); + void set_color(char *, char *); + void reset_color(); + char *get_last_filled(); + char *get_outline_color(); + int supports_filled_polygons(); +private: + position upper_left; + double height; + double width; + double scale; + double pen_size; + + void point(const position &); + void dot(const position &, const line_type &); + void solid_arc(const position ¢, double rad, double start_angle, + double end_angle, const line_type <); + position transform(const position &); +protected: + virtual void set_pen_size(double ps); +}; + +// convert inches to milliinches + +inline int milliinches(double x) +{ + return int(x*1000.0 + .5); +} + +inline position tex_output::transform(const position &pos) +{ + return position((pos.x - upper_left.x)/scale, + (upper_left.y - pos.y)/scale); +} + +output *make_tex_output() +{ + return new tex_output; +} + +tex_output::tex_output() +{ +} + +tex_output::~tex_output() +{ +} + +const int DEFAULT_PEN_SIZE = 8; + +void tex_output::set_pen_size(double ps) +{ + if (ps < 0.0) + ps = -1.0; + if (ps != pen_size) { + pen_size = ps; + printf(" \\special{pn %d}%%\n", + ps < 0.0 ? DEFAULT_PEN_SIZE : int(ps*(1000.0/72.0) + .5)); + } +} + +void tex_output::start_picture(double sc, const position &ll, + const position &ur) +{ + upper_left.x = ll.x; + upper_left.y = ur.y; + scale = compute_scale(sc, ll, ur); + height = (ur.y - ll.y)/scale; + width = (ur.x - ll.x)/scale; + /* The point of \vskip 0pt is to ensure that the vtop gets + a height of 0 rather than the height of the hbox; this + might be non-zero if text from text attributes lies outside pic's + idea of the bounding box of the picture. */ + /* \newbox and \newdimen are defined with \outer in plain.tex and can't + be used directly in an \if clause. */ + printf("\\expandafter\\ifx\\csname %s\\endcsname\\relax\n" + " \\csname newbox\\expandafter\\endcsname\\csname %s\\endcsname\n" + "\\fi\n" + "\\ifx\\graphtemp\\undefined\n" + " \\csname newdimen\\endcsname\\graphtemp\n" + "\\fi\n" + "\\expandafter\\setbox\\csname %s\\endcsname\n" + " =\\vtop{\\vskip 0pt\\hbox{%%\n", + graphname, graphname, graphname); + pen_size = -2.0; +} + +void tex_output::finish_picture() +{ + printf(" \\hbox{\\vrule depth%.3fin width0pt height 0pt}%%\n" + " \\kern %.3fin\n" + " }%%\n" + "}%%\n", + height, width); +} + +void tex_output::text(const position ¢er, text_piece *v, int n, double) +{ + position c = transform(center); + for (int i = 0; i < n; i++) + if (v[i].text != 0 && *v[i].text != '\0') { + int j = 2*i - n + 1; + if (v[i].adj.v == ABOVE_ADJUST) + j--; + else if (v[i].adj.v == BELOW_ADJUST) + j++; + if (j == 0) { + printf(" \\graphtemp=.5ex\n" + " \\advance\\graphtemp by %.3fin\n", c.y); + } + else { + printf(" \\graphtemp=\\baselineskip\n" + " \\multiply\\graphtemp by %d\n" + " \\divide\\graphtemp by 2\n" + " \\advance\\graphtemp by .5ex\n" + " \\advance\\graphtemp by %.3fin\n", + j, c.y); + } + printf(" \\rlap{\\kern %.3fin\\lower\\graphtemp", c.x); + fputs("\\hbox to 0pt{", stdout); + if (v[i].adj.h != LEFT_ADJUST) + fputs("\\hss ", stdout); + fputs(v[i].text, stdout); + if (v[i].adj.h != RIGHT_ADJUST) + fputs("\\hss", stdout); + fputs("}}%\n", stdout); + } +} + +void tex_output::point(const position &pos) +{ + position p = transform(pos); + printf(" \\special{pa %d %d}%%\n", milliinches(p.x), milliinches(p.y)); +} + +void tex_output::line(const position &start, const position *v, int n, + const line_type <) +{ + set_pen_size(lt.thickness); + point(start); + for (int i = 0; i < n; i++) + point(v[i]); + fputs(" \\special{", stdout); + switch(lt.type) { + case line_type::invisible: + fputs("ip", stdout); + break; + case line_type::solid: + fputs("fp", stdout); + break; + case line_type::dotted: + printf("dt %.3f", lt.dash_width/scale); + break; + case line_type::dashed: + printf("da %.3f", lt.dash_width/scale); + break; + } + fputs("}%\n", stdout); +} + +void tex_output::polygon(const position *v, int n, + const line_type <, double fill) +{ + if (fill >= 0.0) { + if (fill > 1.0) + fill = 1.0; + printf(" \\special{sh %.3f}%%\n", fill); + } + line(v[n-1], v, n, lt); +} + +void tex_output::spline(const position &start, const position *v, int n, + const line_type <) +{ + if (lt.type == line_type::invisible) + return; + set_pen_size(lt.thickness); + point(start); + for (int i = 0; i < n; i++) + point(v[i]); + fputs(" \\special{sp", stdout); + switch(lt.type) { + case line_type::solid: + break; + case line_type::dotted: + printf(" %.3f", -lt.dash_width/scale); + break; + case line_type::dashed: + printf(" %.3f", lt.dash_width/scale); + break; + case line_type::invisible: + assert(0); + } + fputs("}%\n", stdout); +} + +void tex_output::solid_arc(const position ¢, double rad, + double start_angle, double end_angle, + const line_type <) +{ + set_pen_size(lt.thickness); + position c = transform(cent); + printf(" \\special{ar %d %d %d %d %f %f}%%\n", + milliinches(c.x), + milliinches(c.y), + milliinches(rad/scale), + milliinches(rad/scale), + -end_angle, + (-end_angle > -start_angle) ? (double)M_PI * 2 - start_angle + : -start_angle); +} + +void tex_output::arc(const position &start, const position ¢, + const position &end, const line_type <) +{ + switch (lt.type) { + case line_type::invisible: + break; + case line_type::dashed: + dashed_arc(start, cent, end, lt); + break; + case line_type::dotted: + dotted_arc(start, cent, end, lt); + break; + case line_type::solid: + { + position c; + if (!compute_arc_center(start, cent, end, &c)) { + line(start, &end, 1, lt); + break; + } + solid_arc(c, + hypot(cent - start), + atan2(start.y - c.y, start.x - c.x), + atan2(end.y - c.y, end.x - c.x), + lt); + break; + } + } +} + +void tex_output::circle(const position ¢, double rad, + const line_type <, double fill) +{ + if (fill >= 0.0 && lt.type != line_type::solid) { + if (fill > 1.0) + fill = 1.0; + line_type ilt; + ilt.type = line_type::invisible; + ellipse(cent, position(rad*2.0, rad*2.0), ilt, fill); + } + switch (lt.type) { + case line_type::dashed: + dashed_circle(cent, rad, lt); + break; + case line_type::invisible: + break; + case line_type::solid: + ellipse(cent, position(rad*2.0,rad*2.0), lt, fill); + break; + case line_type::dotted: + dotted_circle(cent, rad, lt); + break; + default: + assert(0); + } +} + +void tex_output::ellipse(const position ¢, const distance &dim, + const line_type <, double fill) +{ + if (lt.type == line_type::invisible) { + if (fill < 0.0) + return; + } + else + set_pen_size(lt.thickness); + if (fill >= 0.0) { + if (fill > 1.0) + fill = 1.0; + printf(" \\special{sh %.3f}%%\n", fill); + } + position c = transform(cent); + switch (lt.type) { + case line_type::solid: + case line_type::invisible: + printf(" \\special{%s %d %d %d %d 0 6.28319}%%\n", + (lt.type == line_type::invisible ? "ia" : "ar"), + milliinches(c.x), + milliinches(c.y), + milliinches(dim.x/(2.0*scale)), + milliinches(dim.y/(2.0*scale))); + break; + case line_type::dashed: + dashed_ellipse(cent, dim / scale, lt); + break; + case line_type::dotted: + dotted_ellipse(cent, dim / scale, lt); + break; + default: + assert(0); + } +} + +void tex_output::command(const char *s, const char *, int) +{ + fputs(s, stdout); + putchar('%'); // avoid unwanted spaces + putchar('\n'); +} + +int tex_output::supports_filled_polygons() +{ + return 1; +} + +void tex_output::dot(const position &pos, const line_type <) +{ + if (zero_length_line_flag) { + line_type slt = lt; + slt.type = line_type::solid; + line(pos, &pos, 1, slt); + } + else { + int dot_rad = int(lt.thickness*(1000.0/(72.0*2)) + .5); + if (dot_rad == 0) + dot_rad = 1; + position p = transform(pos); + printf(" \\special{sh 1}%%\n" + " \\special{ia %d %d %d %d 0 6.28319}%%\n", + milliinches(p.x), milliinches(p.y), dot_rad, dot_rad); + } +} + +void tex_output::set_color(char *, char *) +{ + /* not implemented yet */ +} + +void tex_output::reset_color() +{ + /* not implemented yet */ +} + +char *tex_output::get_last_filled() +{ + /* not implemented yet */ + return NULL; +} + +char *tex_output::get_outline_color() +{ + /* not implemented yet */ + return NULL; +} + +class tpic_output : public tex_output { +public: + tpic_output(); + void command(const char *, const char *, int); +private: + void set_pen_size(double ps); + int default_pen_size; + int prev_default_pen_size; +}; + +tpic_output::tpic_output() +: default_pen_size(DEFAULT_PEN_SIZE), prev_default_pen_size(DEFAULT_PEN_SIZE) +{ +} + +void tpic_output::command(const char *s, const char *filename, int lineno) +{ + assert(s[0] == '.'); + if (s[1] == 'p' && s[2] == 's' && (s[3] == '\0' || !csalpha(s[3]))) { + const char *p = s + 3; + while (csspace(*p)) + p++; + if (*p == '\0') { + int temp = default_pen_size; + default_pen_size = prev_default_pen_size; + prev_default_pen_size = temp; + } + else { + char *ptr; + int temp = (int)strtol(p, &ptr, 10); + if (temp == 0 && ptr == p) + error_with_file_and_line(filename, lineno, + "argument to '.ps' not an integer"); + else if (temp < 0) + error_with_file_and_line(filename, lineno, + "negative pen size"); + else { + prev_default_pen_size = default_pen_size; + default_pen_size = temp; + } + } + } + else + printf("\\%s%%\n", s + 1); +} + +void tpic_output::set_pen_size(double ps) +{ + if (ps < 0.0) + printf(" \\special{pn %d}%%\n", default_pen_size); + else + tex_output::set_pen_size(ps); +} + +output *make_tpic_output() +{ + return new tpic_output; +} + +#endif diff --git a/src/preproc/pic/text.h b/src/preproc/pic/text.h new file mode 100644 index 0000000..9b9353f --- /dev/null +++ b/src/preproc/pic/text.h @@ -0,0 +1,46 @@ +// -*- 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/>. */ + + +enum hadjustment { + CENTER_ADJUST, + LEFT_ADJUST, + RIGHT_ADJUST + }; + +enum vadjustment { + NONE_ADJUST, + ABOVE_ADJUST, + BELOW_ADJUST + }; + +struct adjustment { + hadjustment h; + vadjustment v; +}; + +struct text_piece { + char *text; + adjustment adj; + const char *filename; + int lineno; + + text_piece(); + ~text_piece(); +}; diff --git a/src/preproc/pic/troff.cpp b/src/preproc/pic/troff.cpp new file mode 100644 index 0000000..3dc87a7 --- /dev/null +++ b/src/preproc/pic/troff.cpp @@ -0,0 +1,579 @@ +/* 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 "pic.h" +#include "common.h" + + +const double RELATIVE_THICKNESS = -1.0; +const double BAD_THICKNESS = -2.0; + +class simple_output : public common_output { + virtual void simple_line(const position &, const position &) = 0; + virtual void simple_spline(const position &, const position *, int n) = 0; + virtual void simple_arc(const position &, const position &, + const position &) = 0; + virtual void simple_circle(int, const position &, double rad) = 0; + virtual void simple_ellipse(int, const position &, const distance &) = 0; + virtual void simple_polygon(int, const position *, int) = 0; + virtual void line_thickness(double) = 0; + virtual void set_fill(double) = 0; + virtual void set_color(char *, char *) = 0; + virtual void reset_color() = 0; + virtual char *get_last_filled() = 0; + void dot(const position &, const line_type &) = 0; +public: + void start_picture(double sc, const position &ll, const position &ur) = 0; + void finish_picture() = 0; + void text(const position &, text_piece *, int, double) = 0; + void line(const position &, const position *, int n, + const line_type &); + void polygon(const position *, int n, + const line_type &, double); + void spline(const position &, const position *, int n, + const line_type &); + void arc(const position &, const position &, const position &, + const line_type &); + void circle(const position &, double rad, const line_type &, double); + void ellipse(const position &, const distance &, const line_type &, double); + int supports_filled_polygons(); +}; + +int simple_output::supports_filled_polygons() +{ + return driver_extension_flag != 0; +} + +void simple_output::arc(const position &start, const position ¢, + const position &end, const line_type <) +{ + switch (lt.type) { + case line_type::solid: + line_thickness(lt.thickness); + simple_arc(start, cent, end); + break; + case line_type::invisible: + break; + case line_type::dashed: + dashed_arc(start, cent, end, lt); + break; + case line_type::dotted: + dotted_arc(start, cent, end, lt); + break; + } +} + +void simple_output::line(const position &start, const position *v, int n, + const line_type <) +{ + position pos = start; + line_thickness(lt.thickness); + for (int i = 0; i < n; i++) { + switch (lt.type) { + case line_type::solid: + simple_line(pos, v[i]); + break; + case line_type::dotted: + { + distance vec(v[i] - pos); + double dist = hypot(vec); + int ndots = int(dist/lt.dash_width + .5); + if (ndots == 0) + dot(pos, lt); + else { + vec /= double(ndots); + for (int j = 0; j <= ndots; j++) + dot(pos + vec*j, lt); + } + } + break; + case line_type::dashed: + { + distance vec(v[i] - pos); + double dist = hypot(vec); + if (dist <= lt.dash_width*2.0) + simple_line(pos, v[i]); + else { + int ndashes = int((dist - lt.dash_width)/(lt.dash_width*2.0) + .5); + distance dash_vec = vec*(lt.dash_width/dist); + double dash_gap = (dist - lt.dash_width)/ndashes; + distance dash_gap_vec = vec*(dash_gap/dist); + for (int j = 0; j <= ndashes; j++) { + position s(pos + dash_gap_vec*j); + simple_line(s, s + dash_vec); + } + } + } + break; + case line_type::invisible: + break; + default: + assert(0); + } + pos = v[i]; + } +} + +void simple_output::spline(const position &start, const position *v, int n, + const line_type <) +{ + line_thickness(lt.thickness); + simple_spline(start, v, n); +} + +void simple_output::polygon(const position *v, int n, + const line_type <, double fill) +{ + if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) { + if (get_last_filled() == 0) + set_fill(fill); + simple_polygon(1, v, n); + } + if (lt.type == line_type::solid && driver_extension_flag) { + line_thickness(lt.thickness); + simple_polygon(0, v, n); + } + else if (lt.type != line_type::invisible) { + line_thickness(lt.thickness); + line(v[n - 1], v, n, lt); + } +} + +void simple_output::circle(const position ¢, double rad, + const line_type <, double fill) +{ + if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) { + if (get_last_filled() == 0) + set_fill(fill); + simple_circle(1, cent, rad); + } + line_thickness(lt.thickness); + switch (lt.type) { + case line_type::invisible: + break; + case line_type::dashed: + dashed_circle(cent, rad, lt); + break; + case line_type::dotted: + dotted_circle(cent, rad, lt); + break; + case line_type::solid: + simple_circle(0, cent, rad); + break; + default: + assert(0); + } +} + +void simple_output::ellipse(const position ¢, const distance &dim, + const line_type <, double fill) +{ + if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) { + if (get_last_filled() == 0) + set_fill(fill); + simple_ellipse(1, cent, dim); + } + if (lt.type != line_type::invisible) + line_thickness(lt.thickness); + switch (lt.type) { + case line_type::invisible: + break; + case line_type::dotted: + dotted_ellipse(cent, dim, lt); + break; + case line_type::dashed: + dashed_ellipse(cent, dim, lt); + break; + case line_type::solid: + simple_ellipse(0, cent, dim); + break; + default: + assert(0); + } +} + +class troff_output : public simple_output { + const char *last_filename; + position upper_left; + double height; + double scale; + double last_line_thickness; + double last_fill; + char *last_filled; // color + char *last_outlined; // color +public: + troff_output(); + ~troff_output(); + void start_picture(double, const position &ll, const position &ur); + void finish_picture(); + void text(const position &, text_piece *, int, double); + void dot(const position &, const line_type &); + void command(const char *, const char *, int); + void set_location(const char *, int); + void simple_line(const position &, const position &); + void simple_spline(const position &, const position *, int n); + void simple_arc(const position &, const position &, const position &); + void simple_circle(int, const position &, double rad); + void simple_ellipse(int, const position &, const distance &); + void simple_polygon(int, const position *, int); + void line_thickness(double p); + void set_fill(double); + void set_color(char *, char *); + void reset_color(); + char *get_last_filled(); + char *get_outline_color(); + position transform(const position &); +}; + +output *make_troff_output() +{ + return new troff_output; +} + +troff_output::troff_output() +: last_filename(0), last_line_thickness(BAD_THICKNESS), + last_fill(-1.0), last_filled(0), last_outlined(0) +{ +} + +troff_output::~troff_output() +{ + free((char *)last_filename); +} + +inline position troff_output::transform(const position &pos) +{ + return position((pos.x - upper_left.x)/scale, + (upper_left.y - pos.y)/scale); +} + +#define FILL_REG "00" + +// If this register > 0, then pic will generate \X'ps: ...' commands +// if the aligned attribute is used. +#define GROPS_REG "0p" + +// If this register is defined, geqn won't produce '\x's. +#define EQN_NO_EXTRA_SPACE_REG "0x" + +void troff_output::start_picture(double sc, + const position &ll, const position &ur) +{ + upper_left.x = ll.x; + upper_left.y = ur.y; + scale = compute_scale(sc, ll, ur); + height = (ur.y - ll.y)/scale; + double width = (ur.x - ll.x)/scale; + printf(".PS %.3fi %.3fi", height, width); + if (args) + printf(" %s\n", args); + else + putchar('\n'); + printf(".\\\" %g %g %g %g\n", ll.x, ll.y, ur.x, ur.y); + printf(".\\\" %.3fi %.3fi %.3fi %.3fi\n", 0.0, height, width, 0.0); + printf(".nr " FILL_REG " \\n(.u\n.nf\n"); + printf(".nr " EQN_NO_EXTRA_SPACE_REG " 1\n"); + // This guarantees that if the picture is used in a diversion it will + // have the right width. + printf("\\h'%.3fi'\n.sp -1\n", width); +} + +void troff_output::finish_picture() +{ + line_thickness(BAD_THICKNESS); + last_fill = -1.0; // force it to be reset for each picture + reset_color(); + if (!(want_flyback || want_alternate_flyback)) + printf(".sp %.3fi+1\n", height); + printf(".if \\n(" FILL_REG " .fi\n"); + printf(".br\n"); + printf(".nr " EQN_NO_EXTRA_SPACE_REG " 0\n"); + // this is a little gross + set_location(current_filename, current_lineno); + if (want_flyback) + fputs(".PF\n", stdout); + else if (want_alternate_flyback) + fputs(".PY\n", stdout); + else + fputs(".PE\n", stdout); +} + +void troff_output::command(const char *s, + const char *filename, int lineno) +{ + if (filename != 0) + set_location(filename, lineno); + fputs(s, stdout); + putchar('\n'); +} + +void troff_output::simple_circle(int filled, const position ¢, double rad) +{ + position c = transform(cent); + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'%c %.3fi'" + "\n.sp -1\n", + c.x - rad/scale, + c.y, + (filled ? 'C' : 'c'), + rad*2.0/scale); +} + +void troff_output::simple_ellipse(int filled, const position ¢, + const distance &dim) +{ + position c = transform(cent); + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'%c %.3fi %.3fi'" + "\n.sp -1\n", + c.x - dim.x/(2.0*scale), + c.y, + (filled ? 'E' : 'e'), + dim.x/scale, dim.y/scale); +} + +void troff_output::simple_arc(const position &start, const distance ¢, + const distance &end) +{ + position s = transform(start); + position c = transform(cent); + distance cv = c - s; + distance ev = transform(end) - c; + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'a %.3fi %.3fi %.3fi %.3fi'" + "\n.sp -1\n", + s.x, s.y, cv.x, cv.y, ev.x, ev.y); +} + +void troff_output::simple_line(const position &start, const position &end) +{ + position s = transform(start); + distance ev = transform(end) - s; + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'l %.3fi %.3fi'" + "\n.sp -1\n", + s.x, s.y, ev.x, ev.y); +} + +void troff_output::simple_spline(const position &start, + const position *v, int n) +{ + position pos = transform(start); + printf("\\h'%.3fi'" + "\\v'%.3fi'", + pos.x, pos.y); + fputs("\\D'~ ", stdout); + for (int i = 0; i < n; i++) { + position temp = transform(v[i]); + distance d = temp - pos; + pos = temp; + if (i != 0) + putchar(' '); + printf("%.3fi %.3fi", d.x, d.y); + } + printf("'\n.sp -1\n"); +} + +// a solid polygon + +void troff_output::simple_polygon(int filled, const position *v, int n) +{ + position pos = transform(v[0]); + printf("\\h'%.3fi'" + "\\v'%.3fi'", + pos.x, pos.y); + printf("\\D'%c ", (filled ? 'P' : 'p')); + for (int i = 1; i < n; i++) { + position temp = transform(v[i]); + distance d = temp - pos; + pos = temp; + if (i != 1) + putchar(' '); + printf("%.3fi %.3fi", d.x, d.y); + } + printf("'\n.sp -1\n"); +} + +const double TEXT_AXIS = 0.22; // in ems + +static const char *choose_delimiter(const char *text) +{ + if (strchr(text, '\'') == 0) + return "'"; + else + return "\\(ts"; +} + +void troff_output::text(const position ¢er, text_piece *v, int n, + double ang) +{ + line_thickness(BAD_THICKNESS); // text might use lines (e.g., in equations) + int rotate_flag = 0; + if (driver_extension_flag && ang != 0.0) { + rotate_flag = 1; + position c = transform(center); + printf(".if \\n(" GROPS_REG " \\{\\\n" + "\\h'%.3fi'" + "\\v'%.3fi'" + "\\X'ps: exec gsave currentpoint 2 copy translate %.4f rotate neg exch neg exch translate'" + "\n.sp -1\n" + ".\\}\n", + c.x, c.y, -ang*180.0/M_PI); + } + for (int i = 0; i < n; i++) + if (v[i].text != 0 && *v[i].text != '\0') { + position c = transform(center); + if (v[i].filename != 0) + set_location(v[i].filename, v[i].lineno); + printf("\\h'%.3fi", c.x); + const char *delim = choose_delimiter(v[i].text); + if (v[i].adj.h == RIGHT_ADJUST) + printf("-\\w%s%s%su", delim, v[i].text, delim); + else if (v[i].adj.h != LEFT_ADJUST) + printf("-(\\w%s%s%su/2u)", delim, v[i].text, delim); + putchar('\''); + printf("\\v'%.3fi-(%dv/2u)+%dv+%.2fm", + c.y, + n - 1, + i, + TEXT_AXIS); + if (v[i].adj.v == ABOVE_ADJUST) + printf("-.5v"); + else if (v[i].adj.v == BELOW_ADJUST) + printf("+.5v"); + putchar('\''); + fputs(v[i].text, stdout); + fputs("\n.sp -1\n", stdout); + } + if (rotate_flag) + printf(".if \\n(" GROPS_REG " \\{\\\n" + "\\X'ps: exec grestore'\n.sp -1\n" + ".\\}\n"); +} + +void troff_output::line_thickness(double p) +{ + if (p < 0.0) + p = RELATIVE_THICKNESS; + if (driver_extension_flag && p != last_line_thickness) { + printf("\\D't %.3fp'\\h'%.3fp'\n.sp -1\n", p, -p); + last_line_thickness = p; + } +} + +void troff_output::set_fill(double f) +{ + if (driver_extension_flag && f != last_fill) { + // \D'Fg ...' emits a node only in compatibility mode, + // thus we add a dummy node + printf("\\&\\D'Fg %.3f'\n.sp -1\n", 1.0 - f); + last_fill = f; + } + if (last_filled) { + free(last_filled); + last_filled = 0; + printf(".fcolor\n"); + } +} + +void troff_output::set_color(char *color_fill, char *color_outlined) +{ + if (driver_extension_flag) { + if (last_filled || last_outlined) { + reset_color(); + } + // .gcolor and .fcolor emit a node in compatibility mode only, + // but that won't work anyway + if (color_fill) { + printf(".fcolor %s\n", color_fill); + last_filled = strsave(color_fill); + } + if (color_outlined) { + printf(".gcolor %s\n", color_outlined); + last_outlined = strsave(color_outlined); + } + } +} + +void troff_output::reset_color() +{ + if (driver_extension_flag) { + if (last_filled) { + printf(".fcolor\n"); + free(last_filled); + last_filled = 0; + } + if (last_outlined) { + printf(".gcolor\n"); + free(last_outlined); + last_outlined = 0; + } + } +} + +char *troff_output::get_last_filled() +{ + return last_filled; +} + +char *troff_output::get_outline_color() +{ + return last_outlined; +} + +const double DOT_AXIS = .044; + +void troff_output::dot(const position ¢, const line_type <) +{ + if (driver_extension_flag) { + line_thickness(lt.thickness); + simple_line(cent, cent); + } + else { + position c = transform(cent); + printf("\\h'%.3fi-(\\w'.'u/2u)'" + "\\v'%.3fi+%.2fm'" + ".\n.sp -1\n", + c.x, + c.y, + DOT_AXIS); + } +} + +void troff_output::set_location(const char *s, int n) +{ + if (last_filename != 0 && strcmp(s, last_filename) == 0) + printf(".lf %d\n", n); + else { + printf(".lf %d %s\n", n, s); + char *lfn = strdup(s); + if (0 == lfn) + fatal("memory allocation failure while copying file name"); + last_filename = lfn; + } +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/preconv/preconv.1.man b/src/preproc/preconv/preconv.1.man new file mode 100644 index 0000000..1535bae --- /dev/null +++ b/src/preproc/preconv/preconv.1.man @@ -0,0 +1,559 @@ +.TH preconv @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +preconv \- prepare files for typesetting with +.I groff +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 2006-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_preconv_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 preconv +.RB [ \-dr ] +.RB [ \-D\~\c +.IR fallback-encoding ] +.RB [ \-e\~\c +.IR encoding ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY preconv +.B \-h +. +.SY preconv +.B \-\-help +.YS +. +. +.SY preconv +.B \-v +. +.SY preconv +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +.I preconv +reads each +.IR file , +converts its encoded characters to a form +.MR @g@troff @MAN1EXT@ +can interpret, +and sends the result to the standard output stream. +. +Currently, +this means that code points in the range 0\[en]127 +(in US-ASCII, +ISO\~8859, +or Unicode) +remain as-is and the remainder are converted to the +.I groff +special character form +.RB \[lq] \[rs][\c +.BI u XXXX ]\c +\[rq], +where +.I XXXX +is a hexadecimal number of four to six digits corresponding to a Unicode +code point. +. +By default, +.I preconv +also inserts a +.I roff +.B .lf +request at the beginning of each +.IR file , +identifying it for the benefit of later processing +(including diagnostic messages); +the +.B \-r +option suppresses this behavior. +. +. +.PP +In typical usage scenarios, +.I preconv +need not be run directly; +instead it should be invoked with the +.B \-k +or +.B \-K +options of +.IR groff . +. +If no +.I file +operands are given on the command line, +or if +.I file +is +.RB \[lq] \- \[rq], +the standard input stream is read. +. +. +.PP +.I preconv +tries to find the input encoding with the following algorithm, +stopping at the first success. +. +. +.IP 1. 4n +If the input encoding has been explicitly specified with option +.BR \-e , +use it. +. +. +.IP 2. +If the input starts with a Unicode Byte Order Mark, +determine the encoding as UTF-8, +UTF-16, +or UTF-32 accordingly. +. +. +.IP 3. +If the input stream is seekable, +check the first and second input lines for a recognized GNU\~Emacs +file-local variable identifying the character encoding, +here referred to as the \[lq]coding tag\[rq] for brevity. +. +If found, +use it. +. +. +.IP 4. +If the input stream is seekable, +and if the +.I uchardet +library is available on the system, +use it to try to infer the encoding of the file. +. +. +.IP 5. +If the +.B \-D +option specifies an encoding, +use it. +. +. +.IP 6. +Use the encoding specified by the current locale +.RI ( LC_CTYPE ), +unless the locale is +\[lq]C\[rq], +\[lq]POSIX\[rq], +or empty, +in which case assume Latin-1 +(ISO\~8859-1). +. +. +.PP +The coding tag and +.I uchardet +methods in the above procedure rely upon a seekable input stream; +when +.I preconv +reads from a pipe, +the stream is not seekable, +and these detection methods are skipped. +. +If character encoding detection of your input files is unreliable, +arrange for one of the other methods to succeed by using +.IR preconv 's +.B \-D +or +.B \-e +options, +or by configuring your locale appropriately. +. +.I groff +also supports a +.I \%GROFF_ENCODING +environment variable, +which can be overridden by its +.B \-K +option. +. +Valid values for +(or parameters to) +all of these are enumerated in the lists of recognized coding tags in +the next subsection, +and are further influenced by +.I iconv +library support. +. +. +.\" ==================================================================== +.SS "Coding tags" +.\" ==================================================================== +. +Text editors that support more than a single character encoding need +tags within the input files to mark the file's encoding. +. +While it is possible to guess the right input encoding with the help of +heuristics that are reliable for a preponderance of natural language +texts, +they are not absolutely reliable. +. +Heuristics can fail on inputs that are too short or don't represent a +natural language. +. +. +.PP +Consequently, +.I preconv +supports the coding tag convention used by GNU\~Emacs +(with some restrictions). +. +This notation appears in specially marked regions of an input file +designated for \[lq]file-local variables\[rq]. +. +. +.PP +.I preconv +interprets the following syntax if it occurs in a +.I roff +comment +in the first or second line of the input file. +. +Both \[lq]\[rs]"\[rq] and \[lq]\[rs]#\[rq] comment forms are recognized, +but the control +(or no-break control) +character must be the default and must begin the line. +. +Similarly, +the escape character must be the default. +. +. +.RS +.EX +.B \-*\- \c +.RB [.\|.\|. ; ]\~\c +.B coding: \c +.I encoding\c +.RB [ ;\~ .\|.\|.\&]\~\c +.B \-*\- +.EE +.RE +. +. +.PP +The only variable +.I preconv +interprets is \[lq]coding\[rq], +which can take the values listed below. +. +. +.PP +The following list comprises all MIME \[lq]charset\[rq] parameter values +recognized, +case-insensitively, +by +.IR preconv . +. +.RS +\%big5, +\%cp1047, +\%euc\-jp, +\%euc\-kr, +\%gb2312, +\%iso\-8859\-1, +\%iso\-8859\-2, +\%iso\-8859\-5, +\%iso\-8859\-7, +\%iso\-8859\-9, +\%iso\-8859\-13, +\%iso\-8859\-15, +\%koi8\-r, +\%us\-ascii, +\%utf\-8, +\%utf\-16, +\%utf\-16be, +\%utf\-16le +.RE +. +. +.PP +In addition, +the following list of other coding tags is recognized, +each of which is mapped to an appropriate value from the list above. +. +.RS +\%ascii, +\%chinese\-big5, +\%chinese\-euc, +\%chinese\-iso\-8bit, +\%cn\-big5, +\%cn\-gb, +\%cn\-gb\-2312, +\%cp878, +\%csascii, +\%csisolatin1, +\%cyrillic\-iso\-8bit, +\%cyrillic\-koi8, +\%euc\-china, +\%euc\-cn, +\%euc\-japan, +\%euc\-japan\-1990, +\%euc\-korea, +\%greek\-iso\-8bit, +\%iso\-10646/utf8, +\%iso\-10646/utf\-8, +\%iso\-latin\-1, +\%iso\-latin\-2, +\%iso\-latin\-5, +\%iso\-latin\-7, +\%iso\-latin\-9, +\%japanese\-euc, +\%japanese\-iso\-8bit, +\%jis8, +\%koi8, +\%korean\-euc, +\%korean\-iso\-8bit, +\%latin\-0, +\%latin1, +\%latin\-1, +\%latin\-2, +\%latin\-5, +\%latin\-7, +\%latin\-9, +\%mule\-utf\-8, +\%mule\-utf\-16, +\%mule\-utf\-16be, +\%mule\-utf\-16\-be, +\%mule\-utf\-16be\-with\-signature, +\%mule\-utf\-16le, +\%mule\-utf\-16\-le, +\%mule\-utf\-16le\-with\-signature, +\%utf8, +\%utf\-16\-be, +\%utf\-16\-be\-with\-signature, +\%utf\-16be\-with\-signature, +\%utf\-16\-le, +\%utf\-16\-le\-with\-signature, +\%utf\-16le\-with\-signature +.RE +. +. +.PP +Trailing +\[lq]\-dos\[rq], +\[lq]\-unix\[rq], +and +\[lq]\-mac\[rq] +suffixes on coding tags +(which indicate the end-of-line convention used in the file) +are disregarded for the purpose of comparison with the above tags. +. +. +.\" ==================================================================== +.SS "\f[I]iconv\f[] support" +.\" ==================================================================== +. +While +.I preconv +recognizes all of the coding tags listed above, +it is capable on its own of interpreting only three encodings: +Latin-1, +code page 1047, +and UTF-8. +. +If +.I iconv +support is configured at compile time and available at run time, +all others are passed to +.I iconv +library functions, +which may recognize many additional encoding strings. +. +The command +.RB \[lq] preconv\~\-v \[rq] +discloses whether +.I iconv +support is configured. +. +. +.PP +The use of +.I iconv +means that characters in the input that encode invalid code points for +that encoding may be dropped from the output stream or mapped to the +Unicode replacement character +(U+FFFD). +. +Compare the following examples using the input \[lq]caf\['e]\[rq] +(note the \[lq]e\[rq] with an acute accent), +which due to its short length challenges inference of the encoding used. +. +.RS +.EX +printf \[aq]caf\[rs]351\[rs]n\[aq] | LC_ALL=en_US.UTF\-8 preconv +printf \[aq]caf\[rs]351\[rs]n\[aq] | preconv \-e us\-ascii +printf \[aq]caf\[rs]351\[rs]n\[aq] | preconv \-e latin\-1 +.EE +.RE +. +The fate of the accented \[lq]e\[rq] differs in each case. +. +In the first, +.I uchardet +fails to detect an encoding +(though the library on your system may behave differently) +and +.I preconv +falls back to the locale settings, +where octal 351 starts an incomplete UTF-8 sequence and results in the +Unicode replacement character. +. +In the second, +it is not a representable character in the declared input encoding of +US-ASCII and is discarded by +.IR iconv . +. +In the last, +it is correctly detected and mapped. +. +. +.\" ==================================================================== +.SS Limitations +.\" ==================================================================== +. +.I preconv +cannot perform any transformation on input that it cannot see. +. +Examples include files that are interpolated by preprocessors that run +subsequently, +including +.MR @g@soelim @MAN1EXT@ ; +files included by +.I @g@troff +itself through +.RB \[lq] so \[rq] +and similar requests; +and string definitions passed to +.I @g@troff +through its +.B \-d +command-line option. +. +. +.P +.I preconv +assumes that its input uses the default escape character, +a backslash +.BR \[rs] , +and writes special character escape sequences accordingly. +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-h +and +.B \-\-help +display a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-d +Emit debugging messages to the standard error stream. +. +. +.TP +.BI \-D\~ fallback-encoding +Report +.I fallback-encoding +if all detection methods fail. +. +. +.TP +.BI \-e\~ encoding +Skip detection and assume +.IR encoding ; +see +.IR groff 's +.B \-K +option. +. +. +.TP +.B \-r +Write files \[lq]raw\[rq]; +do not add +.B .lf +requests. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.MR groff @MAN1EXT@ , +.MR iconv 3 , +.MR locale 7 +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_preconv_1_man_C] +.do rr *groff_preconv_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/preproc/preconv/preconv.am b/src/preproc/preconv/preconv.am new file mode 100644 index 0000000..199ff66 --- /dev/null +++ b/src/preproc/preconv/preconv.am @@ -0,0 +1,37 @@ +# 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/>. + +bin_PROGRAMS += preconv +preconv_LDADD = libgroff.a $(LIBM) $(LIBICONV) $(UCHARDET_LIBS) \ + lib/libgnu.a +preconv_SOURCES = src/preproc/preconv/preconv.cpp +preconv_CPPFLAGS = $(AM_CPPFLAGS) $(UCHARDET_CFLAGS) +man1_MANS += src/preproc/preconv/preconv.1 +EXTRA_DIST += src/preproc/preconv/preconv.1.man + +preconv_TESTS = \ + src/preproc/preconv/tests/do-not-seek-the-unseekable.sh \ + src/preproc/preconv/tests/smoke-test.sh +TESTS += $(preconv_TESTS) +EXTRA_DIST += $(preconv_TESTS) + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/preconv/preconv.cpp b/src/preproc/preconv/preconv.cpp new file mode 100644 index 0000000..d403425 --- /dev/null +++ b/src/preproc/preconv/preconv.cpp @@ -0,0 +1,1318 @@ +/* Copyright (C) 2005-2020 Free Software Foundation, Inc. + Written by Werner Lemberg (wl@gnu.org) + +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 <assert.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/stat.h> +#ifdef HAVE_UCHARDET +#include <uchardet/uchardet.h> +#endif + +#include "errarg.h" +#include "error.h" +#include "localcharset.h" +#include "nonposix.h" +#include "stringclass.h" +#include "lf.h" + +#include <locale.h> + +#if HAVE_ICONV +# include <iconv.h> +# ifdef WORDS_BIGENDIAN +# define UNICODE "UTF-32BE" +# else +# define UNICODE "UTF-32LE" +# endif +#endif + +#define MAX_VAR_LEN 100 + +extern "C" const char *Version_string; + +char fallback_encoding[MAX_VAR_LEN]; +char user_encoding[MAX_VAR_LEN]; +char encoding_string[MAX_VAR_LEN]; +bool is_debugging = false; +int raw_flag = 0; + +struct conversion { + const char *from; + const char *to; +}; + +// The official list of MIME tags can be found at +// +// http://www.iana.org/assignments/character-sets +// +// For encodings which don't have a MIME tag we use GNU iconv's encoding +// names (which also work with the portable GNU libiconv package). They +// are marked with '*'. +// +// Encodings specific to XEmacs and Emacs are marked as such; no mark means +// that they are used by both Emacs and XEmacs. +// +// Encodings marked with '--' are special to Emacs, XEmacs, or other +// applications and shouldn't be used for data exchange. +// +// 'Not covered' means that the encoding can be handled neither by GNU iconv +// nor by libiconv, or just one of them has support for it. +// +// A special case is VIQR encoding: Despite of having a MIME tag it is +// missing in both libiconv 1.10 and iconv (coming with GNU libc 2.3.6). +// +// Finally, we add all aliases of GNU iconv for 'ascii', 'latin1', and +// 'utf8' to catch those encoding names before iconv is called. +// +// Note that most entries are commented out -- only a small, (rather) +// reliable and stable subset of encodings is recognized (for coding tags) +// which are still in greater use today (January 2006). Most notably, all +// Windows-specific encodings are not selected because they lack stability: +// Microsoft has changed the mappings instead of creating new versions. +// +// Please contact the groff list if you find the selection inadequate. + +static const conversion +emacs_to_mime[] = { + {"ascii", "US-ASCII"}, // Emacs + {"big5", "Big5"}, + {"chinese-big5", "Big5"}, // Emacs + {"chinese-euc", "GB2312"}, // XEmacs + {"chinese-iso-8bit", "GB2312"}, // Emacs + {"cn-big5", "Big5"}, + {"cn-gb", "GB2312"}, // Emacs + {"cn-gb-2312", "GB2312"}, + {"cp878", "KOI8-R"}, // Emacs + {"cp1047", "CP1047"}, // EBCDIC + {"csascii", "US-ASCII"}, // alias + {"csisolatin1", "ISO-8859-1"}, // alias + {"cyrillic-iso-8bit", "ISO-8859-5"}, // Emacs + {"cyrillic-koi8", "KOI8-R"}, // not KOI8!, Emacs + {"euc-china", "GB2312"}, // Emacs + {"euc-cn", "GB2312"}, // Emacs + {"euc-japan", "EUC-JP"}, + {"euc-japan-1990", "EUC-JP"}, // Emacs + {"euc-jp", "EUC-JP"}, + {"euc-korea", "EUC-KR"}, + {"euc-kr", "EUC-KR"}, + {"gb2312", "GB2312"}, + {"greek-iso-8bit", "ISO-8859-7"}, + {"iso-10646/utf8", "UTF-8"}, // alias + {"iso-10646/utf-8", "UTF-8"}, // alias + {"iso-8859-1", "ISO-8859-1"}, + {"iso-8859-13", "ISO-8859-13"}, // Emacs + {"iso-8859-15", "ISO-8859-15"}, + {"iso-8859-2", "ISO-8859-2"}, + {"iso-8859-5", "ISO-8859-5"}, + {"iso-8859-7", "ISO-8859-7"}, + {"iso-8859-9", "ISO-8859-9"}, + {"iso-latin-1", "ISO-8859-1"}, + {"iso-latin-2", "ISO-8859-2"}, // Emacs + {"iso-latin-5", "ISO-8859-9"}, // Emacs + {"iso-latin-7", "ISO-8859-13"}, // Emacs + {"iso-latin-9", "ISO-8859-15"}, // Emacs + {"japanese-iso-8bit", "EUC-JP"}, // Emacs + {"japanese-euc", "EUC-JP"}, // XEmacs + {"jis8", "EUC-JP"}, // XEmacs + {"koi8", "KOI8-R"}, // not KOI8!, Emacs + {"koi8-r", "KOI8-R"}, + {"korean-euc", "EUC-KR"}, // XEmacs + {"korean-iso-8bit", "EUC-KR"}, // Emacs + {"latin1", "ISO-8859-1"}, // alias + {"latin-0", "ISO-8859-15"}, // Emacs + {"latin-1", "ISO-8859-1"}, // Emacs + {"latin-2", "ISO-8859-2"}, // Emacs + {"latin-5", "ISO-8859-9"}, // Emacs + {"latin-7", "ISO-8859-13"}, // Emacs + {"latin-9", "ISO-8859-15"}, // Emacs + {"mule-utf-16", "UTF-16"}, // Emacs + {"mule-utf-16be", "UTF-16BE"}, // Emacs + {"mule-utf-16-be", "UTF-16BE"}, // Emacs + {"mule-utf-16be-with-signature", "UTF-16"}, // Emacs, not UTF-16BE + {"mule-utf-16le", "UTF-16LE"}, // Emacs + {"mule-utf-16-le", "UTF-16LE"}, // Emacs + {"mule-utf-16le-with-signature", "UTF-16"}, // Emacs, not UTF-16LE + {"mule-utf-8", "UTF-8"}, // Emacs + {"us-ascii", "US-ASCII"}, // Emacs + {"utf8", "UTF-8"}, // alias + {"utf-16", "UTF-16"}, // Emacs + {"utf-16be", "UTF-16BE"}, // Emacs + {"utf-16-be", "UTF-16BE"}, // Emacs + {"utf-16be-with-signature", "UTF-16"}, // Emacs, not UTF-16BE + {"utf-16-be-with-signature", "UTF-16"}, // Emacs, not UTF-16BE + {"utf-16le", "UTF-16LE"}, // Emacs + {"utf-16-le", "UTF-16LE"}, // Emacs + {"utf-16le-with-signature", "UTF-16"}, // Emacs, not UTF-16LE + {"utf-16-le-with-signature", "UTF-16"}, // Emacs, not UTF-16LE + {"utf-8", "UTF-8"}, // Emacs + +// {"alternativnyj", ""}, // ? +// {"arabic-iso-8bit", "ISO-8859-6"}, // Emacs +// {"binary", ""}, // -- +// {"chinese-hz", "HZ-GB-2312"}, // Emacs +// {"chinese-iso-7bit", "ISO-2022-CN"}, // Emacs +// {"chinese-iso-8bit-with-esc", ""}, // -- +// {"compound-text", ""}, // -- +// {"compound-text-with-extension", ""}, // -- +// {"cp1125", "cp1125"}, // * +// {"cp1250", "windows-1250"},// Emacs +// {"cp1251", "windows-1251"},// Emacs +// {"cp1252", "windows-1252"},// Emacs +// {"cp1253", "windows-1253"},// Emacs +// {"cp1254", "windows-1254"},// Emacs +// {"cp1255", "windows-1255"},// Emacs +// {"cp1256", "windows-1256"},// Emacs +// {"cp1257", "windows-1257"},// Emacs +// {"cp1258", "windows-1258"},// Emacs +// {"cp437", "cp437"}, // Emacs +// {"cp720", ""}, // not covered +// {"cp737", "cp737"}, // *, Emacs +// {"cp775", "cp775"}, // Emacs +// {"cp850", "cp850"}, // Emacs +// {"cp851", "cp851"}, // Emacs +// {"cp852", "cp852"}, // Emacs +// {"cp855", "cp855"}, // Emacs +// {"cp857", "cp857"}, // Emacs +// {"cp860", "cp860"}, // Emacs +// {"cp861", "cp861"}, // Emacs +// {"cp862", "cp862"}, // Emacs +// {"cp863", "cp863"}, // Emacs +// {"cp864", "cp864"}, // Emacs +// {"cp865", "cp865"}, // Emacs +// {"cp866", "cp866"}, // Emacs +// {"cp866u", "cp1125"}, // *, Emacs +// {"cp869", "cp869"}, // Emacs +// {"cp874", "cp874"}, // *, Emacs +// {"cp932", "cp932"}, // *, Emacs +// {"cp936", "cp936"}, // Emacs +// {"cp949", "cp949"}, // *, Emacs +// {"cp950", "cp950"}, // *, Emacs +// {"ctext", ""}, // -- +// {"ctext-no-compositions", ""}, // -- +// {"ctext-with-extensions", ""}, // -- +// {"cyrillic-alternativnyj", ""}, // ?, Emacs +// {"cyrillic-iso-8bit-with-esc", ""}, // -- +// {"cyrillic-koi8-t", "KOI8-T"}, // *, Emacs +// {"devanagari", ""}, // not covered +// {"dos", ""}, // -- +// {"emacs-mule", ""}, // -- +// {"euc-jisx0213", "EUC-JISX0213"},// *, XEmacs? +// {"euc-jisx0213-with-esc", ""}, // XEmacs? +// {"euc-taiwan", "EUC-TW"}, // *, Emacs +// {"euc-tw", "EUC-TW"}, // *, Emacs +// {"georgian-ps", "GEORGIAN-PS"}, // *, Emacs +// {"greek-iso-8bit-with-esc", ""}, // -- +// {"hebrew-iso-8bit", "ISO-8859-8"}, // Emacs +// {"hebrew-iso-8bit-with-esc", ""}, // -- +// {"hz", "HZ-GB-2312"}, +// {"hz-gb-2312", "HZ-GB-2312"}, +// {"in-is13194", ""}, // not covered +// {"in-is13194-devanagari", ""}, // not covered +// {"in-is13194-with-esc", ""}, // -- +// {"iso-2022-7", ""}, // XEmacs? +// {"iso-2022-7bit", ""}, // -- +// {"iso-2022-7bit-lock", ""}, // -- +// {"iso-2022-7bit-lock-ss2", ""}, // -- +// {"iso-2022-7bit-ss2", ""}, // -- +// {"iso-2022-8", ""}, // XEmacs? +// {"iso-2022-8bit", ""}, // XEmacs? +// {"iso-2022-8bit-lock", ""}, // XEmacs? +// {"iso-2022-8bit-lock-ss2", ""}, // XEmacs? +// {"iso-2022-8bit-ss2", ""}, // -- +// {"iso-2022-cjk", ""}, // -- +// {"iso-2022-cn", "ISO-2022-CN"}, // Emacs +// {"iso-2022-cn-ext", "ISO-2022-CN-EXT"},// Emacs +// {"iso-2022-int-1", ""}, // -- +// {"iso-2022-jp", "ISO-2022-JP"}, +// {"iso-2022-jp-1978-irv", "ISO-2022-JP"}, +// {"iso-2022-jp-2", "ISO-2022-JP-2"}, +// {"iso-2022-jp-3", "ISO-2022-JP-3"},// *, XEmacs? +// {"iso-2022-jp-3-compatible", ""}, // XEmacs? +// {"iso-2022-jp-3-strict", "ISO-2022-JP-3"},// *, XEmacs? +// {"iso-2022-kr", "ISO-2022-KR"}, +// {"iso-2022-lock", ""}, // XEmacs? +// {"iso-8859-10", "ISO-8859-10"}, // Emacs +// {"iso-8859-11", "ISO-8859-11"}, // *, Emacs +// {"iso-8859-14", "ISO-8859-14"}, // Emacs +// {"iso-8859-16", "ISO-8859-16"}, +// {"iso-8859-3", "ISO-8859-3"}, +// {"iso-8859-4", "ISO-8859-4"}, +// {"iso-8859-6", "ISO-8859-6"}, +// {"iso-8859-8", "ISO-8859-8"}, +// {"iso-8859-8-e", "ISO-8859-8"}, +// {"iso-8859-8-i", "ISO-8859-8"}, // Emacs +// {"iso-latin-10", "ISO-8859-16"}, // Emacs +// {"iso-latin-1-with-esc", ""}, // -- +// {"iso-latin-2-with-esc", ""}, // -- +// {"iso-latin-3", "ISO-8859-3"}, // Emacs +// {"iso-latin-3-with-esc", ""}, // -- +// {"iso-latin-4", "ISO-8859-4"}, // Emacs +// {"iso-latin-4-with-esc", ""}, // -- +// {"iso-latin-5-with-esc", ""}, // -- +// {"iso-latin-6", "ISO-8859-10"}, // Emacs +// {"iso-latin-8", "ISO-8859-14"}, // Emacs +// {"iso-safe", ""}, // -- +// {"japanese-iso-7bit-1978-irv", "ISO-2022-JP"}, // Emacs +// {"japanese-iso-8bit-with-esc", ""}, // -- +// {"japanese-shift-jis", "Shift_JIS"}, // Emacs +// {"japanese-shift-jisx0213", ""}, // XEmacs? +// {"jis7", "ISO-2022-JP"}, // Xemacs +// {"junet", "ISO-2022-JP"}, +// {"koi8-t", "KOI8-T"}, // *, Emacs +// {"koi8-u", "KOI8-U"}, // Emacs +// {"korean-iso-7bit-lock", "ISO-2022-KR"}, +// {"korean-iso-8bit-with-esc", ""}, // -- +// {"lao", ""}, // not covered +// {"lao-with-esc", ""}, // -- +// {"latin-10", "ISO-8859-16"}, // Emacs +// {"latin-3", "ISO-8859-3"}, // Emacs +// {"latin-4", "ISO-8859-4"}, // Emacs +// {"latin-6", "ISO-8859-10"}, // Emacs +// {"latin-8", "ISO-8859-14"}, // Emacs +// {"mac", ""}, // -- +// {"mac-roman", "MACINTOSH"}, // Emacs +// {"mik", ""}, // not covered +// {"next", "NEXTSTEP"}, // *, Emacs +// {"no-conversion", ""}, // -- +// {"old-jis", "ISO-2022-JP"}, +// {"pt154", "PT154"}, // Emacs +// {"raw-text", ""}, // -- +// {"ruscii", "cp1125"}, // *, Emacs +// {"shift-jis", "Shift_JIS"}, // XEmacs +// {"shift_jis", "Shift_JIS"}, +// {"shift_jisx0213", "Shift_JISX0213"},// *, XEmacs? +// {"sjis", "Shift_JIS"}, // Emacs +// {"tcvn", "TCVN"}, // *, Emacs +// {"tcvn-5712", "TCVN"}, // *, Emacs +// {"thai-tis620", "TIS-620"}, +// {"thai-tis620-with-esc", ""}, // -- +// {"th-tis620", "TIS-620"}, +// {"tibetan", ""}, // not covered +// {"tibetan-iso-8bit", ""}, // not covered +// {"tibetan-iso-8bit-with-esc", ""}, // -- +// {"tis-620", "TIS-620"}, +// {"tis620", "TIS-620"}, +// {"undecided", ""}, // -- +// {"unix", ""}, // -- +// {"utf-7", "UTF-7"}, // Emacs +// {"utf-7-safe", ""}, // XEmacs? +// {"utf-8-ws", "UTF-8"}, // XEmacs? +// {"vietnamese-tcvn", "TCVN"}, // *, Emacs +// {"vietnamese-viqr", "VIQR"}, // not covered +// {"vietnamese-viscii", "VISCII"}, +// {"vietnamese-vscii", ""}, // not covered +// {"viqr", "VIQR"}, // not covered +// {"viscii", "VISCII"}, +// {"vscii", ""}, // not covered +// {"windows-037", ""}, // not covered +// {"windows-10000", ""}, // not covered +// {"windows-10001", ""}, // not covered +// {"windows-10006", ""}, // not covered +// {"windows-10007", ""}, // not covered +// {"windows-10029", ""}, // not covered +// {"windows-10079", ""}, // not covered +// {"windows-10081", ""}, // not covered +// {"windows-1026", ""}, // not covered +// {"windows-1200", ""}, // not covered +// {"windows-1250", "windows-1250"}, +// {"windows-1251", "windows-1251"}, +// {"windows-1252", "windows-1252"}, +// {"windows-1253", "windows-1253"}, +// {"windows-1254", "windows-1254"}, +// {"windows-1255", "windows-1255"}, +// {"windows-1256", "windows-1256"}, +// {"windows-1257", "windows-1257"}, +// {"windows-1258", "windows-1258"}, +// {"windows-1361", "cp1361"}, // *, XEmacs +// {"windows-437", "cp437"}, // XEmacs +// {"windows-500", ""}, // not covered +// {"windows-708", ""}, // not covered +// {"windows-709", ""}, // not covered +// {"windows-710", ""}, // not covered +// {"windows-720", ""}, // not covered +// {"windows-737", "cp737"}, // *, XEmacs +// {"windows-775", "cp775"}, // XEmacs +// {"windows-850", "cp850"}, // XEmacs +// {"windows-852", "cp852"}, // XEmacs +// {"windows-855", "cp855"}, // XEmacs +// {"windows-857", "cp857"}, // XEmacs +// {"windows-860", "cp860"}, // XEmacs +// {"windows-861", "cp861"}, // XEmacs +// {"windows-862", "cp862"}, // XEmacs +// {"windows-863", "cp863"}, // XEmacs +// {"windows-864", "cp864"}, // XEmacs +// {"windows-865", "cp865"}, // XEmacs +// {"windows-866", "cp866"}, // XEmacs +// {"windows-869", "cp869"}, // XEmacs +// {"windows-874", "cp874"}, // XEmacs +// {"windows-875", ""}, // not covered +// {"windows-932", "cp932"}, // *, XEmacs +// {"windows-936", "cp936"}, // XEmacs +// {"windows-949", "cp949"}, // *, XEmacs +// {"windows-950", "cp950"}, // *, XEmacs +// {"x-ctext", ""}, // -- +// {"x-ctext-with-extensions", ""}, // -- + + {NULL, NULL}, +}; + +// --------------------------------------------------------- +// Convert encoding name from emacs to mime. +// --------------------------------------------------------- +char * +emacs2mime(char *emacs_enc) +{ + int emacs_enc_len = strlen(emacs_enc); + if (emacs_enc_len > 4 + && !strcasecmp(emacs_enc + emacs_enc_len - 4, "-dos")) + emacs_enc[emacs_enc_len - 4] = 0; + if (emacs_enc_len > 4 + && !strcasecmp(emacs_enc + emacs_enc_len - 4, "-mac")) + emacs_enc[emacs_enc_len - 4] = 0; + if (emacs_enc_len > 5 + && !strcasecmp(emacs_enc + emacs_enc_len - 5, "-unix")) + emacs_enc[emacs_enc_len - 5] = 0; + for (const conversion *table = emacs_to_mime; table->from; table++) + if (!strcasecmp(emacs_enc, table->from)) + return (char *)table->to; + return emacs_enc; +} + +// --------------------------------------------------------- +// Print out Unicode entity if value is greater than 0x7F. +// --------------------------------------------------------- +inline void +unicode_entity(int u) +{ + if (u < 0x80) + putchar(u); + else { + // Handle no-break space and soft hyphen specially--they are input + // characters only, not glyphs. See groff_char(7). + if (u == 0xA0) { + putchar('\\'); + putchar('~'); + } + else if (u == 0xAD) { + putchar('\\'); + putchar('%'); + } + else + printf("\\[u%04X]", u); + } +} + +// --------------------------------------------------------- +// Conversion functions. All functions take 'data', which +// normally holds the first two lines, and a file pointer. +// --------------------------------------------------------- + +// Conversion from ISO-8859-1 (aka Latin-1) to Unicode. +void +conversion_latin1(FILE *fp, const string &data) +{ + int len = data.length(); + const unsigned char *ptr = (const unsigned char *)data.contents(); + for (int i = 0; i < len; i++) + unicode_entity(ptr[i]); + int c = -1; + while ((c = getc(fp)) != EOF) + unicode_entity(c); +} + +// A future version of groff shall support UTF-8 natively. +// In this case, the UTF-8 stuff here in this file will be +// moved to the troff program. + +struct utf8 { + FILE *fp; + unsigned char s[6]; + enum { + FIRST = 0, + SECOND, + THIRD, + FOURTH, + FIFTH, + SIXTH + } byte; + int expected_byte_count; + bool emit_invalid_utf8_warning; + bool emit_incomplete_utf8_warning; + utf8(FILE *); + ~utf8(); + void add(unsigned char); + void invalid(); + void incomplete(); +}; + +utf8::utf8(FILE *f) : fp(f), byte(FIRST), expected_byte_count(1), + emit_invalid_utf8_warning(true), + emit_incomplete_utf8_warning(true) +{ + // empty +} + +utf8::~utf8() +{ + if (byte != FIRST) + incomplete(); +} + +inline void +utf8::add(unsigned char c) +{ + s[byte] = c; + if (byte == FIRST) { + if (c < 0x80) + unicode_entity(c); + else if (c < 0xC0) + invalid(); + else if (c < 0xE0) { + expected_byte_count = 2; + byte = SECOND; + } + else if (c < 0xF0) { + expected_byte_count = 3; + byte = SECOND; + } + else if (c < 0xF8) { + expected_byte_count = 4; + byte = SECOND; + } + else if (c < 0xFC) { + expected_byte_count = 5; + byte = SECOND; + } + else if (c < 0xFE) { + expected_byte_count = 6; + byte = SECOND; + } + else + invalid(); + return; + } + if (c < 0x80 || c > 0xBF) { + incomplete(); + add(c); + return; + } + switch (byte) { + case FIRST: + // can't happen + break; + case SECOND: + if (expected_byte_count == 2) { + if (s[0] < 0xC2) + invalid(); + else + unicode_entity(((s[0] & 0x1F) << 6) + | (s[1] ^ 0x80)); + byte = FIRST; + } + else + byte = THIRD; + break; + case THIRD: + if (expected_byte_count == 3) { + if (!(s[0] >= 0xE1 || s[1] >= 0xA0)) + invalid(); + else + unicode_entity(((s[0] & 0x1F) << 12) + | ((s[1] ^ 0x80) << 6) + | (s[2] ^ 0x80)); + byte = FIRST; + } + else + byte = FOURTH; + break; + case FOURTH: + // We reject everything greater than 0x10FFFF. + if (expected_byte_count == 4) { + if (!((s[0] >= 0xF1 || s[1] >= 0x90) + && (s[0] < 0xF4 || (s[0] == 0xF4 && s[1] < 0x90)))) + invalid(); + else + unicode_entity(((s[0] & 0x07) << 18) + | ((s[1] ^ 0x80) << 12) + | ((s[2] ^ 0x80) << 6) + | (s[3] ^ 0x80)); + byte = FIRST; + } + else + byte = FIFTH; + break; + case FIFTH: + if (expected_byte_count == 5) { + invalid(); + byte = FIRST; + } + else + byte = SIXTH; + break; + case SIXTH: + invalid(); + byte = FIRST; + break; + } +} + +// We use fprintf(stderr) instead of libgroff's debug() because we need +// to output longs, and libgroff's errprint() doesn't support that. + +void +utf8::invalid() +{ + if (is_debugging && emit_invalid_utf8_warning) { + fprintf(stderr, " invalid UTF-8 sequence(s) in input stream:" + " replacing each such sequence with 0xFFFD\n"); + emit_invalid_utf8_warning = false; + } + unicode_entity(0xFFFD); + byte = FIRST; +} + +void +utf8::incomplete() +{ + if (is_debugging && emit_incomplete_utf8_warning) { + fprintf(stderr, " incomplete UTF-8 sequence(s) in input stream:" + " replacing each such sequence with 0xFFFD\n"); + emit_incomplete_utf8_warning = false; + } + unicode_entity(0xFFFD); + byte = FIRST; +} + +// Conversion from UTF-8 to Unicode. +void +conversion_utf8(FILE *fp, const string &data) +{ + utf8 u(fp); + int len = data.length(); + const unsigned char *ptr = (const unsigned char *)data.contents(); + for (int i = 0; i < len; i++) + u.add(ptr[i]); + int c = -1; + while ((c = getc(fp)) != EOF) + u.add(c); + return; +} + +// Conversion from cp1047 (EBCDIC) to UTF-8. +void +conversion_cp1047(FILE *fp, const string &data) +{ + static unsigned char cp1047[] = { + 0x00, 0x01, 0x02, 0x03, 0x9C, 0x09, 0x86, 0x7F, // 0x00 + 0x97, 0x8D, 0x8E, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x9D, 0x85, 0x08, 0x87, // 0x10 + 0x18, 0x19, 0x92, 0x8F, 0x1C, 0x1D, 0x1E, 0x1F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x0A, 0x17, 0x1B, // 0x20 + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x05, 0x06, 0x07, + 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, // 0x30 + 0x98, 0x99, 0x9A, 0x9B, 0x14, 0x15, 0x9E, 0x1A, + 0x20, 0xA0, 0xE2, 0xE4, 0xE0, 0xE1, 0xE3, 0xE5, // 0x40 + 0xE7, 0xF1, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, + 0x26, 0xE9, 0xEA, 0xEB, 0xE8, 0xED, 0xEE, 0xEF, // 0x50 + 0xEC, 0xDF, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E, + 0x2D, 0x2F, 0xC2, 0xC4, 0xC0, 0xC1, 0xC3, 0xC5, // 0x60 + 0xC7, 0xD1, 0xA6, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, + 0xF8, 0xC9, 0xCA, 0xCB, 0xC8, 0xCD, 0xCE, 0xCF, // 0x70 + 0xCC, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, + 0xD8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // 0x80 + 0x68, 0x69, 0xAB, 0xBB, 0xF0, 0xFD, 0xFE, 0xB1, + 0xB0, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, // 0x90 + 0x71, 0x72, 0xAA, 0xBA, 0xE6, 0xB8, 0xC6, 0xA4, + 0xB5, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 0xA0 + 0x79, 0x7A, 0xA1, 0xBF, 0xD0, 0x5B, 0xDE, 0xAE, + 0xAC, 0xA3, 0xA5, 0xB7, 0xA9, 0xA7, 0xB6, 0xBC, // 0xB0 + 0xBD, 0xBE, 0xDD, 0xA8, 0xAF, 0x5D, 0xB4, 0xD7, + 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // 0xC0 + 0x48, 0x49, 0xAD, 0xF4, 0xF6, 0xF2, 0xF3, 0xF5, + 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, // 0xD0 + 0x51, 0x52, 0xB9, 0xFB, 0xFC, 0xF9, 0xFA, 0xFF, + 0x5C, 0xF7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // 0xE0 + 0x59, 0x5A, 0xB2, 0xD4, 0xD6, 0xD2, 0xD3, 0xD5, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0xF0 + 0x38, 0x39, 0xB3, 0xDB, 0xDC, 0xD9, 0xDA, 0x9F, + }; + int len = data.length(); + const unsigned char *ptr = (const unsigned char *)data.contents(); + for (int i = 0; i < len; i++) + unicode_entity(cp1047[ptr[i]]); + int c = -1; + while ((c = getc(fp)) != EOF) + unicode_entity(cp1047[c]); +} + +// Locale-sensible conversion. +#if HAVE_ICONV +void +conversion_iconv(FILE *fp, const string &data, char *enc) +{ + iconv_t handle = iconv_open(UNICODE, enc); + if (handle == (iconv_t)-1) { + if (errno == EINVAL) { + error("encoding system '%1' not supported by iconv()", enc); + return; + } + fatal("iconv_open failed"); + } + char inbuf[BUFSIZ]; + int outbuf[BUFSIZ]; + char *outptr = (char *)outbuf; + size_t outbytes_left = BUFSIZ * sizeof (int); + // Handle 'data'. + char *inptr = (char *)data.contents(); + size_t inbytes_left = data.length(); + char *limit; + while (inbytes_left > 0) { + size_t status = iconv(handle, + (ICONV_CONST char **)&inptr, &inbytes_left, + &outptr, &outbytes_left); + if (status == (size_t)-1) { + if (errno == EILSEQ) { + // Invalid byte sequence. XXX + inptr++; + inbytes_left--; + } + else if (errno == E2BIG) { + // Output buffer is full. + limit = (char *)outbuf + BUFSIZ * sizeof (int) - outbytes_left; + for (int *ptr = outbuf; (char *)ptr < limit; ptr++) + unicode_entity(*ptr); + memmove(outbuf, outptr, outbytes_left); + outptr = (char *)outbuf + outbytes_left; + outbytes_left = BUFSIZ * sizeof (int) - outbytes_left; + } + else if (errno == EINVAL) { + // 'data' ends with partial input sequence. + memcpy(inbuf, inptr, inbytes_left); + break; + } + } + } + // Handle 'fp' and switch to 'inbuf'. + size_t read_bytes; + char *read_start = inbuf + inbytes_left; + while ((read_bytes = fread(read_start, 1, BUFSIZ - inbytes_left, fp)) > 0) { + inptr = inbuf; + inbytes_left += read_bytes; + while (inbytes_left > 0) { + size_t status = iconv(handle, + (ICONV_CONST char **)&inptr, &inbytes_left, + &outptr, &outbytes_left); + if (status == (size_t)-1) { + if (errno == EILSEQ) { + // Invalid byte sequence. XXX + inptr++; + inbytes_left--; + } + else if (errno == E2BIG) { + // Output buffer is full. + limit = (char *)outbuf + BUFSIZ * sizeof (int) - outbytes_left; + for (int *ptr = outbuf; (char *)ptr < limit; ptr++) + unicode_entity(*ptr); + memmove(outbuf, outptr, outbytes_left); + outptr = (char *)outbuf + outbytes_left; + outbytes_left = BUFSIZ * sizeof (int) - outbytes_left; + } + else if (errno == EINVAL) { + // 'inbuf' ends with partial input sequence. + memmove(inbuf, inptr, inbytes_left); + break; + } + } + } + read_start = inbuf + inbytes_left; + } + iconv_close(handle); + // XXX use ferror? + limit = (char *)outbuf + BUFSIZ * sizeof (int) - outbytes_left; + for (int *ptr = outbuf; (char *)ptr < limit; ptr++) + unicode_entity(*ptr); +} +#endif /* HAVE_ICONV */ + +// --------------------------------------------------------- +// Handle Byte Order Mark. +// +// Since we have a chicken-and-egg problem it's necessary +// to handle the BOM manually if it is in the data stream. +// As documented in the Unicode book it is very unlikely +// that any normal text file (regardless of the encoding) +// starts with the bytes which represent a BOM. +// +// Return the BOM in string 'BOM'; 'data' then starts with +// the byte after the BOM. This function reads (at most) +// four bytes from the data stream. +// +// Return encoding if a BOM is found, NULL otherwise. +// --------------------------------------------------------- +const char * +get_BOM(FILE *fp, string &BOM, string &data) +{ + // The BOM is U+FEFF. We have thus the following possible + // representations. + // + // UTF-8: 0xEFBBBF + // UTF-16: 0xFEFF or 0xFFFE + // UTF-32: 0x0000FEFF or 0xFFFE0000 + static struct { + int len; + const char *str; + const char *name; + } BOM_table[] = { + {4, "\x00\x00\xFE\xFF", "UTF-32"}, + {4, "\xFF\xFE\x00\x00", "UTF-32"}, + {3, "\xEF\xBB\xBF", "UTF-8"}, + {2, "\xFE\xFF", "UTF-16"}, + {2, "\xFF\xFE", "UTF-16"}, + }; + const int BOM_table_len = sizeof (BOM_table) / sizeof (BOM_table[0]); + char BOM_string[4]; + const char *retval = NULL; + int len; + for (len = 0; len < 4; len++) { + int c = getc(fp); + if (c == EOF) + break; + BOM_string[len] = char(c); + } + int i; + for (i = 0; i < BOM_table_len; i++) { + if (BOM_table[i].len <= len + && memcmp(BOM_string, BOM_table[i].str, BOM_table[i].len) == 0) + break; + } + int j = 0; + if (i < BOM_table_len) { + for (; j < BOM_table[i].len; j++) + BOM += BOM_string[j]; + retval = BOM_table[i].name; + } + for (; j < len; j++) + data += BOM_string[j]; + return retval; +} + +// --------------------------------------------------------- +// Get first two lines from input stream. +// +// Return string (allocated with 'new') without zero bytes +// or NULL in case no coding tag can occur in the data +// (which is stored unmodified in 'data'). +// --------------------------------------------------------- +char * +get_tag_lines(FILE *fp, string &data) +{ + int newline_count = 0; + int c, prev = -1; + // Handle CR, LF, and CRLF as line separators. + for (int i = 0; i < data.length(); i++) { + c = data[i]; + if (c == '\n' || c == '\r') + newline_count++; + if (c == '\n' && prev == '\r') + newline_count--; + prev = c; + } + if (newline_count > 1) + return NULL; + bool emit_warning = true; + for (int lines = newline_count; lines < 2; lines++) { + while ((c = getc(fp)) != EOF) { + if (c == '\0' && is_debugging && emit_warning) { + warning("null byte(s) found in input stream:" + " search for coding tag might return false result"); + emit_warning = false; + } + data += char(c); + if (c == '\n' || c == '\r') + break; + } + // Handle CR, LF, and CRLF as line separators. + if (c == '\r') { + c = getc(fp); + if (c != EOF && c != '\n') + ungetc(c, fp); + else + data += char(c); + } + } + return data.extract(); +} + +// --------------------------------------------------------- +// Check whether C string starts with a comment. +// +// Return 1 if true, 0 otherwise. +// --------------------------------------------------------- +int +is_comment_line(char *s) +{ + if (!s || !*s) + return 0; + if (*s == '.' || *s == '\'') + { + s++; + while (*s == ' ' || *s == '\t') + s++; + if (*s && *s == '\\') + { + s++; + if (*s == '"' || *s == '#') + return 1; + } + } + else if (*s == '\\') + { + s++; + if (*s == '#') + return 1; + } + return 0; +} + +// --------------------------------------------------------- +// Get a value/variable pair from a local variables list +// in a C string which look like this: +// +// <variable1>: <value1>; <variable2>: <value2>; ... +// +// Leading and trailing blanks are ignored. There might be +// more than one blank after ':' and ';'. +// +// Return position of next value/variable pair or NULL if +// at end of data. +// --------------------------------------------------------- +char * +get_variable_value_pair(char *d1, char **variable, char **value) +{ + static char var[MAX_VAR_LEN], val[MAX_VAR_LEN]; + *variable = var; + *value = val; + while (*d1 == ' ' || *d1 == '\t') + d1++; + // Get variable. + int l = 0; + while (l < MAX_VAR_LEN - 1 && *d1 && !strchr(";: \t", *d1)) + var[l++] = *(d1++); + var[l] = 0; + // Skip everything until ':', ';', or end of data. + while (*d1 && *d1 != ':' && *d1 != ';') + d1++; + val[0] = 0; + if (!*d1) + return NULL; + if (*d1 == ';') + return d1 + 1; + d1++; + while (*d1 == ' ' || *d1 == '\t') + d1++; + // Get value. + l = 0; + while (l < MAX_VAR_LEN - 1 && *d1 && !strchr("; \t", *d1)) + val[l++] = *(d1++); + val[l] = 0; + // Skip everything until ';' or end of data. + while (*d1 && *d1 != ';') + d1++; + if (*d1 == ';') + return d1 + 1; + return NULL; +} + +// --------------------------------------------------------- +// Check coding tag in the read buffer. +// +// We search for the following line: +// +// <comment> ... -*-<local variables list>-*- +// +// ('...' might be anything). +// +// <comment> can be one of the following syntax forms at the +// beginning of the line: +// +// .\" .\# '\" '\# \# +// +// There can be whitespace after the leading '.' or "'". +// +// The local variables list must occur within the first +// comment block at the very beginning of the data stream. +// +// Within the <local variables list>, we search for +// +// coding: <value> +// +// which specifies the coding system used for the data +// stream. +// +// Return <value> if found, NULL otherwise. +// +// Note that null bytes in the data are skipped before applying +// the algorithm. This should work even with files encoded as +// UTF-16 or UTF-32 (or its siblings) in most cases. +// --------------------------------------------------------- +char * +check_coding_tag(FILE *fp, string &data) +{ + char *inbuf = get_tag_lines(fp, data); + char *lineend; + for (char *p = inbuf; is_comment_line(p); p = lineend + 1) { + if ((lineend = strchr(p, '\n')) == NULL) + break; + *lineend = 0; // switch temporarily to '\0' + char *d1 = strstr(p, "-*-"); + char *d2 = 0; + if (d1) + d2 = strstr(d1 + 3, "-*-"); + *lineend = '\n'; // restore newline + if (!d1 || !d2) + continue; + *d2 = 0; // switch temporarily to '\0' + d1 += 3; + while (d1) { + char *variable, *value; + d1 = get_variable_value_pair(d1, &variable, &value); + if (!strcasecmp(variable, "coding")) { + *d2 = '-'; // restore '-' + free(inbuf); + return value; + } + } + *d2 = '-'; // restore '-' + } + free(inbuf); + return NULL; +} + +char * +detect_file_encoding(FILE *fp) +{ +#ifdef HAVE_UCHARDET + uchardet_t ud = NULL; + struct stat stat_buf; + size_t len, read_bytes; + char *data = NULL; + int res, current_position; + const char *charset; + char *ret = NULL; + + current_position = ftell(fp); + /* Due to BOM and tag detection, we are not at the beginning of the + file. */ + rewind(fp); + if (fstat(fileno(fp), &stat_buf) != 0) { + error("fstat: %1", strerror(errno)); + goto end; + } + len = stat_buf.st_size; + if (is_debugging) + fprintf(stderr, " len: %lu\n", (unsigned long)len); + if (len == 0) + goto end; + data = (char *)calloc(len, 1); + read_bytes = fread(data, 1, len, fp); + if (read_bytes == 0) { + error("fread: %1", strerror(errno)); + goto end; + } + /* We rewind back to the original position */ + if (fseek(fp, current_position, SEEK_SET) != 0) { + fatal("fseek: %1", strerror(errno)); + goto end; + } + ud = uchardet_new(); + res = uchardet_handle_data(ud, data, len); + if (res != 0) { + debug(" uchardet_handle_data: error %1\n", res); + goto end; + } + if (is_debugging) + fprintf(stderr, " uchardet read: %lu bytes\n", + (unsigned long)read_bytes); + uchardet_data_end(ud); + charset = uchardet_get_charset(ud); + if (is_debugging) { + if (charset) + fprintf(stderr, " charset: %s\n", charset); + else + fprintf(stderr, " charset is NULL\n"); + } + /* uchardet 0.0.1 could return an empty string instead of NULL */ + if (charset && *charset) { + ret = (char *)malloc(strlen(charset) + 1); + strcpy(ret, charset); + } + +end: + if (ud) + uchardet_delete(ud); + if (data) + free(data); + + return ret; +#else /* not HAVE_UCHARDET */ + return NULL; +#endif /* not HAVE_UCHARDET */ +} + +// --------------------------------------------------------- +// Handle an input file. If `filename` is "-", read the +// standard input stream. +// +// Return 1 on success, 0 otherwise. +// --------------------------------------------------------- +int +do_file(const char *filename) +{ + FILE *fp; + string BOM, data; + bool is_seekable = false; + string reported_filename; + + // TODO: Consider moving some of this into a `quoted_file_name` + // function in libgroff. + if (strcmp(filename, "-") == 0) { + fp = stdin; + reported_filename = string("<standard input>"); + } + else { + fp = fopen(filename, FOPEN_RB); + reported_filename = "'" + string(filename) + "'"; + } + if (!fp) { + error("can't open %1: %2", reported_filename.contents(), + strerror(errno)); + return 0; + } + if (is_debugging) + fprintf(stderr, "processing %s\n", reported_filename.contents()); + if (fseek(fp, 0L, SEEK_SET) == 0) + is_seekable = true; + else { + SET_BINARY(fileno(fp)); + if (is_debugging) + fprintf(stderr, " stream is not seekable: %s\n", + strerror(errno)); + } + const char *BOM_encoding = get_BOM(fp, BOM, data); + // Determine the encoding. + char *encoding; + int must_free_encoding = 0; + if (user_encoding[0]) { + if (is_debugging) { + fprintf(stderr, " user-specified encoding '%s', " + "no search for coding tag\n", + user_encoding); + if (BOM_encoding && strcmp(BOM_encoding, user_encoding)) + fprintf(stderr, " but BOM in data stream implies encoding '%s'!\n", + BOM_encoding); + } + encoding = (char *)user_encoding; + } + else if (BOM_encoding) { + if (is_debugging) + fprintf(stderr, " found BOM, no search for coding tag\n"); + encoding = (char *)BOM_encoding; + } + else { + // 'check_coding_tag' returns a pointer to a static array (or NULL). + char *file_encoding = check_coding_tag(fp, data); + if (!file_encoding) { + if (is_debugging) + fprintf(stderr, " no coding tag\n"); + if (is_seekable) + file_encoding = detect_file_encoding(fp); + if (!file_encoding) { + if (is_debugging) + fprintf(stderr, " could not detect encoding with uchardet\n"); + file_encoding = fallback_encoding; + } + else + must_free_encoding = 1; + } + else + if (is_debugging) + fprintf(stderr, " coding tag: '%s'\n", file_encoding); + encoding = file_encoding; + } + strncpy(encoding_string, encoding, MAX_VAR_LEN - 1); + encoding_string[MAX_VAR_LEN - 1] = 0; + if (must_free_encoding) + free(encoding); + encoding = encoding_string; + // Translate from MIME & Emacs encoding names to locale encoding names. + encoding = emacs2mime(encoding_string); + if (encoding[0] == '\0') { + error("encoding '%1' not supported, not a portable encoding", + encoding_string); + return 0; + } + if (is_debugging) + fprintf(stderr, " encoding used: '%s'\n", encoding); + if (!raw_flag) { + string fn(filename); + fn += '\0'; + normalize_for_lf(fn); + printf(".lf 1 %s\n", fn.contents()); + } + int success = 1; + // Call converter (converters write to stdout). + if (!strcasecmp(encoding, "ISO-8859-1")) + conversion_latin1(fp, BOM + data); + else if (!strcasecmp(encoding, "UTF-8")) + conversion_utf8(fp, data); + else if (!strcasecmp(encoding, "cp1047")) + conversion_cp1047(fp, BOM + data); + else { +#if HAVE_ICONV + conversion_iconv(fp, BOM + data, encoding); +#else + error("encoding system '%1' not supported", encoding); + success = 0; +#endif /* HAVE_ICONV */ + } + if (fp != stdin) + fclose(fp); + return success; +} + +// --------------------------------------------------------- +// Print usage. +// --------------------------------------------------------- +void +usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-dr] [-D fallback-encoding] [-e encoding] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s {-h | --help}\n", + program_name, program_name, program_name); + if (stdout == stream) { + fprintf(stream, +"\n" +"Read each file, convert its encoded characters to a form GNU" +" troff(1)\n" +"can interpret, and send the result to the standard output stream.\n" +"The default fallback encoding is '%s'. See the preconv(1) manual" +" page.\n", + fallback_encoding); + exit(EXIT_SUCCESS); + } +} + +// --------------------------------------------------------- +// Main routine. +// --------------------------------------------------------- +int +main(int argc, char **argv) +{ + program_name = argv[0]; + // Determine the fallback encoding. This must be done before + // getopt() is called since the usage message shows the fallback + // encoding. + setlocale(LC_ALL, ""); + char *locale = getlocale(LC_CTYPE); + if (!locale || !strcmp(locale, "C") || !strcmp(locale, "POSIX")) + strcpy(fallback_encoding, "latin1"); + else { + strncpy(fallback_encoding, locale_charset(), MAX_VAR_LEN - 1); + fallback_encoding[MAX_VAR_LEN - 1] = 0; + } + + program_name = argv[0]; + int opt; + static const struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + // Parse the command-line options. + while ((opt = getopt_long(argc, argv, + "dD:e:hrv", long_options, NULL)) != EOF) + switch (opt) { + case 'v': + printf("GNU preconv (groff) version %s %s iconv support and %s uchardet support\n", + Version_string, +#ifdef HAVE_ICONV + "with", +#else + "without", +#endif /* HAVE_ICONV */ +#ifdef HAVE_UCHARDET + "with" +#else + "without" +#endif /* HAVE_UCHARDET */ + ); + exit(0); + break; + case 'd': + is_debugging = true; + break; + case 'e': + if (optarg) { + strncpy(user_encoding, optarg, MAX_VAR_LEN - 1); + user_encoding[MAX_VAR_LEN - 1] = 0; + } + else + user_encoding[0] = 0; + break; + case 'D': + if (optarg) { + strncpy(fallback_encoding, optarg, MAX_VAR_LEN - 1); + fallback_encoding[MAX_VAR_LEN - 1] = 0; + } + break; + case 'r': + raw_flag = 1; + break; + case 'h': + usage(stdout); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + int nbad = 0; + if (is_debugging) + fprintf(stderr, "fallback encoding: '%s'\n", fallback_encoding); + if (optind >= argc) + nbad += !do_file("-"); + else + for (int i = optind; i < argc; i++) + nbad += !do_file(argv[i]); + if (ferror(stdout) || fflush(stdout) < 0) + fatal("output error"); + return nbad != 0; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/preconv/tests/do-not-seek-the-unseekable.sh b/src/preproc/preconv/tests/do-not-seek-the-unseekable.sh new file mode 100755 index 0000000..2b1142d --- /dev/null +++ b/src/preproc/preconv/tests/do-not-seek-the-unseekable.sh @@ -0,0 +1,59 @@ +#!/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/>. +# + +set -e + +preconv="${abs_top_builddir:-.}/preconv" + +fail= + +wail () { + echo FAILED >&2 + fail=YES +} + +# Scrape debugging output to see if we're skipping unseekable streams. +# This is fragile, but we don't want to lock the language of diagnostic +# messages (especially debugging ones). If this test fails, check the +# text of the command's debugging output for a mismatch before +# investigating deeper problems. + +echo "testing seekability of file operand '-'" >&2 +output=$(printf '' | "$preconv" -d - 2>&1) +echo "$output" | grep -q "stream is not seekable" || wail + +# /dev/stdin might not exist in a chroot. Or, if it's not (a symbolic +# link to) a character special device, the next test will not be valid, +# as when using GNU Make's `-j` option. +# +# Similarly, we must have a controlling terminal. +test -z "$fail" +echo "skipping if /dev/stdin is not a character device" >&2 +test -c /dev/stdin || exit 77 # skip +echo "skipping if there is no controlling terminal" >&2 +test "$(tty)" != "not a tty" || exit 77 # skip + +echo "testing seekability of standard input stream" >&2 +output=$(printf '' | "$preconv" -d /dev/stdin 2>&1) +echo "$output" | grep -q "stream is not seekable" || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/preconv/tests/smoke-test.sh b/src/preproc/preconv/tests/smoke-test.sh new file mode 100755 index 0000000..4131416 --- /dev/null +++ b/src/preproc/preconv/tests/smoke-test.sh @@ -0,0 +1,88 @@ +#!/bin/sh +# +# Copyright (C) 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/>. +# + +# Ensure a predictable character encoding. +export LC_ALL=C + +set -e + +preconv="${abs_top_builddir:-.}/preconv" + +echo "testing -e flag override of BOM detection" >&2 +printf '\376\377\0\100\0\n' \ + | "$preconv" -d -e euc-kr 2>&1 > /dev/null \ + | grep -q "no search for coding tag" + +echo "testing detection of UTF-32BE BOM" >&2 +printf '\0\0\376\377\0\0\0\100\0\0\0\n' \ + | "$preconv" -d 2>&1 > /dev/null \ + | grep -q "found BOM" + +echo "testing detection of UTF-32LE BOM" >&2 +printf '\377\376\0\0\100\0\0\0\n\0\0\0' \ + | "$preconv" -d 2>&1 > /dev/null \ + | grep -q "found BOM" + +echo "testing detection of UTF-16BE BOM" >&2 +printf '\376\377\0\100\0\n' \ + | "$preconv" -d 2>&1 > /dev/null \ + | grep -q "found BOM" + +echo "testing detection of UTF-16LE BOM" >&2 +printf '\377\376\100\0\n\0' \ + | "$preconv" -d 2>&1 > /dev/null \ + | grep -q "found BOM" + +echo "testing detection of UTF-8 BOM" >&2 +printf '\357\273\277@\n' \ + | "$preconv" -d 2>&1 > /dev/null \ + | grep -q "found BOM" + +# We do not find a coding tag on piped input because it isn't seekable. +echo "testing detection of Emacs coding tag in piped input" >&2 +printf '.\\" -*- coding: euc-kr; -*-\\n' \ + | "$preconv" -d 2>&1 >/dev/null \ + | grep -q "no coding tag" + +# We need uchardet to work to get past this point. +echo "testing uchardet detection of encoding" >&2 +"$preconv" -v | grep -q 'with uchardet support' || exit 77 + +# Instead of using temporary files, which in all fastidiousness means +# cleaning them up even if we're interrupted, which in turn means +# setting up signal handlers, we use files in the build tree. + +doc=contrib/mm/groff_mmse.7 +echo "testing uchardet detection on Latin-1 document $doc" >&2 +"$preconv" -d -D us-ascii 2>&1 >/dev/null $doc \ + | grep -q 'charset: ISO-8859-1' + +# uchardet can't seek on a pipe either. +echo "testing uchardet detection on pipe (expect fallback to -D)" >&2 +printf 'Eat at the caf\351.\n' \ + | "$preconv" -d -D euc-kr 2>&1 > /dev/null \ + | grep -q "encoding used: 'EUC-KR'" + +# Fall back to the locale. preconv assumes Latin-1 for C instead of +# US-ASCII. +echo "testing fallback to locale setting in environment" >&2 +printf 'Eat at the caf\351.\n' \ + | "$preconv" -d 2>&1 > /dev/null \ + | grep -q "encoding used: 'ISO-8859-1'" diff --git a/src/preproc/refer/TODO b/src/preproc/refer/TODO new file mode 100644 index 0000000..36f4508 --- /dev/null +++ b/src/preproc/refer/TODO @@ -0,0 +1,124 @@ +inline references + +Some sort of macro/subroutine that can cover several references. + +move-punctuation should ignore multiple punctuation characters. + +Make the index files machine independent. + +Allow search keys to be negated (with !) to indicate that the +reference should not contain the key. Ignore negated keys during +indexed searching. + +Provide an option with lkbib and lookbib that prints the location +(filename, position) of each reference. Need to map filename_id's +back to filenames. + +Rename join-authors to join-fields. Have a separate label-join-fields +command used by @ and #. + +Have some sort of quantifier: e.g., $.n#A means execute '$.n' for each +instance of an A field, setting $ to that field, and then join the +results using the join-authors command. + +no-text-in-bracket command which says not to allow post_text and +pre_text when the [] flags has been given. Useful for superscripted +footnotes. + +Make it possible to translate - to \(en in page ranges. + +Trim eign a bit. + +In indexed searching discard all numeric keys except dates. + +Allow '\ ' to separate article from first word. + +%also + +Option automatically to supply [] flags in every reference. + +See if we can avoid requiring a comma before jr. and so on +in find_last_name(). + +Cache sortified authors in authors string during tentative evaluation of +label specification. + +Possibly don't allow * and % expressions in the first part of ?:, | or +& expressions. + +Handle better the case where <> occurs inside functions and in the +first operand of ~. Or perhaps implement <> using some magic character +in the string. + +Should special treatment be given to lines beginning with . in +references? (Unix refer seems to treat them like '%'). + +Add global flag to control whether all files should be stat-ed after +loading, and whether they should be stat-ed before each search. +Perhaps make this dependent on the number of files there are. + +Option to truncate keys to truncate_len in linear searching. + +Allow multiple -f options in indxbib. + +In indxbib, possibly store common words rather than common words +filename. In this case store only words that are actually present in +the file. + +Perhaps we should put out an obnoxious copyright message when lookbib +starts up. + +Provide an option that writes a file containing just the references +actually used. Useful if you want to distribute a document. + +Have a magic token such that +%A <sort stuff><magic token><print stuff> +will print as though it were +%A <print stuff> +but sort as though it were +%A <sort stuff> +Do we need this if we can specify author alternatives for sorting? +No, provided we have separate alternatives for @. + +In consider_authors when last names are ambiguous we might be able to +use just the first name and not Jr. bit. Or we might be able to +abbreviate the author. + +It ought to be possible to specify an alternative field to sort on +instead of date. (ie if there's a field giving the type of document -- +these references should sort after any years) + +Provide a way to execute a command using a command-line option. + +Option to set the label-spec as a command-line option (-L). + +Command to specify which fields can occur multiple times: +multiple AE + +Command to specify how various fields sort: +aort-as-name A +sort-as-date D +sort-as-title T +sort-as-other O + +Command to specify which fields are author fields: +# if we don't have A use field Q +author-fields AQ + +Commands to set properties of tokens. +sortify-token \(ae ae +uppercase-token \[ae] \[AE] + +Command to set the names of months: +months january february march april may ... + +Perhaps provide some sort of macro capability: +# perhaps a macro capability +defmacro foo +annotation-field $1 +endef + +Command to control strings used in capitalization +capitalize-start \s+2 +capitalize-end \s-2 +(perhaps make these arguments to the capitalize command.) diff --git a/src/preproc/refer/command.cpp b/src/preproc/refer/command.cpp new file mode 100644 index 0000000..b49e2be --- /dev/null +++ b/src/preproc/refer/command.cpp @@ -0,0 +1,814 @@ +/* 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 "refer.h" +#include "refid.h" +#include "search.h" +#include "command.h" + +cset cs_field_name = csalpha; + +class input_item { + input_item *next; + char *filename; + int first_lineno; + string buffer; + const char *ptr; + const char *end; +public: + input_item(string &, const char *, int = 1); + ~input_item(); + int get_char(); + int peek_char(); + void skip_char(); + void get_location(const char **, int *); + + friend class input_stack; +}; + +input_item::input_item(string &s, const char *fn, int ln) +: filename(strsave(fn)), first_lineno(ln) +{ + buffer.move(s); + ptr = buffer.contents(); + end = ptr + buffer.length(); +} + +input_item::~input_item() +{ + delete[] filename; +} + +inline int input_item::peek_char() +{ + if (ptr >= end) + return EOF; + else + return (unsigned char)*ptr; +} + +inline int input_item::get_char() +{ + if (ptr >= end) + return EOF; + else + return (unsigned char)*ptr++; +} + +inline void input_item::skip_char() +{ + ptr++; +} + +void input_item::get_location(const char **filenamep, int *linenop) +{ + *filenamep = filename; + if (ptr == buffer.contents()) + *linenop = first_lineno; + else { + int ln = first_lineno; + const char *e = ptr - 1; + for (const char *p = buffer.contents(); p < e; p++) + if (*p == '\n') + ln++; + ln--; // Back up to identify line number _before_ last seen newline. + *linenop = ln; + } + return; +} + +class input_stack { + static input_item *top; +public: + static void init(); + static int get_char(); + static int peek_char(); + static void skip_char() { top->skip_char(); } + static void push_file(const char *); + static void push_string(string &, const char *, int); + static void error(const char *format, + const errarg &arg1 = empty_errarg, + const errarg &arg2 = empty_errarg, + const errarg &arg3 = empty_errarg); +}; + +input_item *input_stack::top = 0; + +void input_stack::init() +{ + while (top) { + input_item *tem = top; + top = top->next; + delete tem; + } +} + +int input_stack::get_char() +{ + while (top) { + int c = top->get_char(); + if (c >= 0) + return c; + input_item *tem = top; + top = top->next; + delete tem; + } + return -1; +} + +int input_stack::peek_char() +{ + while (top) { + int c = top->peek_char(); + if (c >= 0) + return c; + input_item *tem = top; + top = top->next; + delete tem; + } + return -1; +} + +void input_stack::push_file(const char *fn) +{ + FILE *fp; + if (strcmp(fn, "-") == 0) { + fp = stdin; + fn = "<standard input>"; + } + else { + errno = 0; + fp = fopen(fn, "r"); + if (fp == 0) { + error("can't open '%1': %2", fn, strerror(errno)); + return; + } + } + string buf; + bool is_at_beginning_of_line = true; + int lineno = 1; + for (;;) { + int c = getc(fp); + if (is_at_beginning_of_line && c == '.') { + // replace lines beginning with .R1 or .R2 with a blank line + c = getc(fp); + if (c == 'R') { + c = getc(fp); + if (c == '1' || c == '2') { + int cc = c; + c = getc(fp); + if (compatible_flag || c == ' ' || c == '\n' || c == EOF) { + while (c != '\n' && c != EOF) + c = getc(fp); + } + else { + buf += '.'; + buf += 'R'; + buf += cc; + } + } + else { + buf += '.'; + buf += 'R'; + } + } + else + buf += '.'; + } + if (c == EOF) + break; + if (is_invalid_input_char(c)) + error_with_file_and_line(fn, lineno, + "invalid input character code %1", c); + else { + buf += c; + if (c == '\n') { + is_at_beginning_of_line = true; + lineno++; + } + else + is_at_beginning_of_line = false; + } + } + if (fp != stdin) + fclose(fp); + if (buf.length() > 0 && buf[buf.length() - 1] != '\n') + buf += '\n'; + input_item *it = new input_item(buf, fn); + it->next = top; + top = it; +} + +void input_stack::push_string(string &s, const char *filename, int lineno) +{ + input_item *it = new input_item(s, filename, lineno); + it->next = top; + top = it; +} + +void input_stack::error(const char *format, const errarg &arg1, + const errarg &arg2, const errarg &arg3) +{ + const char *filename; + int lineno; + for (input_item *it = top; it; it = it->next) { + it->get_location(&filename, &lineno); + error_with_file_and_line(filename, lineno, format, arg1, arg2, arg3); + return; + } + ::error(format, arg1, arg2, arg3); +} + +void command_error(const char *format, const errarg &arg1, + const errarg &arg2, const errarg &arg3) +{ + input_stack::error(format, arg1, arg2, arg3); +} + +// # not recognized in "" +// \<newline> is recognized in "" +// # does not conceal newline +// if missing closing quote, word extends to end of line +// no special treatment of \ other than before newline +// \<newline> not recognized after # +// ; allowed as alternative to newline +// ; not recognized in "" +// don't clear word_buffer; just append on +// return -1 for EOF, 0 for newline, 1 for word + +int get_word(string &word_buffer) +{ + int c = input_stack::get_char(); + for (;;) { + if (c == '#') { + do { + c = input_stack::get_char(); + } while (c != '\n' && c != EOF); + break; + } + if (c == '\\' && input_stack::peek_char() == '\n') + input_stack::skip_char(); + else if (c != ' ' && c != '\t') + break; + c = input_stack::get_char(); + } + if (c == EOF) + return -1; + if (c == '\n' || c == ';') + return 0; + if (c == '"') { + for (;;) { + c = input_stack::peek_char(); + if (c == EOF || c == '\n') + break; + input_stack::skip_char(); + if (c == '"') { + int d = input_stack::peek_char(); + if (d == '"') + input_stack::skip_char(); + else + break; + } + else if (c == '\\') { + int d = input_stack::peek_char(); + if (d == '\n') + input_stack::skip_char(); + else + word_buffer += '\\'; + } + else + word_buffer += c; + } + return 1; + } + word_buffer += c; + for (;;) { + c = input_stack::peek_char(); + if (c == ' ' || c == '\t' || c == '\n' || c == '#' || c == ';') + break; + input_stack::skip_char(); + if (c == '\\') { + int d = input_stack::peek_char(); + if (d == '\n') + input_stack::skip_char(); + else + word_buffer += '\\'; + } + else + word_buffer += c; + } + return 1; +} + +union argument { + const char *s; + int n; +}; + +// This is for debugging. + +static void echo_command(int argc, argument *argv) +{ + for (int i = 0; i < argc; i++) + fprintf(stderr, "%s\n", argv[i].s); +} + +static void include_command(int argc, argument *argv) +{ + assert(argc == 1); + input_stack::push_file(argv[0].s); +} + +static void capitalize_command(int argc, argument *argv) +{ + if (argc > 0) + capitalize_fields = argv[0].s; + else + capitalize_fields.clear(); +} + +static void accumulate_command(int, argument *) +{ + accumulate = 1; +} + +static void no_accumulate_command(int, argument *) +{ + accumulate = 0; +} + +static void move_punctuation_command(int, argument *) +{ + move_punctuation = 1; +} + +static void no_move_punctuation_command(int, argument *) +{ + move_punctuation = 0; +} + +static void sort_command(int argc, argument *argv) +{ + if (argc == 0) + sort_fields = "AD"; + else + sort_fields = argv[0].s; + accumulate = 1; +} + +static void no_sort_command(int, argument *) +{ + sort_fields.clear(); +} + +static void articles_command(int argc, argument *argv) +{ + articles.clear(); + int i; + for (i = 0; i < argc; i++) { + articles += argv[i].s; + articles += '\0'; + } + int len = articles.length(); + for (i = 0; i < len; i++) + articles[i] = cmlower(articles[i]); +} + +static void database_command(int argc, argument *argv) +{ + for (int i = 0; i < argc; i++) + database_list.add_file(argv[i].s); +} + +static void default_database_command(int, argument *) +{ + search_default = 1; +} + +static void no_default_database_command(int, argument *) +{ + search_default = 0; +} + +static void bibliography_command(int argc, argument *argv) +{ + have_bibliography = 1; + const char *saved_filename = current_filename; + int saved_lineno = current_lineno; + int saved_label_in_text = label_in_text; + label_in_text = 0; + if (!accumulate) + fputs(".]<\n", stdout); + for (int i = 0; i < argc; i++) + do_bib(argv[i].s); + if (accumulate) + output_references(); + else + fputs(".]>\n", stdout); + current_filename = saved_filename; + current_lineno = saved_lineno; + label_in_text = saved_label_in_text; +} + +static void annotate_command(int argc, argument *argv) +{ + if (argc > 0) + annotation_field = argv[0].s[0]; + else + annotation_field = 'X'; + if (argc == 2) + annotation_macro = argv[1].s; + else + annotation_macro = "AP"; +} + +static void no_annotate_command(int, argument *) +{ + annotation_macro.clear(); + annotation_field = -1; +} + +static void reverse_command(int, argument *argv) +{ + reverse_fields = argv[0].s; +} + +static void no_reverse_command(int, argument *) +{ + reverse_fields.clear(); +} + +static void abbreviate_command(int argc, argument *argv) +{ + abbreviate_fields = argv[0].s; + period_before_initial = argc > 1 ? argv[1].s : ". "; + period_before_last_name = argc > 2 ? argv[2].s : ". "; + period_before_other = argc > 3 ? argv[3].s : ". "; + period_before_hyphen = argc > 4 ? argv[4].s : "."; +} + +static void no_abbreviate_command(int, argument *) +{ + abbreviate_fields.clear(); +} + +string search_ignore_fields; + +static void search_ignore_command(int argc, argument *argv) +{ + if (argc > 0) + search_ignore_fields = argv[0].s; + else + search_ignore_fields = "XYZ"; + search_ignore_fields += '\0'; + linear_ignore_fields = search_ignore_fields.contents(); +} + +static void no_search_ignore_command(int, argument *) +{ + linear_ignore_fields = ""; +} + +static void search_truncate_command(int argc, argument *argv) +{ + if (argc > 0) + linear_truncate_len = argv[0].n; + else + linear_truncate_len = 6; +} + +static void no_search_truncate_command(int, argument *) +{ + linear_truncate_len = -1; +} + +static void discard_command(int argc, argument *argv) +{ + if (argc == 0) + discard_fields = "XYZ"; + else + discard_fields = argv[0].s; + accumulate = 1; +} + +static void no_discard_command(int, argument *) +{ + discard_fields.clear(); +} + +static void label_command(int, argument *argv) +{ + set_label_spec(argv[0].s); +} + +static void abbreviate_label_ranges_command(int argc, argument *argv) +{ + abbreviate_label_ranges = 1; + label_range_indicator = argc > 0 ? argv[0].s : "-"; +} + +static void no_abbreviate_label_ranges_command(int, argument *) +{ + abbreviate_label_ranges = 0; +} + +static void label_in_reference_command(int, argument *) +{ + label_in_reference = 1; +} + +static void no_label_in_reference_command(int, argument *) +{ + label_in_reference = 0; +} + +static void label_in_text_command(int, argument *) +{ + label_in_text = 1; +} + +static void no_label_in_text_command(int, argument *) +{ + label_in_text = 0; +} + +static void sort_adjacent_labels_command(int, argument *) +{ + sort_adjacent_labels = 1; +} + +static void no_sort_adjacent_labels_command(int, argument *) +{ + sort_adjacent_labels = 0; +} + +static void date_as_label_command(int argc, argument *argv) +{ + if (set_date_label_spec(argc > 0 ? argv[0].s : "D%a*")) + date_as_label = 1; +} + +static void no_date_as_label_command(int, argument *) +{ + date_as_label = 0; +} + +static void short_label_command(int, argument *argv) +{ + if (set_short_label_spec(argv[0].s)) + short_label_flag = 1; +} + +static void no_short_label_command(int, argument *) +{ + short_label_flag = 0; +} + +static void compatible_command(int, argument *) +{ + compatible_flag = 1; +} + +static void no_compatible_command(int, argument *) +{ + compatible_flag = 0; +} + +static void join_authors_command(int argc, argument *argv) +{ + join_authors_exactly_two = argv[0].s; + join_authors_default = argc > 1 ? argv[1].s : argv[0].s; + join_authors_last_two = argc == 3 ? argv[2].s : argv[0].s; +} + +static void bracket_label_command(int, argument *argv) +{ + pre_label = argv[0].s; + post_label = argv[1].s; + sep_label = argv[2].s; +} + +static void separate_label_second_parts_command(int, argument *argv) +{ + separate_label_second_parts = argv[0].s; +} + +static void et_al_command(int argc, argument *argv) +{ + et_al = argv[0].s; + et_al_min_elide = argv[1].n; + if (et_al_min_elide < 1) + et_al_min_elide = 1; + et_al_min_total = argc >= 3 ? argv[2].n : 0; +} + +static void no_et_al_command(int, argument *) +{ + et_al.clear(); + et_al_min_elide = 0; +} + +typedef void (*command_t)(int, argument *); + +/* arg_types is a string describing the numbers and types of arguments. +s means a string, i means an integer, f is a list of fields, F is +a single field, +? means that the previous argument is optional, * means that the +previous argument can occur any number of times. */ + +struct S { + const char *name; + command_t func; + const char *arg_types; +} command_table[] = { + { "include", include_command, "s" }, + { "echo", echo_command, "s*" }, + { "capitalize", capitalize_command, "f?" }, + { "accumulate", accumulate_command, "" }, + { "no-accumulate", no_accumulate_command, "" }, + { "move-punctuation", move_punctuation_command, "" }, + { "no-move-punctuation", no_move_punctuation_command, "" }, + { "sort", sort_command, "s?" }, + { "no-sort", no_sort_command, "" }, + { "articles", articles_command, "s*" }, + { "database", database_command, "ss*" }, + { "default-database", default_database_command, "" }, + { "no-default-database", no_default_database_command, "" }, + { "bibliography", bibliography_command, "ss*" }, + { "annotate", annotate_command, "F?s?" }, + { "no-annotate", no_annotate_command, "" }, + { "reverse", reverse_command, "s" }, + { "no-reverse", no_reverse_command, "" }, + { "abbreviate", abbreviate_command, "ss?s?s?s?" }, + { "no-abbreviate", no_abbreviate_command, "" }, + { "search-ignore", search_ignore_command, "f?" }, + { "no-search-ignore", no_search_ignore_command, "" }, + { "search-truncate", search_truncate_command, "i?" }, + { "no-search-truncate", no_search_truncate_command, "" }, + { "discard", discard_command, "f?" }, + { "no-discard", no_discard_command, "" }, + { "label", label_command, "s" }, + { "abbreviate-label-ranges", abbreviate_label_ranges_command, "s?" }, + { "no-abbreviate-label-ranges", no_abbreviate_label_ranges_command, "" }, + { "label-in-reference", label_in_reference_command, "" }, + { "no-label-in-reference", no_label_in_reference_command, "" }, + { "label-in-text", label_in_text_command, "" }, + { "no-label-in-text", no_label_in_text_command, "" }, + { "sort-adjacent-labels", sort_adjacent_labels_command, "" }, + { "no-sort-adjacent-labels", no_sort_adjacent_labels_command, "" }, + { "date-as-label", date_as_label_command, "s?" }, + { "no-date-as-label", no_date_as_label_command, "" }, + { "short-label", short_label_command, "s" }, + { "no-short-label", no_short_label_command, "" }, + { "compatible", compatible_command, "" }, + { "no-compatible", no_compatible_command, "" }, + { "join-authors", join_authors_command, "sss?" }, + { "bracket-label", bracket_label_command, "sss" }, + { "separate-label-second-parts", separate_label_second_parts_command, "s" }, + { "et-al", et_al_command, "sii?" }, + { "no-et-al", no_et_al_command, "" }, +}; + +static int check_args(const char *types, const char *name, + int argc, argument *argv) +{ + int argno = 0; + while (*types) { + if (argc == 0) { + if (types[1] == '?') + break; + else if (types[1] == '*') { + assert(types[2] == '\0'); + break; + } + else { + input_stack::error("missing argument for command '%1'", name); + return 0; + } + } + switch (*types) { + case 's': + break; + case 'i': + { + char *ptr; + long n = strtol(argv->s, &ptr, 10); + if ((n == 0 && ptr == argv->s) + || *ptr != '\0') { + input_stack::error("argument %1 for command '%2' must be an integer", + argno + 1, name); + return 0; + } + argv->n = (int)n; + break; + } + case 'f': + { + for (const char *ptr = argv->s; *ptr != '\0'; ptr++) + if (!cs_field_name(*ptr)) { + input_stack::error("argument %1 for command '%2' must be a list of fields", + argno + 1, name); + return 0; + } + break; + } + case 'F': + if (argv->s[0] == '\0' || argv->s[1] != '\0' + || !cs_field_name(argv->s[0])) { + input_stack::error("argument %1 for command '%2' must be a field name", + argno + 1, name); + return 0; + } + break; + default: + assert(0); + } + if (types[1] == '?') + types += 2; + else if (types[1] != '*') + types += 1; + --argc; + ++argv; + ++argno; + } + if (argc > 0) { + input_stack::error("too many arguments for command '%1'", name); + return 0; + } + return 1; +} + +static void execute_command(const char *name, int argc, argument *argv) +{ + for (unsigned int i = 0; + i < sizeof(command_table)/sizeof(command_table[0]); i++) + if (strcmp(name, command_table[i].name) == 0) { + if (check_args(command_table[i].arg_types, name, argc, argv)) + (*command_table[i].func)(argc, argv); + return; + } + input_stack::error("unknown command '%1'", name); +} + +static void command_loop() +{ + string command; + for (;;) { + command.clear(); + int res = get_word(command); + if (res != 1) { + if (res == 0) + continue; + break; + } + int argc = 0; + command += '\0'; + while ((res = get_word(command)) == 1) { + argc++; + command += '\0'; + } + argument *argv = new argument[argc]; + const char *ptr = command.contents(); + for (int i = 0; i < argc; i++) + argv[i].s = ptr = strchr(ptr, '\0') + 1; + execute_command(command.contents(), argc, argv); + delete[] argv; + if (res == -1) + break; + } +} + +void process_commands(string &s, const char *file, int lineno) +{ + const char *saved_filename = current_filename; + int saved_lineno = current_lineno; + input_stack::init(); + current_filename = file; + // Report diagnostics with respect to line _before_ last newline seen. + current_lineno = lineno - 1; + input_stack::push_string(s, file, lineno); + command_loop(); + current_filename = saved_filename; + current_lineno = saved_lineno; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/refer/command.h b/src/preproc/refer/command.h new file mode 100644 index 0000000..db850f4 --- /dev/null +++ b/src/preproc/refer/command.h @@ -0,0 +1,35 @@ +// -*- 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/>. */ + +void process_commands(string &s, const char *file, int lineno); + +extern int have_bibliography; +extern int accumulate; +extern int move_punctuation; +extern int search_default; +extern search_list database_list; +extern int label_in_text; +extern int label_in_reference; +extern int sort_adjacent_labels; +extern string pre_label; +extern string post_label; +extern string sep_label; + +extern void do_bib(const char *); +extern void output_references(); diff --git a/src/preproc/refer/label.cpp b/src/preproc/refer/label.cpp new file mode 100644 index 0000000..f8c1645 --- /dev/null +++ b/src/preproc/refer/label.cpp @@ -0,0 +1,2607 @@ +/* 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 19 "../src/preproc/refer/label.ypp" + + +#include "refer.h" +#include "refid.h" +#include "ref.h" +#include "token.h" + +int yylex(); +void yyerror(const char *); + +static const char *format_serial(char c, int n); + +struct label_info { + int start; + int length; + int count; + int total; + label_info(const string &); +}; + +label_info *lookup_label(const string &label); + +struct expression { + enum { + // Does the tentative label depend on the reference? + CONTAINS_VARIABLE = 01, + CONTAINS_STAR = 02, + CONTAINS_FORMAT = 04, + CONTAINS_AT = 010 + }; + virtual ~expression() { } + virtual void evaluate(int, const reference &, string &, + substring_position &) = 0; + virtual unsigned analyze() { return 0; } +}; + +class at_expr : public expression { +public: + at_expr() { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return CONTAINS_VARIABLE|CONTAINS_AT; } +}; + +class format_expr : public expression { + char type; + int width; + int first_number; +public: + format_expr(char c, int w = 0, int f = 1) + : type(c), width(w), first_number(f) { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return CONTAINS_FORMAT; } +}; + +class field_expr : public expression { + int number; + char name; +public: + field_expr(char nm, int num) : number(num), name(nm) { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return CONTAINS_VARIABLE; } +}; + +class literal_expr : public expression { + string s; +public: + literal_expr(const char *ptr, int len) : s(ptr, len) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class unary_expr : public expression { +protected: + expression *expr; +public: + unary_expr(expression *e) : expr(e) { } + ~unary_expr() { delete expr; } + void evaluate(int, const reference &, string &, substring_position &) = 0; + unsigned analyze() { return expr ? expr->analyze() : 0; } +}; + +// This caches the analysis of an expression. + +class analyzed_expr : public unary_expr { + unsigned flags; +public: + analyzed_expr(expression *); + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return flags; } +}; + +class star_expr : public unary_expr { +public: + star_expr(expression *e) : unary_expr(e) { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { + return ((expr ? (expr->analyze() & ~CONTAINS_VARIABLE) : 0) + | CONTAINS_STAR); + } +}; + +typedef void map_func(const char *, const char *, string &); + +class map_expr : public unary_expr { + map_func *func; +public: + map_expr(expression *e, map_func *f) : unary_expr(e), func(f) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +typedef const char *extractor_func(const char *, const char *, const char **); + +class extractor_expr : public unary_expr { + int part; + extractor_func *func; +public: + enum { BEFORE = +1, MATCH = 0, AFTER = -1 }; + extractor_expr(expression *e, extractor_func *f, int pt) + : unary_expr(e), part(pt), func(f) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class truncate_expr : public unary_expr { + int n; +public: + truncate_expr(expression *e, int i) : unary_expr(e), n(i) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class separator_expr : public unary_expr { +public: + separator_expr(expression *e) : unary_expr(e) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class binary_expr : public expression { +protected: + expression *expr1; + expression *expr2; +public: + binary_expr(expression *e1, expression *e2) : expr1(e1), expr2(e2) { } + ~binary_expr() { delete expr1; delete expr2; } + void evaluate(int, const reference &, string &, substring_position &) = 0; + unsigned analyze() { + return (expr1 ? expr1->analyze() : 0) | (expr2 ? expr2->analyze() : 0); + } +}; + +class alternative_expr : public binary_expr { +public: + alternative_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class list_expr : public binary_expr { +public: + list_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class substitute_expr : public binary_expr { +public: + substitute_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class ternary_expr : public expression { +protected: + expression *expr1; + expression *expr2; + expression *expr3; +public: + ternary_expr(expression *e1, expression *e2, expression *e3) + : expr1(e1), expr2(e2), expr3(e3) { } + ~ternary_expr() { delete expr1; delete expr2; delete expr3; } + void evaluate(int, const reference &, string &, substring_position &) = 0; + unsigned analyze() { + return ((expr1 ? expr1->analyze() : 0) + | (expr2 ? expr2->analyze() : 0) + | (expr3 ? expr3->analyze() : 0)); + } +}; + +class conditional_expr : public ternary_expr { +public: + conditional_expr(expression *e1, expression *e2, expression *e3) + : ternary_expr(e1, e2, e3) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +static expression *parsed_label = 0; +static expression *parsed_date_label = 0; +static expression *parsed_short_label = 0; + +static expression *parse_result; + +string literals; + + +#line 270 "src/preproc/refer/label.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_REFER_LABEL_HPP_INCLUDED +# define YY_YY_SRC_PREPROC_REFER_LABEL_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" */ + TOKEN_LETTER = 258, /* TOKEN_LETTER */ + TOKEN_LITERAL = 259, /* TOKEN_LITERAL */ + TOKEN_DIGIT = 260 /* TOKEN_DIGIT */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define TOKEN_LETTER 258 +#define TOKEN_LITERAL 259 +#define TOKEN_DIGIT 260 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 218 "../src/preproc/refer/label.ypp" + + int num; + expression *expr; + struct { int ndigits; int val; } dig; + struct { int start; int len; } str; + +#line 340 "src/preproc/refer/label.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_REFER_LABEL_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_TOKEN_LETTER = 3, /* TOKEN_LETTER */ + YYSYMBOL_TOKEN_LITERAL = 4, /* TOKEN_LITERAL */ + YYSYMBOL_TOKEN_DIGIT = 5, /* TOKEN_DIGIT */ + YYSYMBOL_6_ = 6, /* '?' */ + YYSYMBOL_7_ = 7, /* ':' */ + YYSYMBOL_8_ = 8, /* '|' */ + YYSYMBOL_9_ = 9, /* '&' */ + YYSYMBOL_10_ = 10, /* '~' */ + YYSYMBOL_11_ = 11, /* '@' */ + YYSYMBOL_12_ = 12, /* '%' */ + YYSYMBOL_13_ = 13, /* '.' */ + YYSYMBOL_14_ = 14, /* '+' */ + YYSYMBOL_15_ = 15, /* '-' */ + YYSYMBOL_16_ = 16, /* '*' */ + YYSYMBOL_17_ = 17, /* '(' */ + YYSYMBOL_18_ = 18, /* ')' */ + YYSYMBOL_19_ = 19, /* '<' */ + YYSYMBOL_20_ = 20, /* '>' */ + YYSYMBOL_YYACCEPT = 21, /* $accept */ + YYSYMBOL_expr = 22, /* expr */ + YYSYMBOL_conditional = 23, /* conditional */ + YYSYMBOL_optional_conditional = 24, /* optional_conditional */ + YYSYMBOL_alternative = 25, /* alternative */ + YYSYMBOL_list = 26, /* list */ + YYSYMBOL_substitute = 27, /* substitute */ + YYSYMBOL_string = 28, /* string */ + YYSYMBOL_optional_number = 29, /* optional_number */ + YYSYMBOL_number = 30, /* number */ + YYSYMBOL_digits = 31, /* digits */ + YYSYMBOL_flag = 32 /* flag */ +}; +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_int8 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 21 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 39 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 21 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 12 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 34 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 49 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 260 + + +/* 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, 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, 12, 9, 2, + 17, 18, 16, 14, 2, 15, 13, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 7, 2, + 19, 2, 20, 6, 11, 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, 8, 2, 10, 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 +}; + +#if YYDEBUG +/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_int16 yyrline[] = +{ + 0, 246, 246, 251, 253, 259, 260, 265, 267, 269, + 274, 276, 281, 283, 288, 290, 295, 297, 299, 315, + 319, 350, 352, 354, 356, 358, 364, 365, 370, 372, + 377, 379, 386, 387, 389 +}; +#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\"", "TOKEN_LETTER", + "TOKEN_LITERAL", "TOKEN_DIGIT", "'?'", "':'", "'|'", "'&'", "'~'", "'@'", + "'%'", "'.'", "'+'", "'-'", "'*'", "'('", "')'", "'<'", "'>'", "$accept", + "expr", "conditional", "optional_conditional", "alternative", "list", + "substitute", "string", "optional_number", "number", "digits", "flag", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#define YYPACT_NINF (-26) + +#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_int8 yypact[] = +{ + 2, 11, -26, -26, 12, 2, 2, 24, -26, -26, + 21, 2, 18, -6, -26, 26, -26, -26, 27, 15, + 14, -26, 2, 2, 2, 18, 2, -3, 11, 11, + -26, -26, -26, -26, -26, 28, 2, 2, -6, -26, + -26, 33, 26, 26, 2, 11, -26, -26, 26 +}; + +/* 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[] = +{ + 5, 16, 15, 14, 0, 5, 5, 0, 6, 2, + 3, 7, 10, 12, 28, 17, 18, 30, 19, 0, + 0, 1, 5, 0, 0, 11, 0, 32, 0, 0, + 23, 29, 31, 24, 25, 0, 8, 9, 13, 33, + 34, 0, 21, 22, 0, 26, 4, 20, 27 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -26, -26, -7, -4, -26, -1, -11, 13, -26, -25, + -26, -26 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + 0, 7, 8, 9, 10, 11, 12, 13, 47, 15, + 18, 41 +}; + +/* 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_int8 yytable[] = +{ + 25, 19, 20, 42, 43, 1, 2, 27, 28, 29, + 30, 39, 40, 3, 4, 16, 14, 17, 35, 5, + 48, 6, 36, 37, 21, 25, 25, 22, 26, 23, + 24, 31, 32, 33, 34, 44, 45, 46, 0, 38 +}; + +static const yytype_int8 yycheck[] = +{ + 11, 5, 6, 28, 29, 3, 4, 13, 14, 15, + 16, 14, 15, 11, 12, 3, 5, 5, 22, 17, + 45, 19, 23, 24, 0, 36, 37, 6, 10, 8, + 9, 5, 5, 18, 20, 7, 3, 44, -1, 26 +}; + +/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of + state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 3, 4, 11, 12, 17, 19, 22, 23, 24, + 25, 26, 27, 28, 5, 30, 3, 5, 31, 24, + 24, 0, 6, 8, 9, 27, 10, 13, 14, 15, + 16, 5, 5, 18, 20, 24, 26, 26, 28, 14, + 15, 32, 30, 30, 7, 3, 23, 29, 30 +}; + +/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr1[] = +{ + 0, 21, 22, 23, 23, 24, 24, 25, 25, 25, + 26, 26, 27, 27, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 29, 29, 30, 30, + 31, 31, 32, 32, 32 +}; + +/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 1, 1, 5, 0, 1, 1, 3, 3, + 1, 2, 1, 3, 1, 1, 1, 2, 2, 2, + 5, 3, 3, 2, 3, 3, 0, 1, 1, 2, + 1, 2, 0, 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 2: /* expr: optional_conditional */ +#line 247 "../src/preproc/refer/label.ypp" + { parse_result = ((yyvsp[0].expr) ? new analyzed_expr((yyvsp[0].expr)) : 0); } +#line 1370 "src/preproc/refer/label.cpp" + break; + + case 3: /* conditional: alternative */ +#line 252 "../src/preproc/refer/label.ypp" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1376 "src/preproc/refer/label.cpp" + break; + + case 4: /* conditional: alternative '?' optional_conditional ':' conditional */ +#line 254 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new conditional_expr((yyvsp[-4].expr), (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1382 "src/preproc/refer/label.cpp" + break; + + case 5: /* optional_conditional: %empty */ +#line 259 "../src/preproc/refer/label.ypp" + { (yyval.expr) = 0; } +#line 1388 "src/preproc/refer/label.cpp" + break; + + case 6: /* optional_conditional: conditional */ +#line 261 "../src/preproc/refer/label.ypp" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1394 "src/preproc/refer/label.cpp" + break; + + case 7: /* alternative: list */ +#line 266 "../src/preproc/refer/label.ypp" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1400 "src/preproc/refer/label.cpp" + break; + + case 8: /* alternative: alternative '|' list */ +#line 268 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new alternative_expr((yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1406 "src/preproc/refer/label.cpp" + break; + + case 9: /* alternative: alternative '&' list */ +#line 270 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new conditional_expr((yyvsp[-2].expr), (yyvsp[0].expr), 0); } +#line 1412 "src/preproc/refer/label.cpp" + break; + + case 10: /* list: substitute */ +#line 275 "../src/preproc/refer/label.ypp" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1418 "src/preproc/refer/label.cpp" + break; + + case 11: /* list: list substitute */ +#line 277 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new list_expr((yyvsp[-1].expr), (yyvsp[0].expr)); } +#line 1424 "src/preproc/refer/label.cpp" + break; + + case 12: /* substitute: string */ +#line 282 "../src/preproc/refer/label.ypp" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1430 "src/preproc/refer/label.cpp" + break; + + case 13: /* substitute: substitute '~' string */ +#line 284 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new substitute_expr((yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1436 "src/preproc/refer/label.cpp" + break; + + case 14: /* string: '@' */ +#line 289 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new at_expr; } +#line 1442 "src/preproc/refer/label.cpp" + break; + + case 15: /* string: TOKEN_LITERAL */ +#line 291 "../src/preproc/refer/label.ypp" + { + (yyval.expr) = new literal_expr(literals.contents() + (yyvsp[0].str).start, + (yyvsp[0].str).len); + } +#line 1451 "src/preproc/refer/label.cpp" + break; + + case 16: /* string: TOKEN_LETTER */ +#line 296 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new field_expr((yyvsp[0].num), 0); } +#line 1457 "src/preproc/refer/label.cpp" + break; + + case 17: /* string: TOKEN_LETTER number */ +#line 298 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new field_expr((yyvsp[-1].num), (yyvsp[0].num) - 1); } +#line 1463 "src/preproc/refer/label.cpp" + break; + + case 18: /* string: '%' TOKEN_LETTER */ +#line 300 "../src/preproc/refer/label.ypp" + { + switch ((yyvsp[0].num)) { + case 'I': + case 'i': + case 'A': + case 'a': + (yyval.expr) = new format_expr((yyvsp[0].num)); + break; + default: + command_error("unrecognized format '%1'", char((yyvsp[0].num))); + (yyval.expr) = new format_expr('a'); + break; + } + } +#line 1482 "src/preproc/refer/label.cpp" + break; + + case 19: /* string: '%' digits */ +#line 316 "../src/preproc/refer/label.ypp" + { + (yyval.expr) = new format_expr('0', (yyvsp[0].dig).ndigits, (yyvsp[0].dig).val); + } +#line 1490 "src/preproc/refer/label.cpp" + break; + + case 20: /* string: string '.' flag TOKEN_LETTER optional_number */ +#line 320 "../src/preproc/refer/label.ypp" + { + switch ((yyvsp[-1].num)) { + case 'l': + (yyval.expr) = new map_expr((yyvsp[-4].expr), lowercase); + break; + case 'u': + (yyval.expr) = new map_expr((yyvsp[-4].expr), uppercase); + break; + case 'c': + (yyval.expr) = new map_expr((yyvsp[-4].expr), capitalize); + break; + case 'r': + (yyval.expr) = new map_expr((yyvsp[-4].expr), reverse_name); + break; + case 'a': + (yyval.expr) = new map_expr((yyvsp[-4].expr), abbreviate_name); + break; + case 'y': + (yyval.expr) = new extractor_expr((yyvsp[-4].expr), find_year, (yyvsp[-2].num)); + break; + case 'n': + (yyval.expr) = new extractor_expr((yyvsp[-4].expr), find_last_name, (yyvsp[-2].num)); + break; + default: + (yyval.expr) = (yyvsp[-4].expr); + command_error("unknown function '%1'", char((yyvsp[-1].num))); + break; + } + } +#line 1524 "src/preproc/refer/label.cpp" + break; + + case 21: /* string: string '+' number */ +#line 351 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new truncate_expr((yyvsp[-2].expr), (yyvsp[0].num)); } +#line 1530 "src/preproc/refer/label.cpp" + break; + + case 22: /* string: string '-' number */ +#line 353 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new truncate_expr((yyvsp[-2].expr), -(yyvsp[0].num)); } +#line 1536 "src/preproc/refer/label.cpp" + break; + + case 23: /* string: string '*' */ +#line 355 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new star_expr((yyvsp[-1].expr)); } +#line 1542 "src/preproc/refer/label.cpp" + break; + + case 24: /* string: '(' optional_conditional ')' */ +#line 357 "../src/preproc/refer/label.ypp" + { (yyval.expr) = (yyvsp[-1].expr); } +#line 1548 "src/preproc/refer/label.cpp" + break; + + case 25: /* string: '<' optional_conditional '>' */ +#line 359 "../src/preproc/refer/label.ypp" + { (yyval.expr) = new separator_expr((yyvsp[-1].expr)); } +#line 1554 "src/preproc/refer/label.cpp" + break; + + case 26: /* optional_number: %empty */ +#line 364 "../src/preproc/refer/label.ypp" + { (yyval.num) = -1; } +#line 1560 "src/preproc/refer/label.cpp" + break; + + case 27: /* optional_number: number */ +#line 366 "../src/preproc/refer/label.ypp" + { (yyval.num) = (yyvsp[0].num); } +#line 1566 "src/preproc/refer/label.cpp" + break; + + case 28: /* number: TOKEN_DIGIT */ +#line 371 "../src/preproc/refer/label.ypp" + { (yyval.num) = (yyvsp[0].num); } +#line 1572 "src/preproc/refer/label.cpp" + break; + + case 29: /* number: number TOKEN_DIGIT */ +#line 373 "../src/preproc/refer/label.ypp" + { (yyval.num) = (yyvsp[-1].num)*10 + (yyvsp[0].num); } +#line 1578 "src/preproc/refer/label.cpp" + break; + + case 30: /* digits: TOKEN_DIGIT */ +#line 378 "../src/preproc/refer/label.ypp" + { (yyval.dig).ndigits = 1; (yyval.dig).val = (yyvsp[0].num); } +#line 1584 "src/preproc/refer/label.cpp" + break; + + case 31: /* digits: digits TOKEN_DIGIT */ +#line 380 "../src/preproc/refer/label.ypp" + { (yyval.dig).ndigits = (yyvsp[-1].dig).ndigits + 1; (yyval.dig).val = (yyvsp[-1].dig).val*10 + (yyvsp[0].num); } +#line 1590 "src/preproc/refer/label.cpp" + break; + + case 32: /* flag: %empty */ +#line 386 "../src/preproc/refer/label.ypp" + { (yyval.num) = 0; } +#line 1596 "src/preproc/refer/label.cpp" + break; + + case 33: /* flag: '+' */ +#line 388 "../src/preproc/refer/label.ypp" + { (yyval.num) = 1; } +#line 1602 "src/preproc/refer/label.cpp" + break; + + case 34: /* flag: '-' */ +#line 390 "../src/preproc/refer/label.ypp" + { (yyval.num) = -1; } +#line 1608 "src/preproc/refer/label.cpp" + break; + + +#line 1612 "src/preproc/refer/label.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 393 "../src/preproc/refer/label.ypp" + + +/* bison defines const to be empty unless __STDC__ is defined, which it +isn't under cfront */ + +#ifdef const +#undef const +#endif + +const char *spec_ptr; +const char *spec_end; +const char *spec_cur; + +static char uppercase_array[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', +}; + +static char lowercase_array[] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', +}; + +int yylex() +{ + while (spec_ptr < spec_end && csspace(*spec_ptr)) + spec_ptr++; + spec_cur = spec_ptr; + if (spec_ptr >= spec_end) + return 0; + unsigned char c = *spec_ptr++; + if (csalpha(c)) { + yylval.num = c; + return TOKEN_LETTER; + } + if (csdigit(c)) { + yylval.num = c - '0'; + return TOKEN_DIGIT; + } + if (c == '\'') { + yylval.str.start = literals.length(); + for (; spec_ptr < spec_end; spec_ptr++) { + if (*spec_ptr == '\'') { + if (++spec_ptr < spec_end && *spec_ptr == '\'') + literals += '\''; + else { + yylval.str.len = literals.length() - yylval.str.start; + return TOKEN_LITERAL; + } + } + else + literals += *spec_ptr; + } + yylval.str.len = literals.length() - yylval.str.start; + return TOKEN_LITERAL; + } + return c; +} + +int set_label_spec(const char *label_spec) +{ + spec_cur = spec_ptr = label_spec; + spec_end = strchr(label_spec, '\0'); + literals.clear(); + if (yyparse()) + return 0; + delete parsed_label; + parsed_label = parse_result; + return 1; +} + +int set_date_label_spec(const char *label_spec) +{ + spec_cur = spec_ptr = label_spec; + spec_end = strchr(label_spec, '\0'); + literals.clear(); + if (yyparse()) + return 0; + delete parsed_date_label; + parsed_date_label = parse_result; + return 1; +} + +int set_short_label_spec(const char *label_spec) +{ + spec_cur = spec_ptr = label_spec; + spec_end = strchr(label_spec, '\0'); + literals.clear(); + if (yyparse()) + return 0; + delete parsed_short_label; + parsed_short_label = parse_result; + return 1; +} + +void yyerror(const char *message) +{ + if (spec_cur < spec_end) + command_error("label specification %1 before '%2'", message, spec_cur); + else + command_error("label specification %1 at end of string", + message, spec_cur); +} + +void at_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (tentative) + ref.canonicalize_authors(result); + else { + const char *end, *start = ref.get_authors(&end); + if (start) + result.append(start, end - start); + } +} + +void format_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (tentative) + return; + const label_info *lp = ref.get_label_ptr(); + int num = lp == 0 ? ref.get_number() : lp->count; + if (type != '0') + result += format_serial(type, num + 1); + else { + const char *ptr = i_to_a(num + first_number); + int pad = width - strlen(ptr); + while (--pad >= 0) + result += '0'; + result += ptr; + } +} + +static const char *format_serial(char c, int n) +{ + assert(n > 0); + static char buf[128]; // more than enough. + switch (c) { + case 'i': + case 'I': + { + char *p = buf; + // troff uses z and w to represent 10000 and 5000 in Roman + // numerals; I can find no historical basis for this usage + const char *s = c == 'i' ? "zwmdclxvi" : "ZWMDCLXVI"; + if (n >= 40000) + return i_to_a(n); + while (n >= 10000) { + *p++ = s[0]; + n -= 10000; + } + for (int i = 1000; i > 0; i /= 10, s += 2) { + int m = n/i; + n -= m*i; + switch (m) { + case 3: + *p++ = s[2]; + /* falls through */ + case 2: + *p++ = s[2]; + /* falls through */ + case 1: + *p++ = s[2]; + break; + case 4: + *p++ = s[2]; + *p++ = s[1]; + break; + case 8: + *p++ = s[1]; + *p++ = s[2]; + *p++ = s[2]; + *p++ = s[2]; + break; + case 7: + *p++ = s[1]; + *p++ = s[2]; + *p++ = s[2]; + break; + case 6: + *p++ = s[1]; + *p++ = s[2]; + break; + case 5: + *p++ = s[1]; + break; + case 9: + *p++ = s[2]; + *p++ = s[0]; + } + } + *p = 0; + break; + } + case 'a': + case 'A': + { + char *p = buf; + // this is derived from troff/reg.c + while (n > 0) { + int d = n % 26; + if (d == 0) + d = 26; + n -= d; + n /= 26; + *p++ = c == 'a' ? lowercase_array[d - 1] : + uppercase_array[d - 1]; + } + *p-- = 0; + // Reverse it. + char *q = buf; + while (q < p) { + char temp = *q; + *q = *p; + *p = temp; + --p; + ++q; + } + break; + } + default: + assert(0); + } + return buf; +} + +void field_expr::evaluate(int, const reference &ref, + string &result, substring_position &) +{ + const char *end; + const char *start = ref.get_field(name, &end); + if (start) { + start = nth_field(number, start, &end); + if (start) + result.append(start, end - start); + } +} + +void literal_expr::evaluate(int, const reference &, + string &result, substring_position &) +{ + result += s; +} + +analyzed_expr::analyzed_expr(expression *e) +: unary_expr(e), flags(e ? e->analyze() : 0) +{ +} + +void analyzed_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + if (expr) + expr->evaluate(tentative, ref, result, pos); +} + +void star_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + const label_info *lp = ref.get_label_ptr(); + if (!tentative + && (lp == 0 || lp->total > 1) + && expr) + expr->evaluate(tentative, ref, result, pos); +} + +void separator_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + int start_length = result.length(); + int is_first = pos.start < 0; + if (expr) + expr->evaluate(tentative, ref, result, pos); + if (is_first) { + pos.start = start_length; + pos.length = result.length() - start_length; + } +} + +void map_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (expr) { + string temp; + substring_position temp_pos; + expr->evaluate(tentative, ref, temp, temp_pos); + (*func)(temp.contents(), temp.contents() + temp.length(), result); + } +} + +void extractor_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (expr) { + string temp; + substring_position temp_pos; + expr->evaluate(tentative, ref, temp, temp_pos); + const char *end, *start = (*func)(temp.contents(), + temp.contents() + temp.length(), + &end); + switch (part) { + case BEFORE: + if (start) + result.append(temp.contents(), start - temp.contents()); + else + result += temp; + break; + case MATCH: + if (start) + result.append(start, end - start); + break; + case AFTER: + if (start) + result.append(end, temp.contents() + temp.length() - end); + break; + default: + assert(0); + } + } +} + +static void first_part(int len, const char *ptr, const char *end, + string &result) +{ + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + break; + const token_info *ti = lookup_token(token_start, ptr); + int counts = ti->sortify_non_empty(token_start, ptr); + if (counts && --len < 0) + break; + if (counts || ti->is_accent()) + result.append(token_start, ptr - token_start); + } +} + +static void last_part(int len, const char *ptr, const char *end, + string &result) +{ + const char *start = ptr; + int count = 0; + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + break; + const token_info *ti = lookup_token(token_start, ptr); + if (ti->sortify_non_empty(token_start, ptr)) + count++; + } + ptr = start; + int skip = count - len; + if (skip > 0) { + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + assert(0); + const token_info *ti = lookup_token(token_start, ptr); + if (ti->sortify_non_empty(token_start, ptr) && --skip < 0) { + ptr = token_start; + break; + } + } + } + first_part(len, ptr, end, result); +} + +void truncate_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (expr) { + string temp; + substring_position temp_pos; + expr->evaluate(tentative, ref, temp, temp_pos); + const char *start = temp.contents(); + const char *end = start + temp.length(); + if (n > 0) + first_part(n, start, end, result); + else if (n < 0) + last_part(-n, start, end, result); + } +} + +void alternative_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + int start_length = result.length(); + if (expr1) + expr1->evaluate(tentative, ref, result, pos); + if (result.length() == start_length && expr2) + expr2->evaluate(tentative, ref, result, pos); +} + +void list_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + if (expr1) + expr1->evaluate(tentative, ref, result, pos); + if (expr2) + expr2->evaluate(tentative, ref, result, pos); +} + +void substitute_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + int start_length = result.length(); + if (expr1) + expr1->evaluate(tentative, ref, result, pos); + if (result.length() > start_length && result[result.length() - 1] == '-') { + // ought to see if pos covers the - + result.set_length(result.length() - 1); + if (expr2) + expr2->evaluate(tentative, ref, result, pos); + } +} + +void conditional_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + string temp; + substring_position temp_pos; + if (expr1) + expr1->evaluate(tentative, ref, temp, temp_pos); + if (temp.length() > 0) { + if (expr2) + expr2->evaluate(tentative, ref, result, pos); + } + else { + if (expr3) + expr3->evaluate(tentative, ref, result, pos); + } +} + +void reference::pre_compute_label() +{ + if (parsed_label != 0 + && (parsed_label->analyze() & expression::CONTAINS_VARIABLE)) { + label.clear(); + substring_position temp_pos; + parsed_label->evaluate(1, *this, label, temp_pos); + label_ptr = lookup_label(label); + } +} + +void reference::compute_label() +{ + label.clear(); + if (parsed_label) + parsed_label->evaluate(0, *this, label, separator_pos); + if (short_label_flag && parsed_short_label) + parsed_short_label->evaluate(0, *this, short_label, short_separator_pos); + if (date_as_label) { + string new_date; + if (parsed_date_label) { + substring_position temp_pos; + parsed_date_label->evaluate(0, *this, new_date, temp_pos); + } + set_date(new_date); + } + if (label_ptr) + label_ptr->count += 1; +} + +void reference::immediate_compute_label() +{ + if (label_ptr) + label_ptr->total = 2; // force use of disambiguator + compute_label(); +} + +int reference::merge_labels(reference **v, int n, label_type type, + string &result) +{ + if (abbreviate_label_ranges) + return merge_labels_by_number(v, n, type, result); + else + return merge_labels_by_parts(v, n, type, result); +} + +int reference::merge_labels_by_number(reference **v, int n, label_type type, + string &result) +{ + if (n <= 1) + return 0; + int num = get_number(); + // Only merge three or more labels. + if (v[0]->get_number() != num + 1 + || v[1]->get_number() != num + 2) + return 0; + int i; + for (i = 2; i < n; i++) + if (v[i]->get_number() != num + i + 1) + break; + result = get_label(type); + result += label_range_indicator; + result += v[i - 1]->get_label(type); + return i; +} + +const substring_position &reference::get_separator_pos(label_type type) const +{ + if (type == SHORT_LABEL && short_label_flag) + return short_separator_pos; + else + return separator_pos; +} + +const string &reference::get_label(label_type type) const +{ + if (type == SHORT_LABEL && short_label_flag) + return short_label; + else + return label; +} + +int reference::merge_labels_by_parts(reference **v, int n, label_type type, + string &result) +{ + if (n <= 0) + return 0; + const string &lb = get_label(type); + const substring_position &sp = get_separator_pos(type); + if (sp.start < 0 + || sp.start != v[0]->get_separator_pos(type).start + || memcmp(lb.contents(), v[0]->get_label(type).contents(), + sp.start) != 0) + return 0; + result = lb; + int i = 0; + do { + result += separate_label_second_parts; + const substring_position &s = v[i]->get_separator_pos(type); + int sep_end_pos = s.start + s.length; + result.append(v[i]->get_label(type).contents() + sep_end_pos, + v[i]->get_label(type).length() - sep_end_pos); + } while (++i < n + && sp.start == v[i]->get_separator_pos(type).start + && memcmp(lb.contents(), v[i]->get_label(type).contents(), + sp.start) == 0); + return i; +} + +string label_pool; + +label_info::label_info(const string &s) +: start(label_pool.length()), length(s.length()), count(0), total(1) +{ + label_pool += s; +} + +static label_info **label_table = 0; +static int label_table_size = 0; +static int label_table_used = 0; + +label_info *lookup_label(const string &label) +{ + if (label_table == 0) { + label_table = new label_info *[17]; + label_table_size = 17; + for (int i = 0; i < 17; i++) + label_table[i] = 0; + } + unsigned h = hash_string(label.contents(), label.length()) % label_table_size; + label_info **ptr; + for (ptr = label_table + h; + *ptr != 0; + (ptr == label_table) + ? (ptr = label_table + label_table_size - 1) + : ptr--) + if ((*ptr)->length == label.length() + && memcmp(label_pool.contents() + (*ptr)->start, label.contents(), + label.length()) == 0) { + (*ptr)->total += 1; + return *ptr; + } + label_info *result = *ptr = new label_info(label); + if (++label_table_used * 2 > label_table_size) { + // Rehash the table. + label_info **old_table = label_table; + int old_size = label_table_size; + label_table_size = next_size(label_table_size); + label_table = new label_info *[label_table_size]; + int i; + for (i = 0; i < label_table_size; i++) + label_table[i] = 0; + for (i = 0; i < old_size; i++) + if (old_table[i]) { + h = hash_string(label_pool.contents() + old_table[i]->start, + old_table[i]->length); + label_info **p; + for (p = label_table + (h % label_table_size); + *p != 0; + (p == label_table) + ? (p = label_table + label_table_size - 1) + : --p) + ; + *p = old_table[i]; + } + delete[] old_table; + } + return result; +} + +void clear_labels() +{ + for (int i = 0; i < label_table_size; i++) { + delete label_table[i]; + label_table[i] = 0; + } + label_table_used = 0; + label_pool.clear(); +} + +static void consider_authors(reference **start, reference **end, int i); + +void compute_labels(reference **v, int n) +{ + if (parsed_label + && (parsed_label->analyze() & expression::CONTAINS_AT) + && sort_fields.length() >= 2 + && sort_fields[0] == 'A' + && sort_fields[1] == '+') + consider_authors(v, v + n, 0); + for (int i = 0; i < n; i++) + v[i]->compute_label(); +} + + +/* A reference with a list of authors <A0,A1,...,AN> _needs_ author i +where 0 <= i <= N if there exists a reference with a list of authors +<B0,B1,...,BM> such that <A0,A1,...,AN> != <B0,B1,...,BM> and M >= i +and Aj = Bj for 0 <= j < i. In this case if we can't say "A0, +A1,...,A(i-1) et al" because this would match both <A0,A1,...,AN> and +<B0,B1,...,BM>. If a reference needs author i we only have to call +need_author(j) for some j >= i such that the reference also needs +author j. */ + +/* This function handles 2 tasks: +determine which authors are needed (cannot be elided with et al.); +determine which authors can have only last names in the labels. + +References >= start and < end have the same first i author names. +Also they're sorted by A+. */ + +static void consider_authors(reference **start, reference **end, int i) +{ + if (start >= end) + return; + reference **p = start; + if (i >= (*p)->get_nauthors()) { + for (++p; p < end && i >= (*p)->get_nauthors(); p++) + ; + if (p < end && i > 0) { + // If we have an author list <A B C> and an author list <A B C D>, + // then both lists need C. + for (reference **q = start; q < end; q++) + (*q)->need_author(i - 1); + } + start = p; + } + while (p < end) { + reference **last_name_start = p; + reference **name_start = p; + for (++p; + p < end && i < (*p)->get_nauthors() + && same_author_last_name(**last_name_start, **p, i); + p++) { + if (!same_author_name(**name_start, **p, i)) { + consider_authors(name_start, p, i + 1); + name_start = p; + } + } + consider_authors(name_start, p, i + 1); + if (last_name_start == name_start) { + for (reference **q = last_name_start; q < p; q++) + (*q)->set_last_name_unambiguous(i); + } + // If we have an author list <A B C D> and <A B C E>, then the lists + // need author D and E respectively. + if (name_start > start || p < end) { + for (reference **q = last_name_start; q < p; q++) + (*q)->need_author(i); + } + } +} + +int same_author_last_name(const reference &r1, const reference &r2, int n) +{ + const char *ae1; + const char *as1 = r1.get_sort_field(0, n, 0, &ae1); + const char *ae2; + const char *as2 = r2.get_sort_field(0, n, 0, &ae2); + if (!as1 && !as2) return 1; // they are the same + if (!as1 || !as2) return 0; + return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0; +} + +int same_author_name(const reference &r1, const reference &r2, int n) +{ + const char *ae1; + const char *as1 = r1.get_sort_field(0, n, -1, &ae1); + const char *ae2; + const char *as2 = r2.get_sort_field(0, n, -1, &ae2); + if (!as1 && !as2) return 1; // they are the same + if (!as1 || !as2) return 0; + return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0; +} + + +void int_set::set(int i) +{ + assert(i >= 0); + int bytei = i >> 3; + if (bytei >= v.length()) { + int old_length = v.length(); + v.set_length(bytei + 1); + for (int j = old_length; j <= bytei; j++) + v[j] = 0; + } + v[bytei] |= 1 << (i & 7); +} + +int int_set::get(int i) const +{ + assert(i >= 0); + int bytei = i >> 3; + return bytei >= v.length() ? 0 : (v[bytei] & (1 << (i & 7))) != 0; +} + +void reference::set_last_name_unambiguous(int i) +{ + last_name_unambiguous.set(i); +} + +void reference::need_author(int n) +{ + if (n > last_needed_author) + last_needed_author = n; +} + +const char *reference::get_authors(const char **end) const +{ + if (!computed_authors) { + ((reference *)this)->computed_authors = 1; + string &result = ((reference *)this)->authors; + int na = get_nauthors(); + result.clear(); + for (int i = 0; i < na; i++) { + if (last_name_unambiguous.get(i)) { + const char *e, *start = get_author_last_name(i, &e); + assert(start != 0); + result.append(start, e - start); + } + else { + const char *e, *start = get_author(i, &e); + assert(start != 0); + result.append(start, e - start); + } + if (i == last_needed_author + && et_al.length() > 0 + && et_al_min_elide > 0 + && last_needed_author + et_al_min_elide < na + && na >= et_al_min_total) { + result += et_al; + break; + } + if (i < na - 1) { + if (na == 2) + result += join_authors_exactly_two; + else if (i < na - 2) + result += join_authors_default; + else + result += join_authors_last_two; + } + } + } + const char *start = authors.contents(); + *end = start + authors.length(); + return start; +} + +int reference::get_nauthors() const +{ + if (nauthors < 0) { + const char *dummy; + int na; + for (na = 0; get_author(na, &dummy) != 0; na++) + ; + ((reference *)this)->nauthors = na; + } + return nauthors; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/refer/label.hpp b/src/preproc/refer/label.hpp new file mode 100644 index 0000000..9f79fd2 --- /dev/null +++ b/src/preproc/refer/label.hpp @@ -0,0 +1,98 @@ +/* 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_REFER_LABEL_HPP_INCLUDED +# define YY_YY_SRC_PREPROC_REFER_LABEL_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" */ + TOKEN_LETTER = 258, /* TOKEN_LETTER */ + TOKEN_LITERAL = 259, /* TOKEN_LITERAL */ + TOKEN_DIGIT = 260 /* TOKEN_DIGIT */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define TOKEN_LETTER 258 +#define TOKEN_LITERAL 259 +#define TOKEN_DIGIT 260 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 218 "../src/preproc/refer/label.ypp" + + int num; + expression *expr; + struct { int ndigits; int val; } dig; + struct { int start; int len; } str; + +#line 84 "src/preproc/refer/label.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_REFER_LABEL_HPP_INCLUDED */ diff --git a/src/preproc/refer/label.ypp b/src/preproc/refer/label.ypp new file mode 100644 index 0000000..f5210d5 --- /dev/null +++ b/src/preproc/refer/label.ypp @@ -0,0 +1,1195 @@ +/* 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 "refer.h" +#include "refid.h" +#include "ref.h" +#include "token.h" + +int yylex(); +void yyerror(const char *); + +static const char *format_serial(char c, int n); + +struct label_info { + int start; + int length; + int count; + int total; + label_info(const string &); +}; + +label_info *lookup_label(const string &label); + +struct expression { + enum { + // Does the tentative label depend on the reference? + CONTAINS_VARIABLE = 01, + CONTAINS_STAR = 02, + CONTAINS_FORMAT = 04, + CONTAINS_AT = 010 + }; + virtual ~expression() { } + virtual void evaluate(int, const reference &, string &, + substring_position &) = 0; + virtual unsigned analyze() { return 0; } +}; + +class at_expr : public expression { +public: + at_expr() { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return CONTAINS_VARIABLE|CONTAINS_AT; } +}; + +class format_expr : public expression { + char type; + int width; + int first_number; +public: + format_expr(char c, int w = 0, int f = 1) + : type(c), width(w), first_number(f) { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return CONTAINS_FORMAT; } +}; + +class field_expr : public expression { + int number; + char name; +public: + field_expr(char nm, int num) : number(num), name(nm) { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return CONTAINS_VARIABLE; } +}; + +class literal_expr : public expression { + string s; +public: + literal_expr(const char *ptr, int len) : s(ptr, len) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class unary_expr : public expression { +protected: + expression *expr; +public: + unary_expr(expression *e) : expr(e) { } + ~unary_expr() { delete expr; } + void evaluate(int, const reference &, string &, substring_position &) = 0; + unsigned analyze() { return expr ? expr->analyze() : 0; } +}; + +// This caches the analysis of an expression. + +class analyzed_expr : public unary_expr { + unsigned flags; +public: + analyzed_expr(expression *); + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { return flags; } +}; + +class star_expr : public unary_expr { +public: + star_expr(expression *e) : unary_expr(e) { } + void evaluate(int, const reference &, string &, substring_position &); + unsigned analyze() { + return ((expr ? (expr->analyze() & ~CONTAINS_VARIABLE) : 0) + | CONTAINS_STAR); + } +}; + +typedef void map_func(const char *, const char *, string &); + +class map_expr : public unary_expr { + map_func *func; +public: + map_expr(expression *e, map_func *f) : unary_expr(e), func(f) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +typedef const char *extractor_func(const char *, const char *, const char **); + +class extractor_expr : public unary_expr { + int part; + extractor_func *func; +public: + enum { BEFORE = +1, MATCH = 0, AFTER = -1 }; + extractor_expr(expression *e, extractor_func *f, int pt) + : unary_expr(e), part(pt), func(f) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class truncate_expr : public unary_expr { + int n; +public: + truncate_expr(expression *e, int i) : unary_expr(e), n(i) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class separator_expr : public unary_expr { +public: + separator_expr(expression *e) : unary_expr(e) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class binary_expr : public expression { +protected: + expression *expr1; + expression *expr2; +public: + binary_expr(expression *e1, expression *e2) : expr1(e1), expr2(e2) { } + ~binary_expr() { delete expr1; delete expr2; } + void evaluate(int, const reference &, string &, substring_position &) = 0; + unsigned analyze() { + return (expr1 ? expr1->analyze() : 0) | (expr2 ? expr2->analyze() : 0); + } +}; + +class alternative_expr : public binary_expr { +public: + alternative_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class list_expr : public binary_expr { +public: + list_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class substitute_expr : public binary_expr { +public: + substitute_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +class ternary_expr : public expression { +protected: + expression *expr1; + expression *expr2; + expression *expr3; +public: + ternary_expr(expression *e1, expression *e2, expression *e3) + : expr1(e1), expr2(e2), expr3(e3) { } + ~ternary_expr() { delete expr1; delete expr2; delete expr3; } + void evaluate(int, const reference &, string &, substring_position &) = 0; + unsigned analyze() { + return ((expr1 ? expr1->analyze() : 0) + | (expr2 ? expr2->analyze() : 0) + | (expr3 ? expr3->analyze() : 0)); + } +}; + +class conditional_expr : public ternary_expr { +public: + conditional_expr(expression *e1, expression *e2, expression *e3) + : ternary_expr(e1, e2, e3) { } + void evaluate(int, const reference &, string &, substring_position &); +}; + +static expression *parsed_label = 0; +static expression *parsed_date_label = 0; +static expression *parsed_short_label = 0; + +static expression *parse_result; + +string literals; + +%} + +%union { + int num; + expression *expr; + struct { int ndigits; int val; } dig; + struct { int start; int len; } str; +} + +/* uppercase or lowercase letter */ +%token <num> TOKEN_LETTER +/* literal characters */ +%token <str> TOKEN_LITERAL +/* digit */ +%token <num> TOKEN_DIGIT + +%type <expr> conditional +%type <expr> alternative +%type <expr> list +%type <expr> string +%type <expr> substitute +%type <expr> optional_conditional +%type <num> number +%type <dig> digits +%type <num> optional_number +%type <num> flag + +%% + +expr: + optional_conditional + { parse_result = ($1 ? new analyzed_expr($1) : 0); } + ; + +conditional: + alternative + { $$ = $1; } + | alternative '?' optional_conditional ':' conditional + { $$ = new conditional_expr($1, $3, $5); } + ; + +optional_conditional: + /* empty */ + { $$ = 0; } + | conditional + { $$ = $1; } + ; + +alternative: + list + { $$ = $1; } + | alternative '|' list + { $$ = new alternative_expr($1, $3); } + | alternative '&' list + { $$ = new conditional_expr($1, $3, 0); } + ; + +list: + substitute + { $$ = $1; } + | list substitute + { $$ = new list_expr($1, $2); } + ; + +substitute: + string + { $$ = $1; } + | substitute '~' string + { $$ = new substitute_expr($1, $3); } + ; + +string: + '@' + { $$ = new at_expr; } + | TOKEN_LITERAL + { + $$ = new literal_expr(literals.contents() + $1.start, + $1.len); + } + | TOKEN_LETTER + { $$ = new field_expr($1, 0); } + | TOKEN_LETTER number + { $$ = new field_expr($1, $2 - 1); } + | '%' TOKEN_LETTER + { + switch ($2) { + case 'I': + case 'i': + case 'A': + case 'a': + $$ = new format_expr($2); + break; + default: + command_error("unrecognized format '%1'", char($2)); + $$ = new format_expr('a'); + break; + } + } + + | '%' digits + { + $$ = new format_expr('0', $2.ndigits, $2.val); + } + | string '.' flag TOKEN_LETTER optional_number + { + switch ($4) { + case 'l': + $$ = new map_expr($1, lowercase); + break; + case 'u': + $$ = new map_expr($1, uppercase); + break; + case 'c': + $$ = new map_expr($1, capitalize); + break; + case 'r': + $$ = new map_expr($1, reverse_name); + break; + case 'a': + $$ = new map_expr($1, abbreviate_name); + break; + case 'y': + $$ = new extractor_expr($1, find_year, $3); + break; + case 'n': + $$ = new extractor_expr($1, find_last_name, $3); + break; + default: + $$ = $1; + command_error("unknown function '%1'", char($4)); + break; + } + } + + | string '+' number + { $$ = new truncate_expr($1, $3); } + | string '-' number + { $$ = new truncate_expr($1, -$3); } + | string '*' + { $$ = new star_expr($1); } + | '(' optional_conditional ')' + { $$ = $2; } + | '<' optional_conditional '>' + { $$ = new separator_expr($2); } + ; + +optional_number: + /* empty */ + { $$ = -1; } + | number + { $$ = $1; } + ; + +number: + TOKEN_DIGIT + { $$ = $1; } + | number TOKEN_DIGIT + { $$ = $1*10 + $2; } + ; + +digits: + TOKEN_DIGIT + { $$.ndigits = 1; $$.val = $1; } + | digits TOKEN_DIGIT + { $$.ndigits = $1.ndigits + 1; $$.val = $1.val*10 + $2; } + ; + + +flag: + /* empty */ + { $$ = 0; } + | '+' + { $$ = 1; } + | '-' + { $$ = -1; } + ; + +%% + +/* bison defines const to be empty unless __STDC__ is defined, which it +isn't under cfront */ + +#ifdef const +#undef const +#endif + +const char *spec_ptr; +const char *spec_end; +const char *spec_cur; + +static char uppercase_array[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', +}; + +static char lowercase_array[] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', +}; + +int yylex() +{ + while (spec_ptr < spec_end && csspace(*spec_ptr)) + spec_ptr++; + spec_cur = spec_ptr; + if (spec_ptr >= spec_end) + return 0; + unsigned char c = *spec_ptr++; + if (csalpha(c)) { + yylval.num = c; + return TOKEN_LETTER; + } + if (csdigit(c)) { + yylval.num = c - '0'; + return TOKEN_DIGIT; + } + if (c == '\'') { + yylval.str.start = literals.length(); + for (; spec_ptr < spec_end; spec_ptr++) { + if (*spec_ptr == '\'') { + if (++spec_ptr < spec_end && *spec_ptr == '\'') + literals += '\''; + else { + yylval.str.len = literals.length() - yylval.str.start; + return TOKEN_LITERAL; + } + } + else + literals += *spec_ptr; + } + yylval.str.len = literals.length() - yylval.str.start; + return TOKEN_LITERAL; + } + return c; +} + +int set_label_spec(const char *label_spec) +{ + spec_cur = spec_ptr = label_spec; + spec_end = strchr(label_spec, '\0'); + literals.clear(); + if (yyparse()) + return 0; + delete parsed_label; + parsed_label = parse_result; + return 1; +} + +int set_date_label_spec(const char *label_spec) +{ + spec_cur = spec_ptr = label_spec; + spec_end = strchr(label_spec, '\0'); + literals.clear(); + if (yyparse()) + return 0; + delete parsed_date_label; + parsed_date_label = parse_result; + return 1; +} + +int set_short_label_spec(const char *label_spec) +{ + spec_cur = spec_ptr = label_spec; + spec_end = strchr(label_spec, '\0'); + literals.clear(); + if (yyparse()) + return 0; + delete parsed_short_label; + parsed_short_label = parse_result; + return 1; +} + +void yyerror(const char *message) +{ + if (spec_cur < spec_end) + command_error("label specification %1 before '%2'", message, spec_cur); + else + command_error("label specification %1 at end of string", + message, spec_cur); +} + +void at_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (tentative) + ref.canonicalize_authors(result); + else { + const char *end, *start = ref.get_authors(&end); + if (start) + result.append(start, end - start); + } +} + +void format_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (tentative) + return; + const label_info *lp = ref.get_label_ptr(); + int num = lp == 0 ? ref.get_number() : lp->count; + if (type != '0') + result += format_serial(type, num + 1); + else { + const char *ptr = i_to_a(num + first_number); + int pad = width - strlen(ptr); + while (--pad >= 0) + result += '0'; + result += ptr; + } +} + +static const char *format_serial(char c, int n) +{ + assert(n > 0); + static char buf[128]; // more than enough. + switch (c) { + case 'i': + case 'I': + { + char *p = buf; + // troff uses z and w to represent 10000 and 5000 in Roman + // numerals; I can find no historical basis for this usage + const char *s = c == 'i' ? "zwmdclxvi" : "ZWMDCLXVI"; + if (n >= 40000) + return i_to_a(n); + while (n >= 10000) { + *p++ = s[0]; + n -= 10000; + } + for (int i = 1000; i > 0; i /= 10, s += 2) { + int m = n/i; + n -= m*i; + switch (m) { + case 3: + *p++ = s[2]; + /* falls through */ + case 2: + *p++ = s[2]; + /* falls through */ + case 1: + *p++ = s[2]; + break; + case 4: + *p++ = s[2]; + *p++ = s[1]; + break; + case 8: + *p++ = s[1]; + *p++ = s[2]; + *p++ = s[2]; + *p++ = s[2]; + break; + case 7: + *p++ = s[1]; + *p++ = s[2]; + *p++ = s[2]; + break; + case 6: + *p++ = s[1]; + *p++ = s[2]; + break; + case 5: + *p++ = s[1]; + break; + case 9: + *p++ = s[2]; + *p++ = s[0]; + } + } + *p = 0; + break; + } + case 'a': + case 'A': + { + char *p = buf; + // this is derived from troff/reg.c + while (n > 0) { + int d = n % 26; + if (d == 0) + d = 26; + n -= d; + n /= 26; + *p++ = c == 'a' ? lowercase_array[d - 1] : + uppercase_array[d - 1]; + } + *p-- = 0; + // Reverse it. + char *q = buf; + while (q < p) { + char temp = *q; + *q = *p; + *p = temp; + --p; + ++q; + } + break; + } + default: + assert(0); + } + return buf; +} + +void field_expr::evaluate(int, const reference &ref, + string &result, substring_position &) +{ + const char *end; + const char *start = ref.get_field(name, &end); + if (start) { + start = nth_field(number, start, &end); + if (start) + result.append(start, end - start); + } +} + +void literal_expr::evaluate(int, const reference &, + string &result, substring_position &) +{ + result += s; +} + +analyzed_expr::analyzed_expr(expression *e) +: unary_expr(e), flags(e ? e->analyze() : 0) +{ +} + +void analyzed_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + if (expr) + expr->evaluate(tentative, ref, result, pos); +} + +void star_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + const label_info *lp = ref.get_label_ptr(); + if (!tentative + && (lp == 0 || lp->total > 1) + && expr) + expr->evaluate(tentative, ref, result, pos); +} + +void separator_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + int start_length = result.length(); + int is_first = pos.start < 0; + if (expr) + expr->evaluate(tentative, ref, result, pos); + if (is_first) { + pos.start = start_length; + pos.length = result.length() - start_length; + } +} + +void map_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (expr) { + string temp; + substring_position temp_pos; + expr->evaluate(tentative, ref, temp, temp_pos); + (*func)(temp.contents(), temp.contents() + temp.length(), result); + } +} + +void extractor_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (expr) { + string temp; + substring_position temp_pos; + expr->evaluate(tentative, ref, temp, temp_pos); + const char *end, *start = (*func)(temp.contents(), + temp.contents() + temp.length(), + &end); + switch (part) { + case BEFORE: + if (start) + result.append(temp.contents(), start - temp.contents()); + else + result += temp; + break; + case MATCH: + if (start) + result.append(start, end - start); + break; + case AFTER: + if (start) + result.append(end, temp.contents() + temp.length() - end); + break; + default: + assert(0); + } + } +} + +static void first_part(int len, const char *ptr, const char *end, + string &result) +{ + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + break; + const token_info *ti = lookup_token(token_start, ptr); + int counts = ti->sortify_non_empty(token_start, ptr); + if (counts && --len < 0) + break; + if (counts || ti->is_accent()) + result.append(token_start, ptr - token_start); + } +} + +static void last_part(int len, const char *ptr, const char *end, + string &result) +{ + const char *start = ptr; + int count = 0; + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + break; + const token_info *ti = lookup_token(token_start, ptr); + if (ti->sortify_non_empty(token_start, ptr)) + count++; + } + ptr = start; + int skip = count - len; + if (skip > 0) { + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + assert(0); + const token_info *ti = lookup_token(token_start, ptr); + if (ti->sortify_non_empty(token_start, ptr) && --skip < 0) { + ptr = token_start; + break; + } + } + } + first_part(len, ptr, end, result); +} + +void truncate_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &) +{ + if (expr) { + string temp; + substring_position temp_pos; + expr->evaluate(tentative, ref, temp, temp_pos); + const char *start = temp.contents(); + const char *end = start + temp.length(); + if (n > 0) + first_part(n, start, end, result); + else if (n < 0) + last_part(-n, start, end, result); + } +} + +void alternative_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + int start_length = result.length(); + if (expr1) + expr1->evaluate(tentative, ref, result, pos); + if (result.length() == start_length && expr2) + expr2->evaluate(tentative, ref, result, pos); +} + +void list_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + if (expr1) + expr1->evaluate(tentative, ref, result, pos); + if (expr2) + expr2->evaluate(tentative, ref, result, pos); +} + +void substitute_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + int start_length = result.length(); + if (expr1) + expr1->evaluate(tentative, ref, result, pos); + if (result.length() > start_length && result[result.length() - 1] == '-') { + // ought to see if pos covers the - + result.set_length(result.length() - 1); + if (expr2) + expr2->evaluate(tentative, ref, result, pos); + } +} + +void conditional_expr::evaluate(int tentative, const reference &ref, + string &result, substring_position &pos) +{ + string temp; + substring_position temp_pos; + if (expr1) + expr1->evaluate(tentative, ref, temp, temp_pos); + if (temp.length() > 0) { + if (expr2) + expr2->evaluate(tentative, ref, result, pos); + } + else { + if (expr3) + expr3->evaluate(tentative, ref, result, pos); + } +} + +void reference::pre_compute_label() +{ + if (parsed_label != 0 + && (parsed_label->analyze() & expression::CONTAINS_VARIABLE)) { + label.clear(); + substring_position temp_pos; + parsed_label->evaluate(1, *this, label, temp_pos); + label_ptr = lookup_label(label); + } +} + +void reference::compute_label() +{ + label.clear(); + if (parsed_label) + parsed_label->evaluate(0, *this, label, separator_pos); + if (short_label_flag && parsed_short_label) + parsed_short_label->evaluate(0, *this, short_label, short_separator_pos); + if (date_as_label) { + string new_date; + if (parsed_date_label) { + substring_position temp_pos; + parsed_date_label->evaluate(0, *this, new_date, temp_pos); + } + set_date(new_date); + } + if (label_ptr) + label_ptr->count += 1; +} + +void reference::immediate_compute_label() +{ + if (label_ptr) + label_ptr->total = 2; // force use of disambiguator + compute_label(); +} + +int reference::merge_labels(reference **v, int n, label_type type, + string &result) +{ + if (abbreviate_label_ranges) + return merge_labels_by_number(v, n, type, result); + else + return merge_labels_by_parts(v, n, type, result); +} + +int reference::merge_labels_by_number(reference **v, int n, label_type type, + string &result) +{ + if (n <= 1) + return 0; + int num = get_number(); + // Only merge three or more labels. + if (v[0]->get_number() != num + 1 + || v[1]->get_number() != num + 2) + return 0; + int i; + for (i = 2; i < n; i++) + if (v[i]->get_number() != num + i + 1) + break; + result = get_label(type); + result += label_range_indicator; + result += v[i - 1]->get_label(type); + return i; +} + +const substring_position &reference::get_separator_pos(label_type type) const +{ + if (type == SHORT_LABEL && short_label_flag) + return short_separator_pos; + else + return separator_pos; +} + +const string &reference::get_label(label_type type) const +{ + if (type == SHORT_LABEL && short_label_flag) + return short_label; + else + return label; +} + +int reference::merge_labels_by_parts(reference **v, int n, label_type type, + string &result) +{ + if (n <= 0) + return 0; + const string &lb = get_label(type); + const substring_position &sp = get_separator_pos(type); + if (sp.start < 0 + || sp.start != v[0]->get_separator_pos(type).start + || memcmp(lb.contents(), v[0]->get_label(type).contents(), + sp.start) != 0) + return 0; + result = lb; + int i = 0; + do { + result += separate_label_second_parts; + const substring_position &s = v[i]->get_separator_pos(type); + int sep_end_pos = s.start + s.length; + result.append(v[i]->get_label(type).contents() + sep_end_pos, + v[i]->get_label(type).length() - sep_end_pos); + } while (++i < n + && sp.start == v[i]->get_separator_pos(type).start + && memcmp(lb.contents(), v[i]->get_label(type).contents(), + sp.start) == 0); + return i; +} + +string label_pool; + +label_info::label_info(const string &s) +: start(label_pool.length()), length(s.length()), count(0), total(1) +{ + label_pool += s; +} + +static label_info **label_table = 0; +static int label_table_size = 0; +static int label_table_used = 0; + +label_info *lookup_label(const string &label) +{ + if (label_table == 0) { + label_table = new label_info *[17]; + label_table_size = 17; + for (int i = 0; i < 17; i++) + label_table[i] = 0; + } + unsigned h = hash_string(label.contents(), label.length()) % label_table_size; + label_info **ptr; + for (ptr = label_table + h; + *ptr != 0; + (ptr == label_table) + ? (ptr = label_table + label_table_size - 1) + : ptr--) + if ((*ptr)->length == label.length() + && memcmp(label_pool.contents() + (*ptr)->start, label.contents(), + label.length()) == 0) { + (*ptr)->total += 1; + return *ptr; + } + label_info *result = *ptr = new label_info(label); + if (++label_table_used * 2 > label_table_size) { + // Rehash the table. + label_info **old_table = label_table; + int old_size = label_table_size; + label_table_size = next_size(label_table_size); + label_table = new label_info *[label_table_size]; + int i; + for (i = 0; i < label_table_size; i++) + label_table[i] = 0; + for (i = 0; i < old_size; i++) + if (old_table[i]) { + h = hash_string(label_pool.contents() + old_table[i]->start, + old_table[i]->length); + label_info **p; + for (p = label_table + (h % label_table_size); + *p != 0; + (p == label_table) + ? (p = label_table + label_table_size - 1) + : --p) + ; + *p = old_table[i]; + } + delete[] old_table; + } + return result; +} + +void clear_labels() +{ + for (int i = 0; i < label_table_size; i++) { + delete label_table[i]; + label_table[i] = 0; + } + label_table_used = 0; + label_pool.clear(); +} + +static void consider_authors(reference **start, reference **end, int i); + +void compute_labels(reference **v, int n) +{ + if (parsed_label + && (parsed_label->analyze() & expression::CONTAINS_AT) + && sort_fields.length() >= 2 + && sort_fields[0] == 'A' + && sort_fields[1] == '+') + consider_authors(v, v + n, 0); + for (int i = 0; i < n; i++) + v[i]->compute_label(); +} + + +/* A reference with a list of authors <A0,A1,...,AN> _needs_ author i +where 0 <= i <= N if there exists a reference with a list of authors +<B0,B1,...,BM> such that <A0,A1,...,AN> != <B0,B1,...,BM> and M >= i +and Aj = Bj for 0 <= j < i. In this case if we can't say "A0, +A1,...,A(i-1) et al" because this would match both <A0,A1,...,AN> and +<B0,B1,...,BM>. If a reference needs author i we only have to call +need_author(j) for some j >= i such that the reference also needs +author j. */ + +/* This function handles 2 tasks: +determine which authors are needed (cannot be elided with et al.); +determine which authors can have only last names in the labels. + +References >= start and < end have the same first i author names. +Also they're sorted by A+. */ + +static void consider_authors(reference **start, reference **end, int i) +{ + if (start >= end) + return; + reference **p = start; + if (i >= (*p)->get_nauthors()) { + for (++p; p < end && i >= (*p)->get_nauthors(); p++) + ; + if (p < end && i > 0) { + // If we have an author list <A B C> and an author list <A B C D>, + // then both lists need C. + for (reference **q = start; q < end; q++) + (*q)->need_author(i - 1); + } + start = p; + } + while (p < end) { + reference **last_name_start = p; + reference **name_start = p; + for (++p; + p < end && i < (*p)->get_nauthors() + && same_author_last_name(**last_name_start, **p, i); + p++) { + if (!same_author_name(**name_start, **p, i)) { + consider_authors(name_start, p, i + 1); + name_start = p; + } + } + consider_authors(name_start, p, i + 1); + if (last_name_start == name_start) { + for (reference **q = last_name_start; q < p; q++) + (*q)->set_last_name_unambiguous(i); + } + // If we have an author list <A B C D> and <A B C E>, then the lists + // need author D and E respectively. + if (name_start > start || p < end) { + for (reference **q = last_name_start; q < p; q++) + (*q)->need_author(i); + } + } +} + +int same_author_last_name(const reference &r1, const reference &r2, int n) +{ + const char *ae1; + const char *as1 = r1.get_sort_field(0, n, 0, &ae1); + const char *ae2; + const char *as2 = r2.get_sort_field(0, n, 0, &ae2); + if (!as1 && !as2) return 1; // they are the same + if (!as1 || !as2) return 0; + return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0; +} + +int same_author_name(const reference &r1, const reference &r2, int n) +{ + const char *ae1; + const char *as1 = r1.get_sort_field(0, n, -1, &ae1); + const char *ae2; + const char *as2 = r2.get_sort_field(0, n, -1, &ae2); + if (!as1 && !as2) return 1; // they are the same + if (!as1 || !as2) return 0; + return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0; +} + + +void int_set::set(int i) +{ + assert(i >= 0); + int bytei = i >> 3; + if (bytei >= v.length()) { + int old_length = v.length(); + v.set_length(bytei + 1); + for (int j = old_length; j <= bytei; j++) + v[j] = 0; + } + v[bytei] |= 1 << (i & 7); +} + +int int_set::get(int i) const +{ + assert(i >= 0); + int bytei = i >> 3; + return bytei >= v.length() ? 0 : (v[bytei] & (1 << (i & 7))) != 0; +} + +void reference::set_last_name_unambiguous(int i) +{ + last_name_unambiguous.set(i); +} + +void reference::need_author(int n) +{ + if (n > last_needed_author) + last_needed_author = n; +} + +const char *reference::get_authors(const char **end) const +{ + if (!computed_authors) { + ((reference *)this)->computed_authors = 1; + string &result = ((reference *)this)->authors; + int na = get_nauthors(); + result.clear(); + for (int i = 0; i < na; i++) { + if (last_name_unambiguous.get(i)) { + const char *e, *start = get_author_last_name(i, &e); + assert(start != 0); + result.append(start, e - start); + } + else { + const char *e, *start = get_author(i, &e); + assert(start != 0); + result.append(start, e - start); + } + if (i == last_needed_author + && et_al.length() > 0 + && et_al_min_elide > 0 + && last_needed_author + et_al_min_elide < na + && na >= et_al_min_total) { + result += et_al; + break; + } + if (i < na - 1) { + if (na == 2) + result += join_authors_exactly_two; + else if (i < na - 2) + result += join_authors_default; + else + result += join_authors_last_two; + } + } + } + const char *start = authors.contents(); + *end = start + authors.length(); + return start; +} + +int reference::get_nauthors() const +{ + if (nauthors < 0) { + const char *dummy; + int na; + for (na = 0; get_author(na, &dummy) != 0; na++) + ; + ((reference *)this)->nauthors = na; + } + return nauthors; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/refer/ref.cpp b/src/preproc/refer/ref.cpp new file mode 100644 index 0000000..9e1b5e7 --- /dev/null +++ b/src/preproc/refer/ref.cpp @@ -0,0 +1,1161 @@ +// -*- 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 "refer.h" +#include "refid.h" +#include "ref.h" +#include "token.h" + +static const char *find_day(const char *, const char *, const char **); +static int find_month(const char *start, const char *end); +static void abbreviate_names(string &); + +#define DEFAULT_ARTICLES "the\000a\000an" + +string articles(DEFAULT_ARTICLES, sizeof(DEFAULT_ARTICLES)); + +// Multiple occurrences of fields are separated by FIELD_SEPARATOR. +const char FIELD_SEPARATOR = '\0'; + +const char MULTI_FIELD_NAMES[] = "AE"; +const char *AUTHOR_FIELDS = "AQ"; + +enum { OTHER, JOURNAL_ARTICLE, BOOK, ARTICLE_IN_BOOK, TECH_REPORT, BELL_TM }; + +const char *reference_types[] = { + "other", + "journal-article", + "book", + "article-in-book", + "tech-report", + "bell-tm", +}; + +static string temp_fields[256]; + +reference::reference(const char *start, int len, reference_id *ridp) +: h(0), merged(0), no(-1), field(0), nfields(0), label_ptr(0), + computed_authors(0), last_needed_author(-1), nauthors(-1) +{ + int i; + for (i = 0; i < 256; i++) + field_index[i] = NULL_FIELD_INDEX; + if (ridp) + rid = *ridp; + if (start == 0) + return; + if (len <= 0) + return; + const char *end = start + len; + const char *ptr = start; + assert(*ptr == '%'); + while (ptr < end) { + if (ptr + 1 < end && ptr[1] != '\0' + && ((ptr[1] != '%' && ptr[1] == annotation_field) + || (ptr + 2 < end && ptr[1] == '%' && ptr[2] != '\0' + && discard_fields.search(ptr[2]) < 0))) { + if (ptr[1] == '%') + ptr++; + string &f = temp_fields[(unsigned char)ptr[1]]; + ptr += 2; + while (ptr < end && csspace(*ptr)) + ptr++; + for (;;) { + for (;;) { + if (ptr >= end) { + f += '\n'; + break; + } + f += *ptr; + if (*ptr++ == '\n') + break; + } + if (ptr >= end || *ptr == '%') + break; + } + } + else if (ptr + 1 < end && ptr[1] != '\0' && ptr[1] != '%' + && discard_fields.search(ptr[1]) < 0) { + string &f = temp_fields[(unsigned char)ptr[1]]; + if (f.length() > 0) { + if (strchr(MULTI_FIELD_NAMES, ptr[1]) != 0) + f += FIELD_SEPARATOR; + else + f.clear(); + } + ptr += 2; + if (ptr < end) { + if (*ptr == ' ') + ptr++; + for (;;) { + const char *p = ptr; + while (ptr < end && *ptr != '\n') + ptr++; + // strip trailing white space + const char *q = ptr; + while (q > p && q[-1] != '\n' && csspace(q[-1])) + q--; + while (p < q) + f += *p++; + if (ptr >= end) + break; + ptr++; + if (ptr >= end) + break; + if (*ptr == '%') + break; + f += ' '; + } + } + } + else { + // skip this field + for (;;) { + while (ptr < end && *ptr++ != '\n') + ; + if (ptr >= end || *ptr == '%') + break; + } + } + } + for (i = 0; i < 256; i++) + if (temp_fields[i].length() > 0) + nfields++; + field = new string[nfields]; + int j = 0; + for (i = 0; i < 256; i++) + if (temp_fields[i].length() > 0) { + field[j].move(temp_fields[i]); + if (abbreviate_fields.search(i) >= 0) + abbreviate_names(field[j]); + field_index[i] = j; + j++; + } +} + +reference::~reference() +{ + if (nfields > 0) + delete[] field; +} + +// ref is the inline, this is the database ref + +void reference::merge(reference &ref) +{ + int i; + for (i = 0; i < 256; i++) + if (field_index[i] != NULL_FIELD_INDEX) + temp_fields[i].move(field[field_index[i]]); + for (i = 0; i < 256; i++) + if (ref.field_index[i] != NULL_FIELD_INDEX) + temp_fields[i].move(ref.field[ref.field_index[i]]); + for (i = 0; i < 256; i++) + field_index[i] = NULL_FIELD_INDEX; + int old_nfields = nfields; + nfields = 0; + for (i = 0; i < 256; i++) + if (temp_fields[i].length() > 0) + nfields++; + if (nfields != old_nfields) { + if (old_nfields > 0) + delete[] field; + field = new string[nfields]; + } + int j = 0; + for (i = 0; i < 256; i++) + if (temp_fields[i].length() > 0) { + field[j].move(temp_fields[i]); + field_index[i] = j; + j++; + } + merged = 1; +} + +void reference::insert_field(unsigned char c, string &s) +{ + assert(s.length() > 0); + if (field_index[c] != NULL_FIELD_INDEX) { + field[field_index[c]].move(s); + return; + } + assert(field_index[c] == NULL_FIELD_INDEX); + string *old_field = field; + field = new string[nfields + 1]; + int pos = 0; + int i; + for (i = 0; i < int(c); i++) + if (field_index[i] != NULL_FIELD_INDEX) + pos++; + for (i = 0; i < pos; i++) + field[i].move(old_field[i]); + field[pos].move(s); + for (i = pos; i < nfields; i++) + field[i + 1].move(old_field[i]); + if (nfields > 0) + delete[] old_field; + nfields++; + field_index[c] = pos; + for (i = c + 1; i < 256; i++) + if (field_index[i] != NULL_FIELD_INDEX) + field_index[i] += 1; +} + +void reference::delete_field(unsigned char c) +{ + if (field_index[c] == NULL_FIELD_INDEX) + return; + string *old_field = field; + field = new string[nfields - 1]; + int i; + for (i = 0; i < int(field_index[c]); i++) + field[i].move(old_field[i]); + for (i = field_index[c]; i < nfields - 1; i++) + field[i].move(old_field[i + 1]); + if (nfields > 0) + delete[] old_field; + nfields--; + field_index[c] = NULL_FIELD_INDEX; + for (i = c + 1; i < 256; i++) + if (field_index[i] != NULL_FIELD_INDEX) + field_index[i] -= 1; +} + +void reference::compute_hash_code() +{ + if (!rid.is_null()) + h = rid.hash(); + else { + h = 0; + for (int i = 0; i < nfields; i++) + if (field[i].length() > 0) { + h <<= 4; + h ^= hash_string(field[i].contents(), field[i].length()); + } + } +} + +void reference::set_number(int n) +{ + no = n; +} + +const char SORT_SEP = '\001'; +const char SORT_SUB_SEP = '\002'; +const char SORT_SUB_SUB_SEP = '\003'; + +// sep specifies additional word separators + +void sortify_words(const char *s, const char *end, const char *sep, + string &result) +{ + int non_empty = 0; + int need_separator = 0; + for (;;) { + const char *token_start = s; + if (!get_token(&s, end)) + break; + if ((s - token_start == 1 + && (*token_start == ' ' + || *token_start == '\n' + || (sep && *token_start != '\0' + && strchr(sep, *token_start) != 0))) + || (s - token_start == 2 + && token_start[0] == '\\' && token_start[1] == ' ')) { + if (non_empty) + need_separator = 1; + } + else { + const token_info *ti = lookup_token(token_start, s); + if (ti->sortify_non_empty(token_start, s)) { + if (need_separator) { + result += ' '; + need_separator = 0; + } + ti->sortify(token_start, s, result); + non_empty = 1; + } + } + } +} + +void sortify_word(const char *s, const char *end, string &result) +{ + for (;;) { + const char *token_start = s; + if (!get_token(&s, end)) + break; + const token_info *ti = lookup_token(token_start, s); + ti->sortify(token_start, s, result); + } +} + +void sortify_other(const char *s, int len, string &key) +{ + sortify_words(s, s + len, 0, key); +} + +void sortify_title(const char *s, int len, string &key) +{ + const char *end = s + len; + for (; s < end && (*s == ' ' || *s == '\n'); s++) + ; + const char *ptr = s; + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + break; + if (ptr - token_start == 1 + && (*token_start == ' ' || *token_start == '\n')) + break; + } + if (ptr < end) { + unsigned int first_word_len = ptr - s - 1; + const char *ae = articles.contents() + articles.length(); + for (const char *a = articles.contents(); + a < ae; + a = strchr(a, '\0') + 1) + if (first_word_len == strlen(a)) { + unsigned int j; + for (j = 0; j < first_word_len; j++) + if (a[j] != cmlower(s[j])) + break; + if (j >= first_word_len) { + s = ptr; + for (; s < end && (*s == ' ' || *s == '\n'); s++) + ; + break; + } + } + } + sortify_words(s, end, 0, key); +} + +void sortify_name(const char *s, int len, string &key) +{ + const char *last_name_end; + const char *last_name = find_last_name(s, s + len, &last_name_end); + sortify_word(last_name, last_name_end, key); + key += SORT_SUB_SUB_SEP; + if (last_name > s) + sortify_words(s, last_name, ".", key); + key += SORT_SUB_SUB_SEP; + if (last_name_end < s + len) + sortify_words(last_name_end, s + len, ".,", key); +} + +void sortify_date(const char *s, int len, string &key) +{ + const char *year_end; + const char *year_start = find_year(s, s + len, &year_end); + if (!year_start) { + // Things without years are often 'forthcoming', so it makes sense + // that they sort after things with explicit years. + key += 'A'; + sortify_words(s, s + len, 0, key); + return; + } + int n = year_end - year_start; + while (n < 4) { + key += '0'; + n++; + } + while (year_start < year_end) + key += *year_start++; + int m = find_month(s, s + len); + if (m < 0) + return; + key += 'A' + m; + const char *day_end; + const char *day_start = find_day(s, s + len, &day_end); + if (!day_start) + return; + if (day_end - day_start == 1) + key += '0'; + while (day_start < day_end) + key += *day_start++; +} + +// SORT_{SUB,SUB_SUB}_SEP can creep in from use of @ in label specification. + +void sortify_label(const char *s, int len, string &key) +{ + const char *end = s + len; + for (;;) { + const char *ptr; + for (ptr = s; + ptr < end && *ptr != SORT_SUB_SEP && *ptr != SORT_SUB_SUB_SEP; + ptr++) + ; + if (ptr > s) + sortify_words(s, ptr, 0, key); + s = ptr; + if (s >= end) + break; + key += *s++; + } +} + +void reference::compute_sort_key() +{ + if (sort_fields.length() == 0) + return; + sort_fields += '\0'; + const char *sf = sort_fields.contents(); + int first_time = 1; + while (*sf != '\0') { + if (!first_time) + sort_key += SORT_SEP; + first_time = 0; + char f = *sf++; + int n = 1; + if (*sf == '+') { + n = INT_MAX; + sf++; + } + else if (csdigit(*sf)) { + char *ptr; + long l = strtol(sf, &ptr, 10); + if (l == 0 && ptr == sf) + ; + else { + sf = ptr; + if (l < 0) { + n = 1; + } + else { + n = int(l); + } + } + } + if (f == '.') + sortify_label(label.contents(), label.length(), sort_key); + else if (f == AUTHOR_FIELDS[0]) + sortify_authors(n, sort_key); + else + sortify_field(f, n, sort_key); + } + sort_fields.set_length(sort_fields.length() - 1); +} + +void reference::sortify_authors(int n, string &result) const +{ + for (const char *p = AUTHOR_FIELDS; *p != '\0'; p++) + if (contains_field(*p)) { + sortify_field(*p, n, result); + return; + } + sortify_field(AUTHOR_FIELDS[0], n, result); +} + +void reference::canonicalize_authors(string &result) const +{ + int len = result.length(); + sortify_authors(INT_MAX, result); + if (result.length() > len) + result += SORT_SUB_SEP; +} + +void reference::sortify_field(unsigned char f, int n, string &result) const +{ + typedef void (*sortify_t)(const char *, int, string &); + sortify_t sortifier = sortify_other; + switch (f) { + case 'A': + case 'E': + sortifier = sortify_name; + break; + case 'D': + sortifier = sortify_date; + break; + case 'B': + case 'J': + case 'T': + sortifier = sortify_title; + break; + } + int fi = field_index[(unsigned char)f]; + if (fi != NULL_FIELD_INDEX) { + string &str = field[fi]; + const char *start = str.contents(); + const char *end = start + str.length(); + for (int i = 0; i < n && start < end; i++) { + const char *p = start; + while (start < end && *start != FIELD_SEPARATOR) + start++; + if (i > 0) + result += SORT_SUB_SEP; + (*sortifier)(p, start - p, result); + if (start < end) + start++; + } + } +} + +int compare_reference(const reference &r1, const reference &r2) +{ + assert(r1.no >= 0); + assert(r2.no >= 0); + const char *s1 = r1.sort_key.contents(); + int n1 = r1.sort_key.length(); + const char *s2 = r2.sort_key.contents(); + int n2 = r2.sort_key.length(); + for (; n1 > 0 && n2 > 0; --n1, --n2, ++s1, ++s2) + if (*s1 != *s2) + return (int)(unsigned char)*s1 - (int)(unsigned char)*s2; + if (n2 > 0) + return -1; + if (n1 > 0) + return 1; + return r1.no - r2.no; +} + +int same_reference(const reference &r1, const reference &r2) +{ + if (!r1.rid.is_null() && r1.rid == r2.rid) + return 1; + if (r1.h != r2.h) + return 0; + if (r1.nfields != r2.nfields) + return 0; + int i = 0; + for (i = 0; i < 256; i++) + if (r1.field_index != r2.field_index) + return 0; + for (i = 0; i < r1.nfields; i++) + if (r1.field[i] != r2.field[i]) + return 0; + return 1; +} + +const char *find_last_name(const char *start, const char *end, + const char **endp) +{ + const char *ptr = start; + const char *last_word = start; + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, end)) + break; + if (ptr - token_start == 1) { + if (*token_start == ',') { + *endp = token_start; + return last_word; + } + else if (*token_start == ' ' || *token_start == '\n') { + if (ptr < end && *ptr != ' ' && *ptr != '\n') + last_word = ptr; + } + } + } + *endp = end; + return last_word; +} + +void abbreviate_name(const char *ptr, const char *end, string &result) +{ + const char *last_name_end; + const char *last_name_start = find_last_name(ptr, end, &last_name_end); + int need_period = 0; + for (;;) { + const char *token_start = ptr; + if (!get_token(&ptr, last_name_start)) + break; + const token_info *ti = lookup_token(token_start, ptr); + if (need_period) { + if ((ptr - token_start == 1 && *token_start == ' ') + || (ptr - token_start == 2 && token_start[0] == '\\' + && token_start[1] == ' ')) + continue; + if (ti->is_upper()) + result += period_before_initial; + else + result += period_before_other; + need_period = 0; + } + result.append(token_start, ptr - token_start); + if (ti->is_upper()) { + const char *lower_ptr = ptr; + int first_token = 1; + for (;;) { + token_start = ptr; + if (!get_token(&ptr, last_name_start)) + break; + if ((ptr - token_start == 1 && *token_start == ' ') + || (ptr - token_start == 2 && token_start[0] == '\\' + && token_start[1] == ' ')) + break; + ti = lookup_token(token_start, ptr); + if (ti->is_hyphen()) { + const char *ptr1 = ptr; + if (get_token(&ptr1, last_name_start)) { + ti = lookup_token(ptr, ptr1); + if (ti->is_upper()) { + result += period_before_hyphen; + result.append(token_start, ptr1 - token_start); + ptr = ptr1; + } + } + } + else if (ti->is_upper()) { + // MacDougal -> MacD. + result.append(lower_ptr, ptr - lower_ptr); + lower_ptr = ptr; + first_token = 1; + } + else if (first_token && ti->is_accent()) { + result.append(token_start, ptr - token_start); + lower_ptr = ptr; + } + first_token = 0; + } + need_period = 1; + } + } + if (need_period) + result += period_before_last_name; + result.append(last_name_start, end - last_name_start); +} + +static void abbreviate_names(string &result) +{ + string str; + str.move(result); + const char *ptr = str.contents(); + const char *end = ptr + str.length(); + while (ptr < end) { + const char *name_end = (char *)memchr(ptr, FIELD_SEPARATOR, end - ptr); + if (name_end == 0) + name_end = end; + abbreviate_name(ptr, name_end, result); + if (name_end >= end) + break; + ptr = name_end + 1; + result += FIELD_SEPARATOR; + } +} + +void reverse_name(const char *ptr, const char *name_end, string &result) +{ + const char *last_name_end; + const char *last_name_start = find_last_name(ptr, name_end, &last_name_end); + result.append(last_name_start, last_name_end - last_name_start); + while (last_name_start > ptr + && (last_name_start[-1] == ' ' || last_name_start[-1] == '\n')) + last_name_start--; + if (last_name_start > ptr) { + result += ", "; + result.append(ptr, last_name_start - ptr); + } + if (last_name_end < name_end) + result.append(last_name_end, name_end - last_name_end); +} + +void reverse_names(string &result, int n) +{ + if (n <= 0) + return; + string str; + str.move(result); + const char *ptr = str.contents(); + const char *end = ptr + str.length(); + while (ptr < end) { + if (--n < 0) { + result.append(ptr, end - ptr); + break; + } + const char *name_end = (char *)memchr(ptr, FIELD_SEPARATOR, end - ptr); + if (name_end == 0) + name_end = end; + reverse_name(ptr, name_end, result); + if (name_end >= end) + break; + ptr = name_end + 1; + result += FIELD_SEPARATOR; + } +} + +// Return number of field separators. + +int join_fields(string &f) +{ + const char *ptr = f.contents(); + int len = f.length(); + int nfield_seps = 0; + int j; + for (j = 0; j < len; j++) + if (ptr[j] == FIELD_SEPARATOR) + nfield_seps++; + if (nfield_seps == 0) + return 0; + string temp; + int field_seps_left = nfield_seps; + for (j = 0; j < len; j++) { + if (ptr[j] == FIELD_SEPARATOR) { + if (nfield_seps == 1) + temp += join_authors_exactly_two; + else if (--field_seps_left == 0) + temp += join_authors_last_two; + else + temp += join_authors_default; + } + else + temp += ptr[j]; + } + f = temp; + return nfield_seps; +} + +void uppercase(const char *start, const char *end, string &result) +{ + for (;;) { + const char *token_start = start; + if (!get_token(&start, end)) + break; + const token_info *ti = lookup_token(token_start, start); + ti->upper_case(token_start, start, result); + } +} + +void lowercase(const char *start, const char *end, string &result) +{ + for (;;) { + const char *token_start = start; + if (!get_token(&start, end)) + break; + const token_info *ti = lookup_token(token_start, start); + ti->lower_case(token_start, start, result); + } +} + +void capitalize(const char *ptr, const char *end, string &result) +{ + int in_small_point_size = 0; + for (;;) { + const char *start = ptr; + if (!get_token(&ptr, end)) + break; + const token_info *ti = lookup_token(start, ptr); + const char *char_end = ptr; + int is_lower = ti->is_lower(); + if ((is_lower || ti->is_upper()) && get_token(&ptr, end)) { + const token_info *ti2 = lookup_token(char_end, ptr); + if (!ti2->is_accent()) + ptr = char_end; + } + if (is_lower) { + if (!in_small_point_size) { + result += "\\s-2"; + in_small_point_size = 1; + } + ti->upper_case(start, char_end, result); + result.append(char_end, ptr - char_end); + } + else { + if (in_small_point_size) { + result += "\\s+2"; + in_small_point_size = 0; + } + result.append(start, ptr - start); + } + } + if (in_small_point_size) + result += "\\s+2"; +} + +void capitalize_field(string &str) +{ + string temp; + capitalize(str.contents(), str.contents() + str.length(), temp); + str.move(temp); +} + +int is_terminated(const char *ptr, const char *end) +{ + const char *last_token = end; + for (;;) { + const char *p = ptr; + if (!get_token(&ptr, end)) + break; + last_token = p; + } + return end - last_token == 1 + && (*last_token == '.' || *last_token == '!' || *last_token == '?'); +} + +void reference::output(FILE *fp) +{ + fputs(".]-\n", fp); + for (int i = 0; i < 256; i++) + if (field_index[i] != NULL_FIELD_INDEX && i != annotation_field) { + string &f = field[field_index[i]]; + if (!csdigit(i)) { + int j = reverse_fields.search(i); + if (j >= 0) { + int n; + int len = reverse_fields.length(); + if (++j < len && csdigit(reverse_fields[j])) { + n = reverse_fields[j] - '0'; + for (++j; j < len && csdigit(reverse_fields[j]); j++) + // should check for overflow + n = n*10 + reverse_fields[j] - '0'; + } + else + n = INT_MAX; + reverse_names(f, n); + } + } + int is_multiple = join_fields(f) > 0; + if (capitalize_fields.search(i) >= 0) + capitalize_field(f); + if (memchr(f.contents(), '\n', f.length()) == 0) { + fprintf(fp, ".ds [%c ", i); + if (f[0] == ' ' || f[0] == '\\' || f[0] == '"') + putc('"', fp); + put_string(f, fp); + putc('\n', fp); + } + else { + fprintf(fp, ".de [%c\n", i); + put_string(f, fp); + fputs("..\n", fp); + } + if (i == 'P') { + int multiple_pages = 0; + const char *s = f.contents(); + const char *end = f.contents() + f.length(); + for (;;) { + const char *token_start = s; + if (!get_token(&s, end)) + break; + const token_info *ti = lookup_token(token_start, s); + if (ti->is_hyphen() || ti->is_range_sep()) { + multiple_pages = 1; + break; + } + } + fprintf(fp, ".nr [P %d\n", multiple_pages); + } + else if (i == 'E') + fprintf(fp, ".nr [E %d\n", is_multiple); + } + for (const char *p = "TAO"; *p; p++) { + int fi = field_index[(unsigned char)*p]; + if (fi != NULL_FIELD_INDEX) { + string &f = field[fi]; + fprintf(fp, ".nr [%c %d\n", *p, + is_terminated(f.contents(), f.contents() + f.length())); + } + } + int t = classify(); + fprintf(fp, ".][ %d %s\n", t, reference_types[t]); + if (annotation_macro.length() > 0 && annotation_field >= 0 + && field_index[annotation_field] != NULL_FIELD_INDEX) { + putc('.', fp); + put_string(annotation_macro, fp); + putc('\n', fp); + put_string(field[field_index[annotation_field]], fp); + } +} + +void reference::print_sort_key_comment(FILE *fp) +{ + fputs(".\\\"", fp); + put_string(sort_key, fp); + putc('\n', fp); +} + +const char *find_year(const char *start, const char *end, const char **endp) +{ + for (;;) { + while (start < end && !csdigit(*start)) + start++; + const char *ptr = start; + if (start == end) + break; + while (ptr < end && csdigit(*ptr)) + ptr++; + if (ptr - start == 4 || ptr - start == 3 + || (ptr - start == 2 + && (start[0] >= '4' || (start[0] == '3' && start[1] >= '2')))) { + *endp = ptr; + return start; + } + start = ptr; + } + return 0; +} + +static const char *find_day(const char *start, const char *end, + const char **endp) +{ + for (;;) { + while (start < end && !csdigit(*start)) + start++; + const char *ptr = start; + if (start == end) + break; + while (ptr < end && csdigit(*ptr)) + ptr++; + if ((ptr - start == 1 && start[0] != '0') + || (ptr - start == 2 && + (start[0] == '1' + || start[0] == '2' + || (start[0] == '3' && start[1] <= '1') + || (start[0] == '0' && start[1] != '0')))) { + *endp = ptr; + return start; + } + start = ptr; + } + return 0; +} + +static int find_month(const char *start, const char *end) +{ + static const char *months[] = { + "january", + "february", + "march", + "april", + "may", + "june", + "july", + "august", + "september", + "october", + "november", + "december", + }; + for (;;) { + while (start < end && !csalpha(*start)) + start++; + const char *ptr = start; + if (start == end) + break; + while (ptr < end && csalpha(*ptr)) + ptr++; + if (ptr - start >= 3) { + for (unsigned int i = 0; i < sizeof(months)/sizeof(months[0]); i++) { + const char *q = months[i]; + const char *p = start; + for (; p < ptr; p++, q++) + if (cmlower(*p) != *q) + break; + if (p >= ptr) + return i; + } + } + start = ptr; + } + return -1; +} + +int reference::contains_field(char c) const +{ + return field_index[(unsigned char)c] != NULL_FIELD_INDEX; +} + +int reference::classify() +{ + if (contains_field('J')) + return JOURNAL_ARTICLE; + if (contains_field('B')) + return ARTICLE_IN_BOOK; + if (contains_field('G')) + return TECH_REPORT; + if (contains_field('R')) + return TECH_REPORT; + if (contains_field('I')) + return BOOK; + if (contains_field('M')) + return BELL_TM; + return OTHER; +} + +const char *reference::get_year(const char **endp) const +{ + if (field_index['D'] != NULL_FIELD_INDEX) { + string &date = field[field_index['D']]; + const char *start = date.contents(); + const char *end = start + date.length(); + return find_year(start, end, endp); + } + else + return 0; +} + +const char *reference::get_field(unsigned char c, const char **endp) const +{ + if (field_index[c] != NULL_FIELD_INDEX) { + string &f = field[field_index[c]]; + const char *start = f.contents(); + *endp = start + f.length(); + return start; + } + else + return 0; +} + +const char *reference::get_date(const char **endp) const +{ + return get_field('D', endp); +} + +const char *nth_field(int i, const char *start, const char **endp) +{ + while (--i >= 0) { + start = (char *)memchr(start, FIELD_SEPARATOR, *endp - start); + if (!start) + return 0; + start++; + } + const char *e = (char *)memchr(start, FIELD_SEPARATOR, *endp - start); + if (e) + *endp = e; + return start; +} + +const char *reference::get_author(int i, const char **endp) const +{ + for (const char *f = AUTHOR_FIELDS; *f != '\0'; f++) { + const char *start = get_field(*f, endp); + if (start) { + if (strchr(MULTI_FIELD_NAMES, *f) != 0) + return nth_field(i, start, endp); + else if (i == 0) + return start; + else + return 0; + } + } + return 0; +} + +const char *reference::get_author_last_name(int i, const char **endp) const +{ + for (const char *f = AUTHOR_FIELDS; *f != '\0'; f++) { + const char *start = get_field(*f, endp); + if (start) { + if (strchr(MULTI_FIELD_NAMES, *f) != 0) { + start = nth_field(i, start, endp); + if (!start) + return 0; + } + if (*f == 'A') + return find_last_name(start, *endp, endp); + else + return start; + } + } + return 0; +} + +void reference::set_date(string &d) +{ + if (d.length() == 0) + delete_field('D'); + else + insert_field('D', d); +} + +int same_year(const reference &r1, const reference &r2) +{ + const char *ye1; + const char *ys1 = r1.get_year(&ye1); + const char *ye2; + const char *ys2 = r2.get_year(&ye2); + if (ys1 == 0) { + if (ys2 == 0) + return same_date(r1, r2); + else + return 0; + } + else if (ys2 == 0) + return 0; + else if (ye1 - ys1 != ye2 - ys2) + return 0; + else + return memcmp(ys1, ys2, ye1 - ys1) == 0; +} + +int same_date(const reference &r1, const reference &r2) +{ + const char *e1; + const char *s1 = r1.get_date(&e1); + const char *e2; + const char *s2 = r2.get_date(&e2); + if (s1 == 0) + return s2 == 0; + else if (s2 == 0) + return 0; + else if (e1 - s1 != e2 - s2) + return 0; + else + return memcmp(s1, s2, e1 - s1) == 0; +} + +const char *reference::get_sort_field(int i, int si, int ssi, + const char **endp) const +{ + const char *start = sort_key.contents(); + const char *end = start + sort_key.length(); + if (i < 0) { + *endp = end; + return start; + } + while (--i >= 0) { + start = (char *)memchr(start, SORT_SEP, end - start); + if (!start) + return 0; + start++; + } + const char *e = (char *)memchr(start, SORT_SEP, end - start); + if (e) + end = e; + if (si < 0) { + *endp = end; + return start; + } + while (--si >= 0) { + start = (char *)memchr(start, SORT_SUB_SEP, end - start); + if (!start) + return 0; + start++; + } + e = (char *)memchr(start, SORT_SUB_SEP, end - start); + if (e) + end = e; + if (ssi < 0) { + *endp = end; + return start; + } + while (--ssi >= 0) { + start = (char *)memchr(start, SORT_SUB_SUB_SEP, end - start); + if (!start) + return 0; + start++; + } + e = (char *)memchr(start, SORT_SUB_SUB_SEP, end - start); + if (e) + end = e; + *endp = end; + return start; +} + diff --git a/src/preproc/refer/ref.h b/src/preproc/refer/ref.h new file mode 100644 index 0000000..1205a28 --- /dev/null +++ b/src/preproc/refer/ref.h @@ -0,0 +1,127 @@ +// -*- 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/>. */ + +// declarations to avoid friend name injection problems +int compare_reference(const reference &, const reference &); +int same_reference(const reference &, const reference &); +int same_year(const reference &, const reference &); +int same_date(const reference &, const reference &); +int same_author_last_name(const reference &, const reference &, int); +int same_author_name(const reference &, const reference &, int); + +struct label_info; + +enum label_type { NORMAL_LABEL, SHORT_LABEL }; +const int N_LABEL_TYPES = 2; + +struct substring_position { + int start; + int length; + substring_position() : start(-1) { } +}; + +class int_set { + string v; +public: + int_set() { } + void set(int i); + int get(int i) const; +}; + +class reference { +private: + unsigned h; + reference_id rid; + int merged; + string sort_key; + int no; + string *field; + int nfields; + unsigned char field_index[256]; + enum { NULL_FIELD_INDEX = 255 }; + string label; + substring_position separator_pos; + string short_label; + substring_position short_separator_pos; + label_info *label_ptr; + string authors; + int computed_authors; + int last_needed_author; + int nauthors; + int_set last_name_unambiguous; + + int contains_field(char) const; + void insert_field(unsigned char, string &s); + void delete_field(unsigned char); + void set_date(string &); + const char *get_sort_field(int i, int si, int ssi, const char **endp) const; + int merge_labels_by_parts(reference **, int, label_type, string &); + int merge_labels_by_number(reference **, int, label_type, string &); +public: + reference(const char * = 0, int = -1, reference_id * = 0); + ~reference(); + void output(FILE *); + void print_sort_key_comment(FILE *); + void set_number(int); + int get_number() const { return no; } + unsigned hash() const { return h; } + const string &get_label(label_type type) const; + const substring_position &get_separator_pos(label_type) const; + int is_merged() const { return merged; } + void compute_sort_key(); + void compute_hash_code(); + void pre_compute_label(); + void compute_label(); + void immediate_compute_label(); + int classify(); + void merge(reference &); + int merge_labels(reference **, int, label_type, string &); + int get_nauthors() const; + void need_author(int); + void set_last_name_unambiguous(int); + void sortify_authors(int, string &) const; + void canonicalize_authors(string &) const; + void sortify_field(unsigned char, int, string &) const; + const char *get_author(int, const char **) const; + const char *get_author_last_name(int, const char **) const; + const char *get_date(const char **) const; + const char *get_year(const char **) const; + const char *get_field(unsigned char, const char **) const; + const label_info *get_label_ptr() const { return label_ptr; } + const char *get_authors(const char **) const; + // for sorting + friend int compare_reference(const reference &r1, const reference &r2); + // for merging + friend int same_reference(const reference &, const reference &); + friend int same_year(const reference &, const reference &); + friend int same_date(const reference &, const reference &); + friend int same_author_last_name(const reference &, const reference &, int); + friend int same_author_name(const reference &, const reference &, int); +}; + +const char *find_year(const char *, const char *, const char **); +const char *find_last_name(const char *, const char *, const char **); + +const char *nth_field(int i, const char *start, const char **endp); + +void capitalize(const char *ptr, const char *end, string &result); +void reverse_name(const char *ptr, const char *end, string &result); +void uppercase(const char *ptr, const char *end, string &result); +void lowercase(const char *ptr, const char *end, string &result); +void abbreviate_name(const char *ptr, const char *end, string &result); diff --git a/src/preproc/refer/refer.1.man b/src/preproc/refer/refer.1.man new file mode 100644 index 0000000..210afe7 --- /dev/null +++ b/src/preproc/refer/refer.1.man @@ -0,0 +1,2020 @@ +.TH @g@refer @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +@g@refer \- process bibliographic references for +.I groff +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1989-2021 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_refer_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@refer +.RB [ \-bCenPRS ] +.RB [ \-a\~\c +.IR n ] +.RB [ \-B +.IB field . macro\c +] +.RB [ \-c\~\c +.IR fields ] +.RB [ \-f\~\c +.IR n ] +.RB [ \-i\~\c +.IR fields ] +.RB [ \-k\~\c +.IR field ] +.RB [ \-l\~\c +.IR range-expression ] +.RB [ \-p\~\c +.IR database-file ] +.RB [ \-s\~\c +.IR fields ] +.RB [ \-t\~\c +.IR n ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY @g@refer +.B \-\-help +.YS +. +. +.SY @g@refer +.B \-v +. +.SY @g@refer +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU implementation of +.I \%refer \" generic +is part of the +.MR groff @MAN1EXT@ +document formatting system. +. +.I @g@refer +is a +.MR @g@troff @MAN1EXT@ +preprocessor that prepares bibilographic citations by looking up +keywords specified in a +.MR roff @MAN7EXT@ +input document, +obviating the need to type such annotations, +and permitting the citation style in formatted output to be altered +independently and systematically. +. +It copies the contents of each +.I file +to the standard output stream, +except that it interprets lines between +.B .[ +and +.B .]\& +as citations to be translated into +.I groff +input, +and lines between +.B .R1 +and +.B .R2 +as instructions regarding how citations are to be processed. +. +Normally, +.I @g@refer +is not executed directly by the user, +but invoked by specifying the +.B \-R +option to +.MR groff @MAN1EXT@ . +. +If no +.I file +operands are given on the command line, +or if +.I file +is +.RB \[lq] \- \[rq], +the standard input stream is read. +. +. +.LP +Each citation specifies a reference. +. +The citation can specify a reference that is contained in a +bibliographic database by giving a set of keywords that only that +reference contains. +. +Alternatively it can specify a reference by supplying a database record +in the citation. +. +A combination of these alternatives is also possible. +. +. +.LP +For each citation, +.I @g@refer +can produce a mark in the text. +. +This mark consists of some label which can be separated from the text +and from other labels in various ways. +. +For each reference it also outputs +.MR groff @MAN7EXT@ +language commands that can be used by a macro package to produce a +formatted reference for each citation. +. +The output of +.I @g@refer +must therefore be processed using a suitable macro package, +such as +.\" .IR man , +.IR me , +.IR mm , +.IR mom , +or +.IR ms . +. +The commands to format a citation's reference can be output immediately +after the citation, +or the references may be accumulated, +and the commands output at some later point. +. +If the references are accumulated, +then multiple citations of the same reference will produce a single +formatted reference. +. +. +.LP +The interpretation of lines between +.B .R1 +and +.B .R2 +as prepreocessor commands is a feature of GNU +.IR \%refer . \" GNU +. +Documents making use of this feature can still be processed by AT&T +.I \%refer \" AT&T +just by adding the lines +. +.RS +.EX +\&.de R1 +\&.ig R2 +\&.. +.EE +.RE +. +to the beginning of the document. +. +This will cause +.MR @g@troff @MAN1EXT@ +to ignore everything between +.B .R1 +and +.BR .R2 . +. +The effect of some commands can also be achieved by options. +. +These options are supported mainly for compatibility with AT&T +.IR \%refer . \" AT&T +. +It is usually more convenient to use commands. +. +. +.LP +.I @g@refer +generates +.B .lf +requests so that file names and line numbers in messages produced by +commands that read +.I @g@refer +output will be correct; +it also interprets lines beginning with +.B .lf +so that file names and line numbers in the messages and +.B .lf +lines that it produces will be accurate even if the input has been +preprocessed by a command such as +.MR @g@soelim @MAN1EXT@ . +. +. +.\" ==================================================================== +.SS "Bibliographic databases" +.\" ==================================================================== +. +The bibliographic database is a text file consisting of records +separated by one or more blank lines. +. +Within each record fields start with a +.B % +at the beginning of a line. +. +Each field has a one character name that immediately follows the +.BR % . +It is best to use only upper and lower case letters for the names +of fields. +. +The name of the field should be followed by exactly one space, +and then by the contents of the field. +. +Empty fields are ignored. +. +The conventional meaning of each field is as follows: +. +. +.TP +.B %A +The name of an author. +. +If the name contains a suffix such as \[lq]Jr.\&\[rq], +it should be separated from the last name by a comma. +. +There can be multiple occurrences of the +.B %A +field. +. +The order is significant. +. +It is a good idea always to supply an +.B %A +field or a +.B %Q +field. +. +. +.TP +.B %B +For an article that is part of a book, +the title of the book. +. +. +.TP +.B %C +The place (city) of publication. +. +. +.TP +.B %D +The date of publication. +. +The year should be specified in full. +. +If the month is specified, +the name rather than the number of the month should be used, +but only the first three letters are required. +. +It is a good idea always to supply a +.B %D +field; +if the date is unknown, +a value such as +.B in press +or +.B unknown +can be used. +. +. +.TP +.B %E +For an article that is part of a book, +the name of an editor of the book. +. +Where the work has editors and no authors, +the names of the editors should be given as +.B %A +fields and +.RB \[lq] ,\~(ed.)\& \[rq] +or +.RB \[lq] ,\~(eds.)\& \[rq] +should be appended to the last author. +. +. +.TP +.B %G +U.S. government ordering number. +. +. +.TP +.B %I +The publisher (issuer). +. +. +.TP +.B %J +For an article in a journal, +the name of the journal. +. +. +.TP +.B %K +Keywords to be used for searching. +. +. +.TP +.B %L +Label. +. +. +.TP +.B %N +Journal issue number. +. +. +.TP +.B %O +Other information. +. +This is usually printed at the end of the reference. +. +. +.TP +.B %P +Page number. +. +A range of pages can be specified as +.IB m \- \c +.IR n . +. +. +.TP +.B %Q +The name of the author, +if the author is not a person. +. +This will only be used if there are no +.B %A +fields. +. +There can only be one +.B %Q +field. +. +. +.TP +.B %R +Technical report number. +. +. +.TP +.B %S +Series name. +. +. +.TP +.B %T +Title. +. +For an article in a book or journal, +this should be the title of the article. +. +. +.TP +.B %V +Volume number of the journal or book. +. +. +.TP +.B %X +Annotation. +. +. +.LP +For all fields except +.B %A +and +.BR %E , +if there is more than one occurrence of a particular field in a record, +only the last such field will be used. +. +. +.P +If accent strings are used, +they should follow the character to be accented. +. +This means that an +.I ms +document must call the +.B .AM +macro when it initializes. +. +Accent strings should not be quoted: +use one +.B \e +rather than two. +. +Accent strings are an obsolescent feature of the +.I me +and +.I ms +macro packages; +modern documents should use +.I groff +special character escape sequences instead; +see +.MR groff_char @MAN7EXT@ . +. +. +.\" ==================================================================== +.SS Citations +.\" ==================================================================== +. +Citations have a characteristic format. +. +.RS +.EX +.BI .[ opening-text +.I flags keywords +.I fields +.BI .] closing-text +.EE +.RE +. +. +.LP +The +.IR opening-text , +.IR closing-text , +and +.I flags +components are optional. +. +Only one of the +.I keywords +and +.I fields +components need be specified. +. +. +.LP +The +.I keywords +component says to search the bibliographic databases for a reference +that contains all the words in +.IR keywords . +. +It is an error if more than one reference is found. +. +. +.LP +The +.I fields +components specifies additional fields to replace or supplement those +specified in the reference. +. +When references are being accumulated and the +.I keywords +component is non-empty, +then additional fields should be specified only on the first occasion +that a particular reference is cited, +and will apply to all citations of that reference. +. +. +.br +.ne 2v +.LP +The +.I opening-text +and +.I closing-text +components specify strings to be used to bracket the label instead of +those in the +.B \%bracket\-label +command. +. +If either of these components is non-empty, +the strings specified in the +.B \%bracket\-label +command will not be used; +this behavior can be altered using the +.B [ +and +.B ] +flags. +. +Leading and trailing spaces are significant for these components. +. +. +.LP +The +.I flags +component is a list of non-alphanumeric characters each of which +modifies the treatment of this particular citation. +. +AT&T +.I \%refer \" AT&T +will treat these flags as part of the keywords and so will ignore them +since they are non-alphanumeric. +. +The following flags are currently recognized. +. +. +.TP +.B # +Use the label specified by the +.B \%short\-label +command, +instead of that specified by the +.B \%label +command. +. +If no short label has been specified, +the normal label will be used. +. +Typically the short label is used with author-date labels and consists +of only the date and possibly a disambiguating letter; +the +.RB \[lq] # \[rq] +is supposed to be suggestive of a numeric type of label. +. +. +.TP +.B [ +Precede +.I opening-text +with the first string specified in the +.B \%bracket\-label +command. +. +. +.TP +.B ] +Follow +.I closing-text +with the second string specified in the +.B \%bracket\-label +command. +. +. +.LP +An advantage of using the +.B [ +and +.B ] +flags rather than including the brackets in +.I opening-text +and +.I closing-text +is that +. +you can change the style of bracket used in the document just by +changing the +.B \%bracket\-label +command. +. +Another is that sorting and merging of citations will not necessarily be +inhibited if the flags are used. +. +. +.LP +If a label is to be inserted into the text, +it will be attached to the line preceding the +.B .[ +line. +. +If there is no such line, +then an extra line will be inserted before the +.B .[ +line and a warning will be given. +. +. +.LP +There is no special notation for making a citation to multiple +references. +. +Just use a sequence of citations, +one for each reference. +. +Don't put anything between the citations. +. +The labels for all the citations will be attached to the line preceding +the first citation. +. +The labels may also be sorted or merged. +. +See the description of the +.B <> +label expression, +and of the +.B \%sort\-adjacent\-labels +and +.B \%abbreviate\-label\-ranges +commands. +. +A label will not be merged if its citation has a non-empty +.I opening-text +or +.IR closing-text . +. +However, +the labels for a citation using the +.B ] +flag and without any +.I closing-text +immediately followed by a citation using the +.B [ +flag and without any +.I opening-text +may be sorted and merged +even though the first citation's +.I opening-text +or the second citation's +.I closing-text +is non-empty. +. +(If you wish to prevent this, +use the dummy character escape sequence +.B \[rs]& +as the first citation's +.IR closing-text .) +. +. +.\" ==================================================================== +.SS Commands +.\" ==================================================================== +. +Commands are contained between lines starting with +.B .R1 +and +.BR .R2 . +. +Recognition of these lines can be prevented by the +.B \-R +option. +. +When a +.B .R1 +line is recognized any accumulated references are flushed out. +. +Neither +.B .R1 +nor +.B .R2 +lines, +nor anything between them, +is output. +. +. +.P +Commands are separated by newlines or semicolons. +. +A number sign +.RB ( # ) +introduces a comment that extends to the end of the line, +but does not conceal the newline. +. +Each command is broken up into words. +. +Words are separated by spaces or tabs. +. +A word that begins with a (neutral) double quote +.RB ( \[dq] ) +extends to the next double quote that is not followed by another double +quote. +. +If there is no such double quote, +the word extends to the end of the line. +. +Pairs of double quotes in a word beginning with a double quote collapse +to one double quote. +. +Neither a number sign nor a semicolon is recognized inside double +quotes. +. +A line can be continued by ending it with a backslash +.RB \[lq] \[rs] \[rq]; +this works everywhere except after a number sign. +. +. +.LP +.ds n \fR*\fP\" +Each command +.I name +that is marked with \*n has an associated negative command +.BI no\- name +that undoes the effect of +.IR name . +. +For example, +the +.B no\-sort +command specifies that references should not be sorted. +. +The negative commands take no arguments. +. +. +.LP +In the following description each argument must be a single word; +.I field +is used for a single upper or lower case letter naming a field; +.I fields +is used for a sequence of such letters; +.I m +and +.I n +are used for a non-negative numbers; +.I string +is used for an arbitrary string; +.I file +is used for the name of a file. +. +. +.TP +.BI abbreviate\*n\~ fields\~string1\~string2\~string3\~string4 +Abbreviate the first names of +.IR fields . +. +An initial letter will be separated from another initial letter by +.IR string1 , +from the last name by +.IR string2 , +and from anything else +(such as \[lq]von\[rq] or \[lq]de\[rq]) +by +.IR string3 . +. +These default to a period followed by a space. +. +In a hyphenated first name, +the initial of the first part of the name will be separated from the +hyphen by +.IR string4 ; +this defaults to a period. +. +No attempt is made to handle any ambiguities that might +result from abbreviation. +. +Names are abbreviated before sorting and before label construction. +. +. +.TP +.BI abbreviate\-label\-ranges\*n\~ string +. +Three or more adjacent labels that refer to consecutive references +will be abbreviated to a label consisting of the first label, +followed by +.IR string , +followed by the last label. +. +This is mainly useful with numeric labels. +. +If +.I string +is omitted, +it defaults to +.RB \[lq] \- \[rq]. +. +. +.TP +.B accumulate\*n +Accumulate references instead of writing out each reference +as it is encountered. +. +Accumulated references will be written out whenever a reference +of the form +. +.RS +.RS +.EX +.B .[ +.B $LIST$ +.B .] +.EE +.RE +. +is encountered, +after all input files have been processed, +and whenever a +.B .R1 +line is recognized. +.RE +. +. +.TP +.BI annotate\*n\~ "field string" +.I field +is an annotation; +print it at the end of the reference as a paragraph preceded by the line +. +.RS +.IP +.BI . string +. +. +.LP +If +.I string +is omitted, +it will default to +.BR AP ; +if +.I field +is also omitted it will default to +.BR X . +. +Only one field can be an annotation. +.RE +. +. +.TP +.BI articles\~ string\~\c +\&.\|.\|. +Each +.I string +is a definite or indefinite article, +and should be ignored at the beginning of +.B T +fields when sorting. +. +Initially, +\[lq]a\[rq], +\[lq]an\[rq], +and +\[lq]the\[rq] are recognized as articles. +. +. +.TP +.BI bibliography\~ file\~\c +\&.\|.\|. +. +Write out all the references contained in each bibliographic database +.IR file . +. +This command should come last in an +.BR .R1 / .R2 +block. +. +. +.TP +.BI bracket\-label\~ "string1 string2 string3" +In the text, +bracket each label with +.I string1 +and +.IR string2 . +. +An occurrence of +.I string2 +immediately followed by +.I string1 +will be turned into +.IR string3 . +. +The default behavior is as follows. +. +.RS \" RS twice to get inboard of the tagged paragraph indentation. +.RS +.EX +.B bracket\-label \e*([. \e*(.] \[dq], \[dq] +.EE +.RE +.RE +. +. +.TP +.BI capitalize\~ fields +Convert +.I fields +to caps and small caps. +. +. +.TP +.B compatible\*n +Recognize +.B .R1 +and +.B .R2 +even when followed by a character other than space or newline. +. +. +.TP +.BI database\~ file\~\c +\&.\|.\|. +Search each bibliographic database +.IR file . +. +For each +.IR file , +if an index +.RI file @INDEX_SUFFIX@ +created by +.MR @g@indxbib @MAN1EXT@ +exists, +then it will be searched instead; +each index can cover multiple databases. +. +. +.TP +.BI date\-as\-label\*n\~ string +.I string +is a label expression that specifies a string with which to replace the +.B D +field after constructing the label. +. +See subsection \[lq]Label expressions\[rq] below for a description of +label expressions. +. +This command is useful if you do not want explicit labels in the +reference list, +but instead want to handle any necessary disambiguation by qualifying +the date in some way. +. +The label used in the text would typically be some combination of the +author and date. +. +In most cases you should also use the +.B \%no\-label\-in\-reference +command. +. +For example, +. +.RS \" RS twice to get inboard of the tagged paragraph indentation. +.RS +.EX +.B date\-as\-label D.+yD.y%a*D.\-y +.EE +.RE +. +would attach a disambiguating letter to the year part of the +.B D +field in the reference. +.RE +. +. +.TP +.B default\-database\*n +The default database should be searched. +. +This is the default behavior, +so the negative version of this command is more useful. +. +.I @g@refer +determines whether the default database should be searched +on the first occasion that it needs to do a search. +. +Thus a +.B \%no\-default\-database +command must be given before then, +in order to be effective. +. +. +.TP +.BI discard\*n\~ fields +When the reference is read, +.I fields +should be discarded; +no string definitions for +.I fields +will be output. +. +Initially, +.I fields +are +.BR XYZ . +. +. +.TP +.BI et\-al\*n\~ "string m n" +Control use of +.B et al.\& +in the evaluation of +.B @ +expressions in label expressions. +. +If the number of authors needed to make the author sequence unambiguous +is +.I u +and the total number of authors is +.I t +then the last +.IR t \|\-\| u +authors will be replaced by +.I string +provided that +.IR t \|\-\| u +is not less than +.I m +and +.I t +is not less than +.IR n . +. +The default behavior is as follows. +. +.RS \" RS twice to get inboard of the tagged paragraph indentation. +.RS +.EX +.B et\-al \[dq] et al\[dq] 2 3 +.EE +.RE +. +Note the absence of a dot from the end of the abbreviation, +which is arguably not correct. +. +.RI ( "Et al" [.] +is short for +.IR "et alli" , +as +.I etc.\& +is short for +.IR "et cetera".) +.RE +. +. +.TP +.BI include\~ file +Include +.I file +and interpret the contents as commands. +. +. +.TP +.BI join\-authors\~ "string1 string2 string3" +Join multiple authors together with +.IR string s. +. +When there are exactly two authors, +they will be joined with +.IR string1 . +. +When there are more than two authors, +all but the last two will be joined with +.IR string2 , +and the last two authors will be joined with +.IR string3 . +. +If +.I string3 +is omitted, +it will default to +.IR string1 ; +if +.I string2 +is also omitted it will also default to +.IR string1 . +. +For example, +. +.RS +.RS +.EX +join\-authors \[dq] and \[dq] \[dq], \[dq] \[dq], and \[dq] +.EE +.RE +. +will restore the default method for joining authors. +.RE +. +. +.TP +.B label\-in\-reference\*n +When outputting the reference, +define the string +.B [F +to be the reference's label. +. +This is the default behavior, +so the negative version of this command is more useful. +. +. +.TP +.B label\-in\-text\*n +For each reference output a label in the text. +. +The label will be separated from the surrounding text as described in +the +.B \%bracket\-label +command. +. +This is the default behavior, +so the negative version of this command is more useful. +. +. +.TP +.BI label\~ string +.I string +is a label expression describing how to label each reference. +. +. +.TP +.BI separate\-label\-second\-parts\~ string +When merging two-part labels, +separate the second part of the second label from the first label with +.IR string . +. +See the description of the +.B <> +label expression. +. +. +.TP +.B move\-punctuation\*n +In the text, +move any punctuation at the end of line past the label. +. +It is usually a good idea to give this command unless you are using +superscripted numbers as labels. +. +. +.TP +.BI reverse\*n\~ string +Reverse the fields whose names +are in +.IR string . +. +Each field name can be followed by a number which says how many such +fields should be reversed. +. +If no number is given for a field, +all such fields will be reversed. +. +. +.TP +.BI search\-ignore\*n\~ fields +While searching for keys in databases for which no index exists, +ignore the contents of +.IR fields . +. +Initially, +fields +.B XYZ +are ignored. +. +. +.TP +.BI search\-truncate\*n\~ n +Only require the first +.I n +characters of keys to be given. +. +In effect when searching for a given key words in the database are +truncated to the maximum of +.I n +and the length of the key. +. +Initially, +.I n +is\~6. +. +. +.TP +.BI short\-label\*n\~ string +.I string +is a label expression that specifies an alternative +(usually shorter) +style of label. +. +This is used when the +.B # +flag is given in the citation. +. +When using author-date style labels, +the identity of the author or authors is sometimes clear from the +context, +and so it may be desirable to omit the author or authors from the label. +. +The +.B \%short\-label +command will typically be used to specify a label containing just +a date and possibly a disambiguating letter. +. +. +.TP +.BI sort\*n\~ string +Sort references according to +.IR string . +. +References will automatically be accumulated. +. +.I string +should be a list of field names, +each followed by a number, +indicating how many fields with the name should be used for sorting. +. +.RB \[lq] + \[rq] +can be used to indicate that all the fields with the name should be +used. +. +Also +.B .\& +can be used to indicate the references should be sorted using the +(tentative) label. +. +(Subsection \[lq]Label expressions\[rq] below describes the concept of a +tentative label.) +. +. +.TP +.B sort\-adjacent\-labels\*n +Sort labels that are adjacent in the text according to their position +in the reference list. +. +This command should usually be given if the +.B \%abbreviate\-label\-ranges +command has been given, +or if the label expression contains a +.B <> +expression. +. +This will have no effect unless references are being accumulated. +. +. +.\" ==================================================================== +.SS "Label expressions" +.\" ==================================================================== +. +Label expressions can be evaluated both normally and tentatively. +. +The result of normal evaluation is used for output. +. +The result of tentative evaluation, +called the +.IR "tentative label" , +is used to gather the information that normal evaluation needs to +disambiguate the label. +. +Label expressions specified by the +.B \%date\-as\-label +and +.B \%short\-label +commands are not evaluated tentatively. +. +Normal and tentative evaluation are the same for all types of expression +other than +.BR @ , +.BR * , +and +.B % +expressions. +. +The description below applies to normal evaluation, +except where otherwise specified. +. +. +.TP +.I field +.TQ +.I field\~n +The +.IR n -th +part of +.IR field . +. +If +.I n +is omitted, +it defaults to\~1. +. +. +.TP +.BI \[aq] string \[aq] +The characters in +.I string +literally. +. +. +.TP +.B @ +All the authors joined as specified by the +.B \%join\-authors +command. +. +The whole of each author's name will be used. +. +However, +if the references are sorted by author +(that is, +the sort specification starts with +.RB \[lq] A+ \[rq]), +then authors' last names will be used instead, +provided that this does not introduce ambiguity, +and also an initial subsequence of the authors may be used instead of +all the authors, +again provided that this does not introduce ambiguity. +. +The use of only the last name for the +.IR i -th +author of some reference +is considered to be ambiguous if +there is some other reference, +such that the first +.IR i \|\-\|1 +authors of the references are the same, +the +.IR i -th +authors are not the same, +but the +.IR i -th +authors last names are the same. +. +A proper initial subsequence of the sequence of authors for some +reference is considered to be ambiguous if there is a reference with +some other sequence of authors which also has that subsequence as a +proper initial subsequence. +. +When an initial subsequence of authors is used, +the remaining authors are replaced by the string specified by the +.B \%et\-al +command; +this command may also specify additional requirements that must be +met before an initial subsequence can be used. +. +.B @ +tentatively evaluates to a canonical representation of the authors, +such that authors that compare equally for sorting purpose will have +the same representation. +. +. +.TP +.BI % n +.TQ +.B %a +.TQ +.B %A +.TQ +.B %i +.TQ +.B %I +The serial number of the reference formatted according to the +character following the +.BR % . +The serial number of a reference is\~1 plus the number of earlier +references with same tentative label as this reference. +. +These expressions tentatively evaluate to an empty string. +. +.TP +.IB expr * +If there is another reference with the same tentative label as this +reference, +then +.IR expr , +otherwise an empty string. +. +It tentatively evaluates to an empty string. +. +. +.TP +.IB expr + n +.TQ +.IB expr \- n +The first +.RB ( + ) +or last +.RB ( \- ) +.I n +upper or lower case letters or digits of +.IR expr . +. +.I roff +special characters +(such as +.BR \e(\[aq]a ) +count as a single letter. +. +Accent strings are retained but do not count towards the total. +. +. +.TP +.IB expr .l +.I expr +converted to lowercase. +. +. +.TP +.IB expr .u +.I expr +converted to uppercase. +. +. +.TP +.IB expr .c +.I expr +converted to caps and small caps. +. +. +.TP +.IB expr .r +.I expr +reversed so that the last name is first. +. +. +.TP +.IB expr .a +.I expr +with first names abbreviated. +. +Fields specified in the +.B \%abbreviate +command are abbreviated before any labels are evaluated. +. +Thus +.B .a +is useful only when you want a field to be abbreviated in a label +but not in a reference. +. +. +.TP +.IB expr .y +The year part of +.IR expr . +. +. +.TP +.IB expr .+y +The part of +.I expr +before the year, +or the whole of +.I expr +if it does not contain a year. +. +. +.TP +.IB expr .\-y +The part of +.I expr +after the year, +or an empty string if +.I expr +does not contain a year. +. +. +.TP +.IB expr .n +The last name part of +.IR expr . +. +. +.TP +.IB expr1 \[ti] expr2 +.I expr1 +except that if the last character of +.I expr1 +is +.B \- +then it will be replaced by +.IR expr2 . +. +. +.TP +.I expr1 expr2 +The concatenation of +.I expr1 +and +.IR expr2 . +. +. +.TP +.IB expr1 | expr2 +If +.I expr1 +is non-empty then +.I expr1 +otherwise +.IR expr2 . +. +. +.TP +.IB expr1 & expr2 +If +.I expr1 +is non-empty +then +.I expr2 +otherwise an empty string. +. +. +.TP +.IB expr1 ? expr2 : expr3 +If +.I expr1 +is non-empty +then +.I expr2 +otherwise +.IR expr3 . +. +. +.TP +.BI < expr > +The label is in two parts, +which are separated by +.IR expr . +. +Two adjacent two-part labels which have the same first part will be +merged by appending the second part of the second label onto the first +label separated by the string specified in the +.B \%separate\-label\-second\-parts +command +(initially, +a comma followed by a space); +the resulting label will also be a two-part label with the same first +part as before merging, +and so additional labels can be merged into it. +. +It is permissible for the first part to be empty; +this may be desirable for expressions used in the +.B \%short\-label +command. +. +. +.TP +.BI ( expr ) +The same as +.IR expr . +. +Used for grouping. +. +. +.LP +The above expressions are listed in order of precedence +(highest first); +.B & +and +.B | +have the same precedence. +. +. +.\" ==================================================================== +.SS "Macro interface" +.\" ==================================================================== +. +Each reference starts with a call to the macro +.BR ]\- . +. +The string +.B [F +will be defined to be the label for this reference, +unless the +.B \%no\-label\-in\-reference +command has been given. +. +There then follows a series of string definitions, +one for each field: +string +.BI [ X +corresponds to field +.IR X . +. +The register +.B [P +is set to\~1 if the +.B P +field contains a range of pages. +. +The +.BR [T , +.B [A +and +.B [O +registers are set to\~1 according as the +.BR T , +.B A +and +.B O +fields end with any of +.B .?!\& +(an end-of-sentence character). +. +The +.B [E +register will be set to\~1 if the +.B [E +string contains more than one name. +. +The reference is followed by a call to the +.B ][ +macro. +. +The first argument to this macro gives a number representing +the type of the reference. +. +If a reference contains a +.B J +field, +it will be classified as type\~1, +otherwise if it contains a +.B B +field, +it will be type\~3, +otherwise if it contains a +.B G +or +.B R +field it will be type\~4, +otherwise if it contains an +.B I +field it will be type\~2, +otherwise it will be type\~0. +. +The second argument is a symbolic name for the type: +.BR other , +.BR \%journal\-article , +.BR book , +.BR \%article\-in\-book , +or +.BR \%tech\-report . +. +Groups of references that have been accumulated or are produced by the +.B \%bibliography +command are preceded by a call to the +.B ]< +macro and followed by a call to the +.B ]> +macro. +. +. +.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 \-R +Don't recognize lines beginning with +.BR .R1 / .R2 . +. +. +.P +Other options are equivalent to +.I @g@refer +commands. +. +. +.TP 16n +.BI \-a\~ n +.B reverse +.BI A n +. +. +.TP +.B \-b +.B "\%no\-label\-in\-text; \%no\-label\-in\-reference" +. +. +.TP +.B \-B +See below. +. +. +.TP +.BI \-c\~ fields +.B capitalize +.I fields +. +. +.TP +.B \-C +.B compatible +. +. +.TP +.B \-e +.B accumulate +. +. +.TP +.BI \-f\~ n +.B \%label +.BI % n +. +. +.TP +.BI \-i\~ fields +.B search\-ignore +.I fields +. +. +.TP +.B \-k +.B \%label +.B L\[ti]%a +. +. +.TP +.BI \-k\~ field +.B \%label +.IB field \[ti]%a +. +. +.TP +.B \-l +.B \%label +.B A.nD.y%a +. +. +.TP +.BI \-l\~ m +.B \%label +.BI A.n+ m D.y%a +. +. +.TP +.BI \-l\~, n +.B \%label +.BI A.nD.y\- n %a +. +. +.TP +.BI \-l\~ m , n +.B \%label +.BI A.n+ m D.y\- n %a +. +. +.TP +.B \-n +.B \%no\-default\-database +. +. +.TP +.BI \-p\~ db-file +.B database +.I db-file +. +. +.TP +.B \-P +.B move\-punctuation +. +. +.TP +.BI \-s\~ spec +.B sort +.I spec +. +. +.TP +.B \-S +.B \%label \[dq](A.n|Q) \[aq], \[aq] (D.y|D)\[dq]; \ +\%bracket-\%label \[dq]\~(\[dq]\~)\~\[dq];\~\[dq] +. +. +.TP +.BI \-t\~ n +.B search\-truncate +.I n +. +. +.P +The +.B B +option has command equivalents with the addition that the file names +specified on the command line are processed as if they were arguments to +the +.B \%bibliography +command instead of in the normal way. +. +. +.TP 16n +.B \-B +.B "annotate X AP; \%no\-label\-in\-reference" +. +. +.TP +.BI \-B\~ field . macro +.B annotate +.I field +.IB macro ; +.B \%no\-label\-in\-reference +. +. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I REFER +If set, +overrides the default database. +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @DEFAULT_INDEX@ +Default database. +. +. +.TP +.RI file @INDEX_SUFFIX@ +Index files. +. +. +.TP +.I @MACRODIR@/\:refer\:.tmac +defines macros and strings facilitating integration with macro packages +that wish to support +.IR @g@refer . +. +. +.LP +.I @g@refer +uses temporary files. +. +See the +.MR groff @MAN1EXT@ +man page for details of where such files are created. +. +. +.\" ==================================================================== +.SH Bugs +.\" ==================================================================== +. +In label expressions, +.B <> +expressions are ignored inside +.BI . char +expressions. +. +. +.\" ==================================================================== +.SH Examples +.\" ==================================================================== +. +We can illustrate the operation of +.I @g@refer +with a sample bibliographic database containing one entry and a simple +.I roff +document to cite that entry. +. +. +.P +.RS +.EX +$ \c +.B cat > my\-db\-file +.B %A Daniel P.\[rs]& Friedman +.B %A Matthias Felleisen +.B %C Cambridge, Massachusetts +.B %D 1996 +.B %I The MIT Press +.B %T The Little Schemer, Fourth Edition +$ \c +.B refer -p my\-db\-file +.B Read the book +.B .[ +.B friedman +.B .] +.B on your summer vacation. +.I <Control+D> +\&.lf 1 \- +Read the book\[rs]*([.1\[rs]*(.] +\&.ds [F 1 +\&.]\- +\&.ds [A Daniel P. Friedman and Matthias Felleisen +\&.ds [C Cambridge, Massachusetts +\&.ds [D 1996 +\&.ds [I The MIT Press +\&.ds [T The Little Schemer, Fourth Edition +\&.nr [T 0 +\&.nr [A 0 +\&.][ 2 book +\&.lf 5 \- +on your summer vacation. +.EE +.RE +. +. +.P +The foregoing shows us that +.I @g@refer +(a) produces a label \[lq]1\[rq]; +(b) brackets that label with interpolations of the +.RB \[lq] [. \[rq] +and +.RB \[lq] .] \[rq] +strings; +(c) calls a macro +.RB \[lq] ]\- \[rq]; +(d) defines strings and registers containing the label and bibliographic +data for the reference; +(e) calls a macro +.RB \[lq] ][ \[rq]; +and (f) uses the +.B lf +request to restore the line numbers of the original input. +. +As discussed in subsection \[lq]Macro interface\[rq] above, +it is up to the document or a macro package to employ and format this +information usefully. +. +Let us see how we might turn +.MR groff_ms @MAN7EXT@ +to this task. +. +. +.P +.RS +.EX +$ \c +.B REFER=my\-db\-file groff \-R \-ms +.B .LP +.B Read the book +.B .[ +.B friedman +.B .] +.B on your summer vacation. +.B Commentary is available.\[rs]*{*\[rs]*} +.B .FS \[rs]*{*\[rs]*} +.B Space reserved for penetrating insight. +.B .FE +.EE +.RE +. +. +.LP +.IR ms 's +automatic footnote numbering mechanism is not aware of +.IR @g@refer 's +label numbering, +so we have manually specified a (superscripted) symbolic footnote for +our non-bibliographic aside. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +\[lq]Some Applications of Inverted Indexes on the Unix System\[rq], +by M.\& E.\& Lesk, +1978, +AT&T Bell Laboratories Computing Science Technical Report No.\& 69. +. +. +.LP +.MR @g@indxbib @MAN1EXT@ , +.MR @g@lookbib @MAN1EXT@ , +.MR lkbib @MAN1EXT@ +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_refer_1_man_C] +.do rr *groff_refer_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/preproc/refer/refer.am b/src/preproc/refer/refer.am new file mode 100644 index 0000000..273f334 --- /dev/null +++ b/src/preproc/refer/refer.am @@ -0,0 +1,61 @@ +# 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 += refer +refer_CPPFLAGS = $(AM_CPPFLAGS) -I $(top_srcdir)/src/preproc/refer +refer_LDADD = libbib.a libgroff.a $(LIBM) lib/libgnu.a +refer_SOURCES = \ + src/preproc/refer/command.cpp \ + src/preproc/refer/ref.cpp \ + src/preproc/refer/refer.cpp \ + src/preproc/refer/token.cpp \ + src/preproc/refer/label.ypp \ + src/preproc/refer/refer.h \ + src/preproc/refer/ref.h \ + src/preproc/refer/token.h \ + src/preproc/refer/command.h + +PREFIXMAN1 += src/preproc/refer/refer.1 +EXTRA_DIST += \ + src/preproc/refer/TODO \ + src/preproc/refer/refer.1.man + +# Since refer_CPPFLAGS was set, all .o files have a 'refer-' prefix. +src/preproc/refer/refer-command.$(OBJEXT): defs.h +src/preproc/refer/refer-ref.$(OBJEXT): defs.h +src/preproc/refer/refer-refer.$(OBJEXT): defs.h +src/preproc/refer/refer-token.$(OBJEXT): defs.h +src/preproc/refer/refer-label.$(OBJEXT): defs.h + +MAINTAINERCLEANFILES += \ + src/preproc/refer/label.cpp \ + src/preproc/refer/label.hpp \ + src/preproc/refer/label.output + +refer_TESTS = \ + src/preproc/refer/tests/report-correct-line-numbers.sh +TESTS += $(refer_TESTS) +EXTRA_DIST += \ + $(refer_TESTS) \ + src/preproc/refer/tests/artifacts/62124.bib + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/refer/refer.cpp b/src/preproc/refer/refer.cpp new file mode 100644 index 0000000..a5c291e --- /dev/null +++ b/src/preproc/refer/refer.cpp @@ -0,0 +1,1267 @@ +/* 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 "refer.h" +#include "refid.h" +#include "ref.h" +#include "token.h" +#include "search.h" +#include "command.h" + +extern "C" const char *Version_string; + +const char PRE_LABEL_MARKER = '\013'; +const char POST_LABEL_MARKER = '\014'; +const char LABEL_MARKER = '\015'; // label_type is added on + +#define FORCE_LEFT_BRACKET 04 +#define FORCE_RIGHT_BRACKET 010 + +static FILE *outfp = stdout; + +string capitalize_fields; +string reverse_fields; +string abbreviate_fields; +string period_before_last_name = ". "; +string period_before_initial = "."; +string period_before_hyphen = ""; +string period_before_other = ". "; +string sort_fields; +int annotation_field = -1; +string annotation_macro; +string discard_fields = "XYZ"; +string pre_label = "\\*([."; +string post_label = "\\*(.]"; +string sep_label = ", "; +int have_bibliography = 0; +int accumulate = 0; +int move_punctuation = 0; +int abbreviate_label_ranges = 0; +string label_range_indicator; +int label_in_text = 1; +int label_in_reference = 1; +int date_as_label = 0; +int sort_adjacent_labels = 0; +// Join exactly two authors with this. +string join_authors_exactly_two = " and "; +// When there are more than two authors join the last two with this. +string join_authors_last_two = ", and "; +// Otherwise join authors with this. +string join_authors_default = ", "; +string separate_label_second_parts = ", "; +// Use this string to represent that there are other authors. +string et_al = " et al"; +// Use et al only if it can replace at least this many authors. +int et_al_min_elide = 2; +// Use et al only if the total number of authors is at least this. +int et_al_min_total = 3; + + +int compatible_flag = 0; + +int short_label_flag = 0; + +static bool recognize_R1_R2 = true; + +search_list database_list; +int search_default = 1; +static int default_database_loaded = 0; + +static reference **citation = 0; +static int ncitations = 0; +static int citation_max = 0; + +static reference **reference_hash_table = 0; +static int hash_table_size; +static int nreferences = 0; + +static int need_syncing = 0; +string pending_line; +string pending_lf_lines; + +static void output_pending_line(); +static unsigned immediately_handle_reference(const string &); +static void immediately_output_references(); +static unsigned store_reference(const string &); +static void divert_to_temporary_file(); +static reference *make_reference(const string &, unsigned *); +static void usage(FILE *stream); +static void do_file(const char *); +static void split_punct(string &line, string &punct); +static void output_citation_group(reference **v, int n, label_type, + FILE *fp); +static void possibly_load_default_database(); + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + outfp = stdout; + int finished_options = 0; + int bib_flag = 0; + int done_spec = 0; + + // TODO: Migrate to getopt_long; see, e.g., src/preproc/eqn/main.cpp. + for (--argc, ++argv; + !finished_options && argc > 0 && argv[0][0] == '-' + && argv[0][1] != '\0'; + argv++, argc--) { + const char *opt = argv[0] + 1; + while (opt != 0 && *opt != '\0') { + switch (*opt) { + case 'C': + compatible_flag = 1; + opt++; + break; + case 'B': + bib_flag = 1; + label_in_reference = 0; + label_in_text = 0; + ++opt; + if (*opt == '\0') { + annotation_field = 'X'; + annotation_macro = "AP"; + } + else if (csalnum(opt[0]) && opt[1] == '.' && opt[2] != '\0') { + annotation_field = opt[0]; + annotation_macro = opt + 2; + } + opt = 0; + break; + case 'P': + move_punctuation = 1; + opt++; + break; + case 'R': + recognize_R1_R2 = false; + opt++; + break; + case 'S': + // Not a very useful spec. + set_label_spec("(A.n|Q)', '(D.y|D)"); + done_spec = 1; + pre_label = " ("; + post_label = ")"; + sep_label = "; "; + opt++; + break; + case 'V': + do_verify = true; + opt++; + break; + case 'f': + { + const char *num = 0; + if (*++opt == '\0') { + if (argc > 1) { + num = *++argv; + --argc; + } + else { + error("'f' option requires an argument"); + usage(stderr); + exit(1); + } + } + else { + num = opt; + opt = 0; + } + const char *ptr; + for (ptr = num; *ptr; ptr++) + if (!csdigit(*ptr)) { + error("invalid character '%1' in argument to 'f' option", + *ptr); + break; + } + if (*ptr == '\0') { + string spec; + spec = '%'; + spec += num; + spec += '\0'; + set_label_spec(spec.contents()); + done_spec = 1; + } + break; + } + case 'b': + label_in_text = 0; + label_in_reference = 0; + opt++; + break; + case 'e': + accumulate = 1; + opt++; + break; + case 'c': + capitalize_fields = ++opt; + opt = 0; + break; + case 'k': + { + char buf[5]; + if (csalpha(*++opt)) + buf[0] = *opt++; + else { + if (*opt != '\0') + error("invalid field name '%1' in argument to 'k' option", + *opt++); + buf[0] = 'L'; + } + buf[1] = '~'; + buf[2] = '%'; + buf[3] = 'a'; + buf[4] = '\0'; + set_label_spec(buf); + done_spec = 1; + } + break; + case 'a': + { + const char *ptr; + for (ptr = ++opt; *ptr; ptr++) + if (!csdigit(*ptr)) { + error("'a' option argument must be an integer"); + break; + } + if (*ptr == '\0') { + reverse_fields = 'A'; + reverse_fields += opt; + } + opt = 0; + } + break; + case 'i': + linear_ignore_fields = ++opt; + opt = 0; + break; + case 'l': + { + char buf[INT_DIGITS*2 + 11]; // A.n+2D.y-3%a + strcpy(buf, "A.n"); + if (*++opt != '\0' && *opt != ',') { + char *ptr; + long n = strtol(opt, &ptr, 10); + if (n == 0 && ptr == opt) { + error("invalid integer '%1' in 'l' option argument", opt); + opt = 0; + break; + } + if (n < 0) + n = 0; + opt = ptr; + sprintf(strchr(buf, '\0'), "+%ld", n); + } + strcat(buf, "D.y"); + if (*opt == ',') + opt++; + if (*opt != '\0') { + char *ptr; + long n = strtol(opt, &ptr, 10); + if (n == 0 && ptr == opt) { + error("invalid integer '%1' in 'l' option argument", opt); + opt = 0; + break; + } + if (n < 0) + n = 0; + sprintf(strchr(buf, '\0'), "-%ld", n); + opt = ptr; + if (*opt != '\0') + error("argument to 'l' option not of form 'm,n'"); + } + strcat(buf, "%a"); + if (!set_label_spec(buf)) + assert(0 == "set_label_spec() failed"); + done_spec = 1; + } + break; + case 'n': + search_default = 0; + opt++; + break; + case 'p': + { + const char *filename = 0; + if (*++opt == '\0') { + if (argc > 1) { + filename = *++argv; + argc--; + } + else { + error("option 'p' requires an argument"); + usage(stderr); + exit(1); + } + } + else { + filename = opt; + opt = 0; + } + database_list.add_file(filename); + } + break; + case 's': + if (*++opt == '\0') + sort_fields = "AD"; + else { + sort_fields = opt; + opt = 0; + } + accumulate = 1; + break; + case 't': + { + char *ptr; + long n = strtol(opt, &ptr, 10); + if (n == 0 && ptr == opt) { + error("invalid integer '%1' in 't' option argument", opt); + opt = 0; + break; + } + if (n < 1) + n = 1; + linear_truncate_len = int(n); + opt = ptr; + break; + } + case '-': + if (opt[1] == '\0') { + finished_options = 1; + opt++; + break; + } + if (strcmp(opt, "-version") == 0) { + case 'v': + printf("GNU refer (groff) version %s\n", Version_string); + exit(0); + break; + } + if (strcmp(opt, "-help") == 0) { + usage(stdout); + exit(0); + break; + } + // fall through + default: + error("unrecognized option '%1'", opt); + usage(stderr); + exit(1); + break; + } + } + } + if (!done_spec) + set_label_spec("%1"); + if (argc <= 0) { + if (bib_flag) + do_bib("-"); + else + do_file("-"); + } + else { + for (int i = 0; i < argc; i++) { + if (bib_flag) + do_bib(argv[i]); + else + do_file(argv[i]); + } + } + if (accumulate) + output_references(); + if (fflush(stdout) < 0) + fatal("output error: %1", strerror(errno)); + return 0; +} + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-bCenPRS] [-aN] [-cXYZ] [-fN] [-iXYZ] [-kX] [-lM,N]" +" [-p db-file] [-sXYZ] [-tN] [-Bl.m] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); +} + +static void possibly_load_default_database() +{ + if (search_default && !default_database_loaded) { + char *filename = getenv("REFER"); + if (filename) + database_list.add_file(filename); + else + database_list.add_file(DEFAULT_INDEX, 1); + default_database_loaded = 1; + } +} + +static bool is_list(const string &str) +{ + const char *start = str.contents(); + const char *end = start + str.length(); + while (end > start && csspace(end[-1])) + end--; + while (start < end && csspace(*start)) + start++; + return end - start == 6 && memcmp(start, "$LIST$", 6) == 0; +} + +static void do_file(const char *filename) +{ + FILE *fp; + if (strcmp(filename, "-") == 0) { + fp = stdin; + } + else { + errno = 0; + fp = fopen(filename, "r"); + if (fp == 0) { + error("can't open '%1': %2", filename, strerror(errno)); + return; + } + } + string fn(filename); + fn += '\0'; + normalize_for_lf(fn); + current_filename = fn.contents(); + fprintf(outfp, ".lf 1 %s\n", current_filename); + current_lineno = 1; + string line; + for (;;) { + line.clear(); + for (;;) { + int c = getc(fp); + if (EOF == c) { + if (line.length() > 0) + line += '\n'; + break; + } + if (is_invalid_input_char(c)) + error("invalid input character code %1", c); + else { + line += c; + if ('\n' == c) + break; + } + } + int len = line.length(); + if (len == 0) + break; + current_lineno++; + if (len >= 2 && line[0] == '.' && line[1] == '[') { + int start_lineno = current_lineno; + bool at_start_of_line = true; + string str; + string post; + string pre(line.contents() + 2, line.length() - 3); + for (;;) { + int c = getc(fp); + if (EOF == c) { + error_with_file_and_line(current_filename, start_lineno, + "missing '.]' line"); + break; + } + if (at_start_of_line) + current_lineno++; + if (at_start_of_line && '.' == c) { + int d = getc(fp); + if (d == ']') { + while ((d = getc(fp)) != '\n' && d != EOF) { + if (is_invalid_input_char(d)) + error("invalid input character code %1", d); + else + post += d; + } + break; + } + if (d != EOF) + ungetc(d, fp); + } + if (is_invalid_input_char(c)) + error("invalid input character code %1", c); + else + str += c; + at_start_of_line = ('\n' == c); + } + if (is_list(str)) { + output_pending_line(); + if (accumulate) + output_references(); + else + error("found '$LIST$' but not accumulating references"); + } + else { + unsigned flags = (accumulate + ? store_reference(str) + : immediately_handle_reference(str)); + if (label_in_text) { + if (accumulate && outfp == stdout) + divert_to_temporary_file(); + if (pending_line.length() == 0) { + warning("can't attach citation to previous line"); + } + else + pending_line.set_length(pending_line.length() - 1); + string punct; + if (move_punctuation) + split_punct(pending_line, punct); + int have_text = pre.length() > 0 || post.length() > 0; + label_type lt = label_type(flags & ~(FORCE_LEFT_BRACKET + |FORCE_RIGHT_BRACKET)); + if ((flags & FORCE_LEFT_BRACKET) || !have_text) + pending_line += PRE_LABEL_MARKER; + pending_line += pre; + char lm = LABEL_MARKER + (int)lt; + pending_line += lm; + pending_line += post; + if ((flags & FORCE_RIGHT_BRACKET) || !have_text) + pending_line += POST_LABEL_MARKER; + pending_line += punct; + pending_line += '\n'; + } + } + need_syncing = 1; + } + else if (len >= 4 + && '.' == line[0] && 'l' == line[1] && 'f' == line[2] + && (compatible_flag || '\n' == line[3] || ' ' == line[3])) + { + pending_lf_lines += line; + line += '\0'; + if (interpret_lf_args(line.contents() + 3)) + current_lineno--; + } + else if (recognize_R1_R2 + && len >= 4 + && '.' == line[0] && 'R' == line[1] && '1' == line[2] + && (compatible_flag || '\n' == line[3] || ' ' == line[3])) + { + line.clear(); + int start_lineno = current_lineno; + bool at_start_of_line = true; + for (;;) { + int c = getc(fp); + if (c != EOF && at_start_of_line) + current_lineno++; + if (at_start_of_line && '.' == c) { + c = getc(fp); + if ('R' == c) { + c = getc(fp); + if ('2' == c) { + c = getc(fp); + if (compatible_flag || ' ' == c || '\n' == c || EOF == c) + { + while (c != EOF && c != '\n') + c = getc(fp); + break; + } + else { + line += '.'; + line += 'R'; + line += '2'; + } + } + else { + line += '.'; + line += 'R'; + } + } + else + line += '.'; + } + if (EOF == c) { + error_with_file_and_line(current_filename, start_lineno, + "missing '.R2' line"); + break; + } + if (is_invalid_input_char(c)) + error_with_file_and_line(current_filename, start_lineno, + "invalid input character code %1", + c); + else { + line += c; + at_start_of_line = ('\n' == c); + } + } + output_pending_line(); + if (accumulate) + output_references(); + else + nreferences = 0; + process_commands(line, current_filename, start_lineno + 1); + need_syncing = 1; + } + else { + output_pending_line(); + pending_line = line; + } + } + need_syncing = 0; + output_pending_line(); + if (fp != stdin) + fclose(fp); +} + +class label_processing_state { + enum { + NORMAL, + PENDING_LABEL, + PENDING_LABEL_POST, + PENDING_LABEL_POST_PRE, + PENDING_POST + } state; + label_type type; // type of pending labels + int count; // number of pending labels + reference **rptr; // pointer to next reference + int rcount; // number of references left + FILE *fp; + int handle_pending(int c); +public: + label_processing_state(reference **, int, FILE *); + ~label_processing_state(); + void process(int c); +}; + +static void output_pending_line() +{ + if (label_in_text && !accumulate && ncitations > 0) { + label_processing_state state(citation, ncitations, outfp); + int len = pending_line.length(); + for (int i = 0; i < len; i++) + state.process((unsigned char)(pending_line[i])); + } + else + put_string(pending_line, outfp); + pending_line.clear(); + if (pending_lf_lines.length() > 0) { + put_string(pending_lf_lines, outfp); + pending_lf_lines.clear(); + } + if (!accumulate) + immediately_output_references(); + if (need_syncing) { + fprintf(outfp, ".lf %d %s\n", current_lineno, current_filename); + need_syncing = 0; + } +} + +static void split_punct(string &line, string &punct) +{ + const char *start = line.contents(); + const char *end = start + line.length(); + const char *ptr = start; + const char *last_token_start = 0; + for (;;) { + if (ptr >= end) + break; + last_token_start = ptr; + if (*ptr == PRE_LABEL_MARKER || *ptr == POST_LABEL_MARKER + || (*ptr >= LABEL_MARKER + && *ptr < LABEL_MARKER + N_LABEL_TYPES)) + ptr++; + else if (!get_token(&ptr, end)) + break; + } + if (last_token_start) { + const token_info *ti = lookup_token(last_token_start, end); + if (ti->is_punct()) { + punct.append(last_token_start, end - last_token_start); + line.set_length(last_token_start - start); + } + } +} + +static void divert_to_temporary_file() +{ + outfp = xtmpfile(); +} + +static void store_citation(reference *ref) +{ + if (ncitations >= citation_max) { + if (citation == 0) + citation = new reference*[citation_max = 100]; + else { + reference **old_citation = citation; + citation_max *= 2; + citation = new reference *[citation_max]; + memcpy(citation, old_citation, ncitations*sizeof(reference *)); + delete[] old_citation; + } + } + citation[ncitations++] = ref; +} + +static unsigned store_reference(const string &str) +{ + if (reference_hash_table == 0) { + reference_hash_table = new reference *[17]; + hash_table_size = 17; + for (int i = 0; i < hash_table_size; i++) + reference_hash_table[i] = 0; + } + unsigned flags; + reference *ref = make_reference(str, &flags); + ref->compute_hash_code(); + unsigned h = ref->hash(); + reference **ptr; + for (ptr = reference_hash_table + (h % hash_table_size); + *ptr != 0; + ((ptr == reference_hash_table) + ? (ptr = reference_hash_table + hash_table_size - 1) + : --ptr)) + if (same_reference(**ptr, *ref)) + break; + if (*ptr != 0) { + if (ref->is_merged()) + warning("fields ignored because reference already used"); + delete ref; + ref = *ptr; + } + else { + *ptr = ref; + ref->set_number(nreferences); + nreferences++; + ref->pre_compute_label(); + ref->compute_sort_key(); + if (nreferences*2 >= hash_table_size) { + // Rehash it. + reference **old_table = reference_hash_table; + int old_size = hash_table_size; + hash_table_size = next_size(hash_table_size); + reference_hash_table = new reference*[hash_table_size]; + int i; + for (i = 0; i < hash_table_size; i++) + reference_hash_table[i] = 0; + for (i = 0; i < old_size; i++) + if (old_table[i]) { + reference **p; + for (p = (reference_hash_table + + (old_table[i]->hash() % hash_table_size)); + *p; + ((p == reference_hash_table) + ? (p = reference_hash_table + hash_table_size - 1) + : --p)) + ; + *p = old_table[i]; + } + delete[] old_table; + } + } + if (label_in_text) + store_citation(ref); + return flags; +} + +unsigned immediately_handle_reference(const string &str) +{ + unsigned flags; + reference *ref = make_reference(str, &flags); + ref->set_number(nreferences); + if (label_in_text || label_in_reference) { + ref->pre_compute_label(); + ref->immediate_compute_label(); + } + nreferences++; + store_citation(ref); + return flags; +} + +static void immediately_output_references() +{ + for (int i = 0; i < ncitations; i++) { + reference *ref = citation[i]; + if (label_in_reference) { + fputs(".ds [F ", outfp); + const string &label = ref->get_label(NORMAL_LABEL); + if (label.length() > 0 + && (label[0] == ' ' || label[0] == '\\' || label[0] == '"')) + putc('"', outfp); + put_string(label, outfp); + putc('\n', outfp); + } + ref->output(outfp); + delete ref; + } + ncitations = 0; +} + +static void output_citation_group(reference **v, int n, label_type type, + FILE *fp) +{ + if (sort_adjacent_labels) { + // Do an insertion sort. Usually n will be very small. + for (int i = 1; i < n; i++) { + int num = v[i]->get_number(); + reference *temp = v[i]; + int j; + for (j = i - 1; j >= 0 && v[j]->get_number() > num; j--) + v[j + 1] = v[j]; + v[j + 1] = temp; + } + } + // This messes up if !accumulate. + if (accumulate && n > 1) { + // remove duplicates + int j = 1; + for (int i = 1; i < n; i++) + if (v[i]->get_label(type) != v[i - 1]->get_label(type)) + v[j++] = v[i]; + n = j; + } + string merged_label; + for (int i = 0; i < n; i++) { + int nmerged = v[i]->merge_labels(v + i + 1, n - i - 1, type, + merged_label); + if (nmerged > 0) { + put_string(merged_label, fp); + i += nmerged; + } + else + put_string(v[i]->get_label(type), fp); + if (i < n - 1) + put_string(sep_label, fp); + } +} + + +label_processing_state::label_processing_state(reference **p, int n, + FILE *f) +: state(NORMAL), count(0), rptr(p), rcount(n), fp(f) +{ +} + +label_processing_state::~label_processing_state() +{ + int handled = handle_pending(EOF); + assert(!handled); + assert(rcount == 0); +} + +int label_processing_state::handle_pending(int c) +{ + switch (state) { + case NORMAL: + break; + case PENDING_LABEL: + if (POST_LABEL_MARKER == c) { + state = PENDING_LABEL_POST; + return 1; + } + else { + output_citation_group(rptr, count, type, fp); + rptr += count ; + rcount -= count; + state = NORMAL; + } + break; + case PENDING_LABEL_POST: + if (PRE_LABEL_MARKER == c) { + state = PENDING_LABEL_POST_PRE; + return 1; + } + else { + output_citation_group(rptr, count, type, fp); + rptr += count; + rcount -= count; + put_string(post_label, fp); + state = NORMAL; + } + break; + case PENDING_LABEL_POST_PRE: + if (c >= LABEL_MARKER + && c < LABEL_MARKER + N_LABEL_TYPES + && c - LABEL_MARKER == type) { + count += 1; + state = PENDING_LABEL; + return 1; + } + else { + output_citation_group(rptr, count, type, fp); + rptr += count; + rcount -= count; + put_string(sep_label, fp); + state = NORMAL; + } + break; + case PENDING_POST: + if (PRE_LABEL_MARKER == c) { + put_string(sep_label, fp); + state = NORMAL; + return 1; + } + else { + put_string(post_label, fp); + state = NORMAL; + } + break; + } + return 0; +} + +void label_processing_state::process(int c) +{ + if (handle_pending(c)) + return; + assert(state == NORMAL); + switch (c) { + case PRE_LABEL_MARKER: + put_string(pre_label, fp); + state = NORMAL; + break; + case POST_LABEL_MARKER: + state = PENDING_POST; + break; + case LABEL_MARKER: + case LABEL_MARKER + 1: + count = 1; + state = PENDING_LABEL; + type = label_type(c - LABEL_MARKER); + break; + default: + state = NORMAL; + putc(c, fp); + break; + } +} + +extern "C" { + +int rcompare(const void *p1, const void *p2) +{ + return compare_reference(**(reference **)p1, **(reference **)p2); +} + +} + +void output_references() +{ + assert(accumulate); + if (!hash_table_size) { + if (have_bibliography) + error("nothing to reference (probably 'bibliography' before" + " 'sort')"); + accumulate = 0; + nreferences = 0; + return; + } + if (nreferences > 0) { + int j = 0; + int i; + for (i = 0; i < hash_table_size; i++) + if (reference_hash_table[i] != 0) + reference_hash_table[j++] = reference_hash_table[i]; + assert(j == nreferences); + for (; j < hash_table_size; j++) + reference_hash_table[j] = 0; + qsort(reference_hash_table, nreferences, sizeof(reference*), + rcompare); + for (i = 0; i < nreferences; i++) + reference_hash_table[i]->set_number(i); + compute_labels(reference_hash_table, nreferences); + } + if (outfp != stdout) { + rewind(outfp); + { + label_processing_state state(citation, ncitations, stdout); + int c; + while ((c = getc(outfp)) != EOF) + state.process(c); + } + ncitations = 0; + fclose(outfp); + outfp = stdout; + } + if (nreferences > 0) { + fputs(".]<\n", outfp); + for (int i = 0; i < nreferences; i++) { + if (sort_fields.length() > 0) + reference_hash_table[i]->print_sort_key_comment(outfp); + if (label_in_reference) { + fputs(".ds [F ", outfp); + const string &label + = reference_hash_table[i]->get_label(NORMAL_LABEL); + if (label.length() > 0 + && (label[0] == ' ' || label[0] == '\\' || label[0] == '"')) + putc('"', outfp); + put_string(label, outfp); + putc('\n', outfp); + } + reference_hash_table[i]->output(outfp); + delete reference_hash_table[i]; + reference_hash_table[i] = 0; + } + fputs(".]>\n", outfp); + nreferences = 0; + } + clear_labels(); +} + +static reference *find_reference(const char *query, int query_len) +{ + // This is so that error messages look better. + while (query_len > 0 && csspace(query[query_len - 1])) + query_len--; + string str; + for (int i = 0; i < query_len; i++) + str += query[i] == '\n' ? ' ' : query[i]; + str += '\0'; + possibly_load_default_database(); + search_list_iterator iter(&database_list, str.contents()); + reference_id rid; + const char *start; + int len; + if (!iter.next(&start, &len, &rid)) { + error("no matches for '%1'", str.contents()); + return 0; + } + const char *end = start + len; + while (start < end) { + if (*start == '%') + break; + while (start < end && *start++ != '\n') + ; + } + if (start >= end) { + error("found a reference for '%1' but it didn't contain any fields", + str.contents()); + return 0; + } + reference *result = new reference(start, end - start, &rid); + if (iter.next(&start, &len, &rid)) + warning("multiple matches for '%1'", str.contents()); + return result; +} + +static reference *make_reference(const string &str, unsigned *flagsp) +{ + const char *start = str.contents(); + const char *end = start + str.length(); + const char *ptr = start; + while (ptr < end) { + if (*ptr == '%') + break; + while (ptr < end && *ptr++ != '\n') + ; + } + *flagsp = 0; + for (; start < ptr; start++) { + if (*start == '#') + *flagsp = (SHORT_LABEL | (*flagsp & (FORCE_RIGHT_BRACKET + | FORCE_LEFT_BRACKET))); + else if (*start == '[') + *flagsp |= FORCE_LEFT_BRACKET; + else if (*start == ']') + *flagsp |= FORCE_RIGHT_BRACKET; + else if (!csspace(*start)) + break; + } + if (start >= end) { + error("empty reference"); + return new reference; + } + reference *database_ref = 0; + if (start < ptr) + database_ref = find_reference(start, ptr - start); + reference *inline_ref = 0; + if (ptr < end) + inline_ref = new reference(ptr, end - ptr); + if (inline_ref) { + if (database_ref) { + database_ref->merge(*inline_ref); + delete inline_ref; + return database_ref; + } + else + return inline_ref; + } + else if (database_ref) + return database_ref; + else + return new reference; +} + +static void do_ref(const string &str) +{ + if (accumulate) + (void)store_reference(str); + else { + (void)immediately_handle_reference(str); + immediately_output_references(); + } +} + +static void trim_blanks(string &str) +{ + const char *start = str.contents(); + const char *end = start + str.length(); + while (end > start && end[-1] != '\n' && csspace(end[-1])) + --end; + str.set_length(end - start); +} + +void do_bib(const char *filename) +{ + FILE *fp; + if (strcmp(filename, "-") == 0) + fp = stdin; + else { + errno = 0; + fp = fopen(filename, "r"); + if (fp == 0) { + error("can't open '%1': %2", filename, strerror(errno)); + return; + } + current_filename = filename; + } + current_lineno = 1; + enum { + START, MIDDLE, BODY, BODY_START, BODY_BLANK, BODY_DOT + } state = START; + string body; + for (;;) { + int c = getc(fp); + if (EOF == c) + break; + if (is_invalid_input_char(c)) { + error("invalid input character code %1", c); + continue; + } + switch (state) { + case START: + if ('%' == c) { + body = c; + state = BODY; + } + else if (c != '\n') + state = MIDDLE; + break; + case MIDDLE: + if ('\n' == c) + state = START; + break; + case BODY: + body += c; + if ('\n' == c) + state = BODY_START; + break; + case BODY_START: + if ('\n' == c) { + do_ref(body); + state = START; + } + else if ('.' == c) + state = BODY_DOT; + else if (csspace(c)) { + state = BODY_BLANK; + body += c; + } + else { + body += c; + state = BODY; + } + break; + case BODY_BLANK: + if ('\n' == c) { + trim_blanks(body); + do_ref(body); + state = START; + } + else if (csspace(c)) + body += c; + else { + body += c; + state = BODY; + } + break; + case BODY_DOT: + if (']' == c) { + do_ref(body); + state = MIDDLE; + } + else { + body += '.'; + body += c; + state = ('\n' == c) ? BODY_START : BODY; + } + break; + default: + assert(0 == "unhandled case while parsing bibliography file"); + } + if ('\n' == c) + current_lineno++; + } + switch (state) { + case START: + case MIDDLE: + break; + case BODY: + body += '\n'; + do_ref(body); + break; + case BODY_DOT: + case BODY_START: + do_ref(body); + break; + case BODY_BLANK: + trim_blanks(body); + do_ref(body); + break; + } + fclose(fp); +} + +// from the Dragon Book + +unsigned hash_string(const char *s, int len) +{ + const char *end = s + len; + unsigned h = 0, g; + while (s < end) { + h <<= 4; + h += *s++; + if ((g = h & 0xf0000000) != 0) { + h ^= g >> 24; + h ^= g; + } + } + return h; +} + +int next_size(int n) +{ + static const int table_sizes[] = { + 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009, + 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009, + 16000057, 32000011, 64000031, 128000003, 0 + }; + + const int *p; + for (p = table_sizes; *p <= n && *p != 0; p++) + ; + assert(*p != 0); + return *p; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/refer/refer.h b/src/preproc/refer/refer.h new file mode 100644 index 0000000..3ebff27 --- /dev/null +++ b/src/preproc/refer/refer.h @@ -0,0 +1,81 @@ +/* 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 "errarg.h" +#include "error.h" +#include "stringclass.h" +#include "cset.h" +#include "cmap.h" +#include "lf.h" + +#include "defs.h" + +unsigned hash_string(const char *, int); +int next_size(int); + +extern string capitalize_fields; +extern string reverse_fields; +extern string abbreviate_fields; +extern string period_before_last_name; +extern string period_before_initial; +extern string period_before_hyphen; +extern string period_before_other; +extern string sort_fields; +extern int annotation_field; +extern string annotation_macro; +extern string discard_fields; +extern string articles; +extern int abbreviate_label_ranges; +extern string label_range_indicator; +extern int date_as_label; +extern string join_authors_exactly_two; +extern string join_authors_last_two; +extern string join_authors_default; +extern string separate_label_second_parts; +extern string et_al; +extern int et_al_min_elide; +extern int et_al_min_total; + +extern int compatible_flag; + +extern int set_label_spec(const char *); +extern int set_date_label_spec(const char *); +extern int set_short_label_spec(const char *); + +extern int short_label_flag; + +void clear_labels(); +void command_error(const char *, + const errarg &arg1 = empty_errarg, + const errarg &arg2 = empty_errarg, + const errarg &arg3 = empty_errarg); + +class reference; + +void compute_labels(reference **, int); + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/refer/tests/artifacts/62124.bib b/src/preproc/refer/tests/artifacts/62124.bib new file mode 100644 index 0000000..1093837 --- /dev/null +++ b/src/preproc/refer/tests/artifacts/62124.bib @@ -0,0 +1,4 @@ +%A Irritablé, X. +%T Universit\*'e de Grenoble. Cours donn\*'es aux Houches. +%Z Mon dieu, Consiel ! +%Z NOTE: This file is deliberately not valid UTF-8. Try Latin-1. diff --git a/src/preproc/refer/tests/report-correct-line-numbers.sh b/src/preproc/refer/tests/report-correct-line-numbers.sh new file mode 100755 index 0000000..19cae53 --- /dev/null +++ b/src/preproc/refer/tests/report-correct-line-numbers.sh @@ -0,0 +1,136 @@ +#!/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/>. +# + +refer="${abs_top_builddir:-.}/refer" + +fail= + +wail () { + echo FAILED >&2 + fail=YES +} + +# Regression-test Savannah #62124. Ensure correct line numbers in +# diagnostics on bibliography files. + +# Locate directory containing our test artifacts. +artifact_dir= + +for buildroot in . .. ../.. +do + d=$buildroot/src/preproc/refer/tests/artifacts + if [ -d "$d" ] + then + artifact_dir=$d + break + fi +done + +# If we can't find it, we can't test. +test -z "$artifact_dir" && exit 77 # skip + +input=". +.R1 +bibliography $artifact_dir/62124.bib +cattywumpus +.R2 +. +.R1 +bibliography $artifact_dir/62124.bib +cattywumpus +.R2" + +# We want standard error _only_. +output=$(echo "$input" | "$refer" -e -p "$artifact_dir"/62124.bib \ + 2>&1 >/dev/null) + +# We should get every complaint about the bibliography twice because it +# is dumped twice; the line numbers should not change because they're +# problems with the bibliography file, not the input file. + +# We're pattern-matching diagnostic output here, which is a delicate +# thing to do. If a test failure occurs, ensure the diagnostic message +# text hasn't changed before assuming a deeper logic problem. + +echo "checking line number of invalid character on bibliography line 1" +count=$(echo "$output" | grep -c "refer:.*/62124.bib:1:.*code 129") +test $count -eq 2 || wail + +echo "checking line number of first invalid character on bibliography" \ + "line 2" +count=$(echo "$output" | grep -c "refer:.*/62124.bib:2:.*code 136") +test $count -eq 2 || wail + +echo "checking line number of second invalid character on" \ + "bibliography line 2" +count=$(echo "$output" | grep -c "refer:.*/62124.bib:2:.*code 137") +test $count -eq 2 || wail + +echo "checking line number of first invalid character on" \ + "bibliography line 3" +count=$(echo "$output" | grep -c "refer:.*/62124.bib:3:.*code 136") +test $count -eq 2 || wail + +echo "checking line number of second invalid character on" \ + "bibliography line 3" +count=$(echo "$output" | grep -c "refer:.*/62124.bib:3:.*code 137") +test $count -eq 2 || wail + +# Problems with the input file should also be accurately located. + +echo "checking line number of invalid refer(1) command on input line 4" +echo "$output" +echo "$output" | grep -q "refer:.*:4:.*unknown command" || wail + +echo "checking line number of invalid refer(1) command on input line 9" +echo "$output" +echo "$output" | grep -q "refer:.*:9:.*unknown command" || wail + +# Regression-test Savannah #62391. + +output=$(printf '\0201\n' | "$refer" 2>&1 >/dev/null) + +echo "checking line number of invalid input character on input line 1" +echo "$output" | grep -q "refer:.*:1:.*invalid input character" \ + || wail + +output=$(printf '.R1\nbogus \0200\n.R2\n' | "$refer" 2>&1 >/dev/null) + +echo "checking line number of invalid input character after refer(1)" \ + "command on input line 2" +echo "$output" | grep -q "refer:.*:2:.*invalid input character" \ + || wail + +output=$(printf '.R1\ndatabase nonexistent.bib\n.R2\n' | "$refer" 2>&1 \ + >/dev/null) + +echo "checking line number of attempt to load nonexistent database" +echo "$output" | grep -q "refer:.*:2:.*can't open 'nonexistent\.bib':" \ + || wail + +output=$(printf '.R1\ninclude nonexistent.bib\n.R2\n' | "$refer" 2>&1 \ + >/dev/null) + +echo "checking line number of attempt to load nonexistent inclusion" +echo "$output" | grep -q "refer:.*:2:.*can't open 'nonexistent\.bib':" \ + || wail +test -z "$fail" || exit 1 + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/refer/token.cpp b/src/preproc/refer/token.cpp new file mode 100644 index 0000000..e643cbd --- /dev/null +++ b/src/preproc/refer/token.cpp @@ -0,0 +1,377 @@ +// -*- 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 "refer.h" +#include "token.h" + +#define TOKEN_TABLE_SIZE 1009 +// I believe in Icelandic thorn sorts after z. +#define THORN_SORT_KEY "{" + +struct token_table_entry { + const char *tok; + token_info ti; + token_table_entry(); +}; + +token_table_entry token_table[TOKEN_TABLE_SIZE]; +int ntokens = 0; + +static void skip_name(const char **ptr, const char *end) +{ + if (*ptr < end) { + switch (*(*ptr)++) { + case '(': + if (*ptr < end) { + *ptr += 1; + if (*ptr < end) + *ptr += 1; + } + break; + case '[': + while (*ptr < end) + if (*(*ptr)++ == ']') + break; + break; + } + } +} + +int get_token(const char **ptr, const char *end) +{ + if (*ptr >= end) + return 0; + char c = *(*ptr)++; + if (c == '\\' && *ptr < end) { + switch (**ptr) { + default: + *ptr += 1; + break; + case '(': + case '[': + skip_name(ptr, end); + break; + case '*': + case 'f': + *ptr += 1; + skip_name(ptr, end); + break; + } + } + return 1; +} + +token_info::token_info() +: type(TOKEN_OTHER), sort_key(0), other_case(0) +{ +} + +void token_info::set(token_type t, const char *sk, const char *oc) +{ + assert(oc == 0 || t == TOKEN_UPPER || t == TOKEN_LOWER); + type = t; + sort_key = sk; + other_case = oc; +} + +void token_info::sortify(const char *start, const char *end, string &result) + const +{ + if (sort_key) + result += sort_key; + else if (type == TOKEN_UPPER || type == TOKEN_LOWER) { + for (; start < end; start++) + if (csalpha(*start)) + result += cmlower(*start); + } +} + +int token_info::sortify_non_empty(const char *start, const char *end) const +{ + if (sort_key) + return *sort_key != '\0'; + if (type != TOKEN_UPPER && type != TOKEN_LOWER) + return 0; + for (; start < end; start++) + if (csalpha(*start)) + return 1; + return 0; +} + + +void token_info::lower_case(const char *start, const char *end, + string &result) const +{ + if (type != TOKEN_UPPER) { + while (start < end) + result += *start++; + } + else if (other_case) + result += other_case; + else { + while (start < end) + result += cmlower(*start++); + } +} + +void token_info::upper_case(const char *start, const char *end, + string &result) const +{ + if (type != TOKEN_LOWER) { + while (start < end) + result += *start++; + } + else if (other_case) + result += other_case; + else { + while (start < end) + result += cmupper(*start++); + } +} + +token_table_entry::token_table_entry() +: tok(0) +{ +} + +static void store_token(const char *tok, token_type typ, + const char *sk = 0, const char *oc = 0) +{ + unsigned n = hash_string(tok, strlen(tok)) % TOKEN_TABLE_SIZE; + for (;;) { + if (token_table[n].tok == 0) { + if (++ntokens == TOKEN_TABLE_SIZE) + assert(0); + token_table[n].tok = tok; + break; + } + if (strcmp(tok, token_table[n].tok) == 0) + break; + if (n == 0) + n = TOKEN_TABLE_SIZE - 1; + else + --n; + } + token_table[n].ti.set(typ, sk, oc); +} + + +token_info default_token_info; + +const token_info *lookup_token(const char *start, const char *end) +{ + unsigned n = hash_string(start, end - start) % TOKEN_TABLE_SIZE; + for (;;) { + if (token_table[n].tok == 0) + break; + if (strlen(token_table[n].tok) == size_t(end - start) + && memcmp(token_table[n].tok, start, end - start) == 0) + return &(token_table[n].ti); + if (n == 0) + n = TOKEN_TABLE_SIZE - 1; + else + --n; + } + return &default_token_info; +} + +static void init_ascii() +{ + const char *p; + for (p = "abcdefghijklmnopqrstuvwxyz"; *p; p++) { + char buf[2]; + buf[0] = *p; + buf[1] = '\0'; + store_token(strsave(buf), TOKEN_LOWER); + buf[0] = cmupper(buf[0]); + store_token(strsave(buf), TOKEN_UPPER); + } + for (p = "0123456789"; *p; p++) { + char buf[2]; + buf[0] = *p; + buf[1] = '\0'; + const char *s = strsave(buf); + store_token(s, TOKEN_OTHER, s); + } + for (p = ".,:;?!"; *p; p++) { + char buf[2]; + buf[0] = *p; + buf[1] = '\0'; + store_token(strsave(buf), TOKEN_PUNCT); + } + store_token("-", TOKEN_HYPHEN); +} + +static void store_letter(const char *lower, const char *upper, + const char *sort_key = 0) +{ + store_token(lower, TOKEN_LOWER, sort_key, upper); + store_token(upper, TOKEN_UPPER, sort_key, lower); +} + +static void init_letter(unsigned char uc_code, unsigned char lc_code, + const char *sort_key) +{ + char lbuf[2]; + lbuf[0] = lc_code; + lbuf[1] = 0; + char ubuf[2]; + ubuf[0] = uc_code; + ubuf[1] = 0; + store_letter(strsave(lbuf), strsave(ubuf), sort_key); +} + +static void init_latin1() +{ + init_letter(0xc0, 0xe0, "a"); + init_letter(0xc1, 0xe1, "a"); + init_letter(0xc2, 0xe2, "a"); + init_letter(0xc3, 0xe3, "a"); + init_letter(0xc4, 0xe4, "a"); + init_letter(0xc5, 0xe5, "a"); + init_letter(0xc6, 0xe6, "ae"); + init_letter(0xc7, 0xe7, "c"); + init_letter(0xc8, 0xe8, "e"); + init_letter(0xc9, 0xe9, "e"); + init_letter(0xca, 0xea, "e"); + init_letter(0xcb, 0xeb, "e"); + init_letter(0xcc, 0xec, "i"); + init_letter(0xcd, 0xed, "i"); + init_letter(0xce, 0xee, "i"); + init_letter(0xcf, 0xef, "i"); + + init_letter(0xd0, 0xf0, "d"); + init_letter(0xd1, 0xf1, "n"); + init_letter(0xd2, 0xf2, "o"); + init_letter(0xd3, 0xf3, "o"); + init_letter(0xd4, 0xf4, "o"); + init_letter(0xd5, 0xf5, "o"); + init_letter(0xd6, 0xf6, "o"); + init_letter(0xd8, 0xf8, "o"); + init_letter(0xd9, 0xf9, "u"); + init_letter(0xda, 0xfa, "u"); + init_letter(0xdb, 0xfb, "u"); + init_letter(0xdc, 0xfc, "u"); + init_letter(0xdd, 0xfd, "y"); + init_letter(0xde, 0xfe, THORN_SORT_KEY); + + store_token("\337", TOKEN_LOWER, "ss", "SS"); + store_token("\377", TOKEN_LOWER, "y", "Y"); +} + +static void init_two_char_letter(char l1, char l2, char u1, char u2, + const char *sk = 0) +{ + char buf[6]; + buf[0] = '\\'; + buf[1] = '('; + buf[2] = l1; + buf[3] = l2; + buf[4] = '\0'; + const char *p = strsave(buf); + buf[2] = u1; + buf[3] = u2; + store_letter(p, strsave(buf), sk); + buf[1] = '['; + buf[4] = ']'; + buf[5] = '\0'; + p = strsave(buf); + buf[2] = l1; + buf[3] = l2; + store_letter(strsave(buf), p, sk); + +} + +static void init_special_chars() +{ + const char *p; + for (p = "':^`~"; *p; p++) + for (const char *q = "aeiouy"; *q; q++) { + // Use a variable to work around bug in gcc 2.0 + char c = cmupper(*q); + init_two_char_letter(*p, *q, *p, c); + } + for (p = "/l/o~n,coeaeij"; *p; p += 2) { + // Use variables to work around bug in gcc 2.0 + char c0 = cmupper(p[0]); + char c1 = cmupper(p[1]); + init_two_char_letter(p[0], p[1], c0, c1); + } + init_two_char_letter('v', 's', 'v', 'S', "s"); + init_two_char_letter('v', 'z', 'v', 'Z', "z"); + init_two_char_letter('o', 'a', 'o', 'A', "a"); + init_two_char_letter('T', 'p', 'T', 'P', THORN_SORT_KEY); + init_two_char_letter('-', 'd', '-', 'D'); + + store_token("\\(ss", TOKEN_LOWER, 0, "SS"); + store_token("\\[ss]", TOKEN_LOWER, 0, "SS"); + + store_token("\\(Sd", TOKEN_LOWER, "d", "\\(-D"); + store_token("\\[Sd]", TOKEN_LOWER, "d", "\\[-D]"); + store_token("\\(hy", TOKEN_HYPHEN); + store_token("\\[hy]", TOKEN_HYPHEN); + store_token("\\(en", TOKEN_RANGE_SEP); + store_token("\\[en]", TOKEN_RANGE_SEP); +} + +static void init_strings() +{ + char buf[6]; + buf[0] = '\\'; + buf[1] = '*'; + for (const char *p = "'`^^,:~v_o./;"; *p; p++) { + buf[2] = *p; + buf[3] = '\0'; + store_token(strsave(buf), TOKEN_ACCENT); + buf[2] = '['; + buf[3] = *p; + buf[4] = ']'; + buf[5] = '\0'; + store_token(strsave(buf), TOKEN_ACCENT); + } + + // -ms special letters + store_letter("\\*(th", "\\*(Th", THORN_SORT_KEY); + store_letter("\\*[th]", "\\*[Th]", THORN_SORT_KEY); + store_letter("\\*(d-", "\\*(D-"); + store_letter("\\*[d-]", "\\*[D-]"); + store_letter("\\*(ae", "\\*(Ae", "ae"); + store_letter("\\*[ae]", "\\*[Ae]", "ae"); + store_letter("\\*(oe", "\\*(Oe", "oe"); + store_letter("\\*[oe]", "\\*[Oe]", "oe"); + + store_token("\\*3", TOKEN_LOWER, "y", "Y"); + store_token("\\*8", TOKEN_LOWER, "ss", "SS"); + store_token("\\*q", TOKEN_LOWER, "o", "O"); +} + +struct token_initer { + token_initer(); +}; + +static token_initer the_token_initer; + +token_initer::token_initer() +{ + init_ascii(); + init_latin1(); + init_special_chars(); + init_strings(); + default_token_info.set(TOKEN_OTHER); +} diff --git a/src/preproc/refer/token.h b/src/preproc/refer/token.h new file mode 100644 index 0000000..9cd688c --- /dev/null +++ b/src/preproc/refer/token.h @@ -0,0 +1,87 @@ +// -*- 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/>. */ + +enum token_type { + TOKEN_OTHER, + TOKEN_UPPER, + TOKEN_LOWER, + TOKEN_ACCENT, + TOKEN_PUNCT, + TOKEN_HYPHEN, + TOKEN_RANGE_SEP +}; + +class token_info { +private: + token_type type; + const char *sort_key; + const char *other_case; +public: + token_info(); + void set(token_type, const char *sk = 0, const char *oc = 0); + void lower_case(const char *start, const char *end, string &result) const; + void upper_case(const char *start, const char *end, string &result) const; + void sortify(const char *start, const char *end, string &result) const; + int sortify_non_empty(const char *start, const char *end) const; + int is_upper() const; + int is_lower() const; + int is_accent() const; + int is_other() const; + int is_punct() const; + int is_hyphen() const; + int is_range_sep() const; +}; + +inline int token_info::is_upper() const +{ + return type == TOKEN_UPPER; +} + +inline int token_info::is_lower() const +{ + return type == TOKEN_LOWER; +} + +inline int token_info::is_accent() const +{ + return type == TOKEN_ACCENT; +} + +inline int token_info::is_other() const +{ + return type == TOKEN_OTHER; +} + +inline int token_info::is_punct() const +{ + return type == TOKEN_PUNCT; +} + +inline int token_info::is_hyphen() const +{ + return type == TOKEN_HYPHEN; +} + +inline int token_info::is_range_sep() const +{ + return type == TOKEN_RANGE_SEP; +} + +int get_token(const char **ptr, const char *end); +const token_info *lookup_token(const char *start, const char *end); diff --git a/src/preproc/soelim/TODO b/src/preproc/soelim/TODO new file mode 100644 index 0000000..f2a3924 --- /dev/null +++ b/src/preproc/soelim/TODO @@ -0,0 +1 @@ +Understand .pso. diff --git a/src/preproc/soelim/soelim.1.man b/src/preproc/soelim/soelim.1.man new file mode 100644 index 0000000..4a1c042 --- /dev/null +++ b/src/preproc/soelim/soelim.1.man @@ -0,0 +1,456 @@ +'\" p +.TH @g@soelim @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +@g@soelim \- recursively interpolate source requests in +.I roff +or other text files +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1989-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_soelim_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 +.\" Man pages are seldom preprocessed with pic(1). +.mso pic.tmac +. +. +.\" ==================================================================== +.\" Definitions +.\" ==================================================================== +. +.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X +.el .ds tx TeX +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY @g@soelim +.RB [ \-Crt ] +.RB [ \-I +.IR dir ] +.RI [ input-file\~ .\|.\|.] +.YS +. +. +.SY @g@soelim +.B \-\-help +.YS +. +. +.SY @g@soelim +.B \-v +. +.SY @g@soelim +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +GNU +.I soelim \" GNU +is a preprocessor for the +.MR groff @MAN7EXT@ +document formatting system. +. +.I @g@soelim +works as a filter to eliminate source requests in +.MR roff @MAN7EXT@ +input files; +that is, +it replaces lines of the form +.RB \[lq] .so +.IR included-file \[rq] +within each text +.I input-file +with the contents of +.IR included-file , +recursively. +. +By default, +it writes +.B lf +requests as well to record the name and line number of each +.I input-file +and +.IR included-file , +so that any diagnostics produced by later processing can be accurately +traced to the original input. +. +Options allow this information to be suppressed +.RB ( \-r ) +or supplied in \*[tx] comments instead +.RB ( \-t ). +. +In the absence of +.I input-file +arguments, +.I @g@soelim +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.PP +If the name of a +.I macro-file +contains a backslash, +use +.B \[rs]\[rs] +or +.B \[rs]e +to embed it. +. +To embed a space, +write +.RB \[lq] \[rs]\~ \[rq] +(backslash followed by a space). +. +Any other escape sequence in +.IR macro-file , +including +.RB \[lq] \[rs][rs] \[rq], +prevents +.I @g@soelim +from replacing the source request. +. +. +.PP +The dot must be at the beginning of a line and must be followed by +.RB \[lq] so \[rq] +without intervening spaces or tabs for +.I @g@soelim +to handle it. +. +This convention allows source requests to be \[lq]protected\[rq] from +processing by +.IR @g@soelim , +for instance as part of macro definitions or +.RB \[lq] if \[rq] +requests. +. +. +.PP +There must also be at least one space between +.RB \[lq] so \[rq] +and its +.I macro-file +argument. +. +The +.B \-C +option overrides this requirement. +. +. +.PP +The foregoing is the limit of +.IR @g@soelim 's +understanding of the +.I roff +language; +it does not, +for example, +replace the input line +. +.RS +.EX +\&.if 1 .so otherfile +.EE +.RE +. +with the contents of +.IR otherfile . +. +With its +.B \-r +option, +therefore, +.I @g@soelim +can be used to process text files in general, +to flatten a tree of input documents. +. +. +.PP +.I soelim \" generic +was designed to handle situations where the target of a +.I roff \" generic +source request requires a preprocessor such as +.MR @g@eqn @MAN1EXT@ , +.MR @g@pic @MAN1EXT@ , +.MR @g@refer @MAN1EXT@ , +or +.MR @g@tbl @MAN1EXT@ . +. +The usual processing sequence of +.MR groff @MAN1EXT@ +is as follows. +. +.\" Does this groff installation use a command prefix? In installed +.\" pages, this comparison will not look like it needs to be dynamically +.\" decided. +.\" +.\" This is done so that the box sizes (in the pic(1) diagram) and arrow +.\" alignments (in the text alternative) can remain fixed. +.if !'@g@'\%' \{\ +In the diagrams below, +the traditional names for +.I soelim +and +.I troff +are used; +on this system, +the GNU versions are installed as +.I @g@soelim +and +.IR @g@troff . +.\} +. +. +.PP +.ie t \{\ +.PS +.ps 10 +.vs 12 +box invisible width 0.5 height 0.4 "input" "file"; +move to last box .bottom; +down; +arrow 0.3; +box invisible width 0.8 height 0.2 "preprocessor"; +move to last box .right +right; +arrow 0.3; +A: box invisible width 0.35 height 0.2 "troff"; +move to last box .top; +up; +move 0.3; +box invisible width 0.6 height 0.4 "sourced" "file"; +line <- up 0.3 from A.top; +move to A.right; +right; +arrow 0.3; +box invisible width 0.85 height 0.2 "postprocessor"; +move to last box .bottom; +down; +arrow 0.3; +box invisible width 0.5 height 0.4 "output" "file" +.ps +.vs +.PE +.\} +.el \{\ +.EX + input sourced + file file + \[bv] \[bv] + \[da] \[da] + preprocessor \[an]\[->] troff \[an]\[->] postprocessor + \[bv] + \[da] + output + file +.EE +.\} +.PP +That is, +files sourced with +.RB \[lq] so \[rq] +are normally read +.I only +by the formatter, +.IR @g@troff . +. +.I @g@soelim +is +.I not +required for +.I @g@troff +to source files. +. +. +.PP +If a file to be sourced should also be preprocessed, +it must already be read +.I before +the input file passes through the preprocessor. +. +.IR @g@soelim , +normally invoked via +.IR groff 's +.B \-s +option, +handles this. +. +. +.PP +.ie t \{\ +.PS +.ps 10 +.vs 12 +box invisible width 0.5 height 0.4 "input" "file"; +move to last box .bottom; +down; +arrow 0.3; +A: box invisible width 0.5 height 0.2 "soelim"; +line <- 0.3; +box invisible width 0.5 height 0.4 "sourced" "file"; +move to A.right; +right; +arrow 0.3; +box invisible width 0.8 height 0.2 "preprocessor"; +arrow 0.3; +box invisible width 0.35 height 0.2 "troff"; +arrow 0.3 +box invisible width 0.85 height 0.2 "postprocessor"; +move to last box .bottom; +down; +arrow 0.3; +box invisible width 0.5 height 0.4 "output" "file" +.ps +.vs +.PE +.\} +.el \{\ +.EX + input + file + \[bv] + \[da] + soelim \[an]\[->] preprocessor \[an]\[->] troff \[an]\[->] \ +postprocessor + \[ua] \[bv] + \[bv] \[da] + sourced output + file file +.EE +.\} +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-C +Recognize an input line starting with +.B .so +even if a character other than a space or newline follows. +. +.TP +.BI \-I\~ dir +Search the directory +.I dir +path for +.I input- +and +.I included-files. +. +.B \-I +may be specified more than once; +each +.I dir +is searched in the given order. +. +To search the current working directory before others, +add +.RB \[lq] "\-I .\&" \[rq] +at the desired place; +it is otherwise searched last. +. +. +.TP +.B \-r +Write files \[lq]raw\[rq]; +do not add +.B lf +requests. +. +. +.TP +.B \-t +Emit \*[tx] comment lines starting with +.RB \[lq] % \[rq] +indicating the current file and line number, +rather than +.B lf +requests for the same purpose. +. +. +.PP +If both +.B \-r +and +.B \-t +are given, +the last one specified controls. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.MR groff @MAN1EXT@ +. +. +.\" Clean up. +.rm tx +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_soelim_1_man_C] +.do rr *groff_soelim_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/preproc/soelim/soelim.am b/src/preproc/soelim/soelim.am new file mode 100644 index 0000000..1aa7941 --- /dev/null +++ b/src/preproc/soelim/soelim.am @@ -0,0 +1,31 @@ +# 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 += soelim +soelim_LDADD = libgroff.a $(LIBM) lib/libgnu.a +soelim_SOURCES = src/preproc/soelim/soelim.cpp +PREFIXMAN1 += src/preproc/soelim/soelim.1 +EXTRA_DIST += \ + src/preproc/soelim/TODO \ + src/preproc/soelim/soelim.1.man + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/soelim/soelim.cpp b/src/preproc/soelim/soelim.cpp new file mode 100644 index 0000000..bafc5cd --- /dev/null +++ b/src/preproc/soelim/soelim.cpp @@ -0,0 +1,315 @@ +/* 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 <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> + +#include "errarg.h" +#include "error.h" +#include "stringclass.h" +#include "nonposix.h" +#include "searchpath.h" +#include "lf.h" + +// The include search path initially contains only the current directory. +static search_path include_search_path(0, 0, 0, 1); + +int compatible_flag = 0; +int raw_flag = 0; +int tex_flag = 0; + +extern "C" const char *Version_string; + +int do_file(const char *); + + +void usage(FILE *stream) +{ + fprintf(stream, "usage: %s [-Crt] [-I dir] [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]; + int opt; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((opt = getopt_long(argc, argv, "CI:rtv", long_options, NULL)) != EOF) + switch (opt) { + case 'v': + printf("GNU soelim (groff) version %s\n", Version_string); + exit(0); + break; + case 'C': + compatible_flag = 1; + break; + case 'I': + include_search_path.command_line_dir(optarg); + break; + case 'r': + raw_flag = 1; + break; + case 't': + tex_flag = 1; + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + int nbad = 0; + if (optind >= argc) + nbad += !do_file("-"); + else + for (int i = optind; i < argc; i++) + nbad += !do_file(argv[i]); + if (ferror(stdout) || fflush(stdout) < 0) + fatal("output error"); + return nbad != 0; +} + +void set_location() +{ + if (!raw_flag) { + if (!tex_flag) + printf(".lf %d %s\n", current_lineno, current_filename); + else + printf("%% file %s, line %d\n", current_filename, current_lineno); + } +} + +void do_so(const char *line) +{ + const char *p = line; + while (*p == ' ') + p++; + string filename; + int success = 1; + for (const char *q = p; + success && *q != '\0' && *q != '\n' && *q != ' '; + q++) + if (*q == '\\') { + switch (*++q) { + case 'e': + case '\\': + filename += '\\'; + break; + case ' ': + filename += ' '; + break; + default: + success = 0; + break; + } + } + else + filename += char(*q); + if (success && filename.length() > 0) { + filename += '\0'; + const char *fn = current_filename; + int ln = current_lineno; + current_lineno--; + if (do_file(filename.contents())) { + current_filename = fn; + current_lineno = ln; + set_location(); + return; + } + current_lineno++; + } + fputs(".so", stdout); + fputs(line, stdout); +} + +int do_file(const char *filename) +{ + char *file_name_in_path = 0; + FILE *fp = include_search_path.open_file_cautious(filename, + &file_name_in_path); + int err = errno; + string whole_filename(file_name_in_path ? file_name_in_path : filename); + whole_filename += '\0'; + free(file_name_in_path); + if (fp == 0) { + error("can't open '%1': %2", whole_filename.contents(), strerror(err)); + return 0; + } + normalize_for_lf(whole_filename); + current_filename = whole_filename.contents(); + current_lineno = 1; + set_location(); + enum { START, MIDDLE, HAD_DOT, HAD_s, HAD_so, HAD_l, HAD_lf } state = START; + for (;;) { + int c = getc(fp); + if (c == EOF) + break; + switch (state) { + case START: + if (c == '.') + state = HAD_DOT; + else { + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case MIDDLE: + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + break; + case HAD_DOT: + if (c == 's') + state = HAD_s; + else if (c == 'l') + state = HAD_l; + else { + putchar('.'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_s: + if (c == 'o') + state = HAD_so; + else { + putchar('.'); + putchar('s'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_so: + if (c == ' ' || c == '\n' || compatible_flag) { + string line; + for (; c != EOF && c != '\n'; c = getc(fp)) + line += c; + current_lineno++; + line += '\n'; + line += '\0'; + do_so(line.contents()); + state = START; + } + else { + fputs(".so", stdout); + putchar(c); + state = MIDDLE; + } + break; + case HAD_l: + if (c == 'f') + state = HAD_lf; + else { + putchar('.'); + putchar('l'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_lf: + if (c == ' ' || c == '\n' || compatible_flag) { + string line; + for (; c != EOF && c != '\n'; c = getc(fp)) + line += c; + current_lineno++; + line += '\n'; + line += '\0'; + interpret_lf_args(line.contents()); + printf(".lf%s", line.contents()); + state = START; + } + else { + fputs(".lf", stdout); + putchar(c); + state = MIDDLE; + } + break; + default: + assert(0); + } + } + switch (state) { + case HAD_DOT: + fputs(".\n", stdout); + break; + case HAD_l: + fputs(".l\n", stdout); + break; + case HAD_s: + fputs(".s\n", stdout); + break; + case HAD_lf: + fputs(".lf\n", stdout); + break; + case HAD_so: + fputs(".so\n", stdout); + break; + case MIDDLE: + putc('\n', stdout); + break; + case START: + break; + } + if (fp != stdin) + fclose(fp); + current_filename = 0; + return 1; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/tbl/main.cpp b/src/preproc/tbl/main.cpp new file mode 100644 index 0000000..db105c2 --- /dev/null +++ b/src/preproc/tbl/main.cpp @@ -0,0 +1,1692 @@ +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +groff is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or +(at your option) any later version. + +groff is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "table.h" + +#define MAX_POINT_SIZE 99 +#define MAX_VERTICAL_SPACING 72 + +extern "C" const char *Version_string; + +int compatible_flag = 0; + +class table_input { + FILE *fp; + enum { START, MIDDLE, + REREAD_T, REREAD_TE, REREAD_E, + LEADER_1, LEADER_2, LEADER_3, LEADER_4, + END, ERROR } state; + string unget_stack; +public: + table_input(FILE *); + int get(); + int ended() { return unget_stack.empty() && state == END; } + void unget(char); +}; + +table_input::table_input(FILE *p) +: fp(p), state(START) +{ +} + +void table_input::unget(char c) +{ + assert(c != '\0'); + unget_stack += c; + if (c == '\n') + current_lineno--; +} + +int table_input::get() +{ + int len = unget_stack.length(); + if (len != 0) { + unsigned char c = unget_stack[len - 1]; + unget_stack.set_length(len - 1); + if (c == '\n') + current_lineno++; + return c; + } + int c; + for (;;) { + switch (state) { + case START: + if ((c = getc(fp)) == '.') { + if ((c = getc(fp)) == 'T') { + if ((c = getc(fp)) == 'E') { + if (compatible_flag) { + state = END; + return EOF; + } + else { + c = getc(fp); + if (c != EOF) + ungetc(c, fp); + if (c == EOF || c == ' ' || c == '\n') { + state = END; + return EOF; + } + state = REREAD_TE; + return '.'; + } + } + else { + if (c != EOF) + ungetc(c, fp); + state = REREAD_T; + return '.'; + } + } + else { + if (c != EOF) + ungetc(c, fp); + state = MIDDLE; + return '.'; + } + } + else if (c == EOF) { + state = ERROR; + return EOF; + } + else { + if (c == '\n') + current_lineno++; + else { + state = MIDDLE; + if (c == '\0') { + error("invalid input character code 0"); + break; + } + } + return c; + } + break; + case MIDDLE: + // handle line continuation and uninterpreted leader character + if ((c = getc(fp)) == '\\') { + c = getc(fp); + if (c == '\n') { + current_lineno++; + c = getc(fp); + } + else if (c == 'a' && compatible_flag) { + state = LEADER_1; + return '\\'; + } + else { + if (c != EOF) + ungetc(c, fp); + c = '\\'; + } + } + if (c == EOF) { + state = ERROR; + return EOF; + } + else { + if (c == '\n') { + state = START; + current_lineno++; + } + else if (c == '\0') { + error("invalid input character code 0"); + break; + } + return c; + } + case REREAD_T: + state = MIDDLE; + return 'T'; + case REREAD_TE: + state = REREAD_E; + return 'T'; + case REREAD_E: + state = MIDDLE; + return 'E'; + case LEADER_1: + state = LEADER_2; + return '*'; + case LEADER_2: + state = LEADER_3; + return '('; + case LEADER_3: + state = LEADER_4; + return PREFIX_CHAR; + case LEADER_4: + state = MIDDLE; + return LEADER_CHAR; + case END: + case ERROR: + return EOF; + } + } +} + +void process_input_file(FILE *); +void process_table(table_input &in); + +void process_input_file(FILE *fp) +{ + enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state; + state = START; + int c; + while ((c = getc(fp)) != EOF) + switch (state) { + case START: + if (c == '.') + state = HAD_DOT; + else { + if (c == '\n') + current_lineno++; + else + state = MIDDLE; + putchar(c); + } + break; + case MIDDLE: + if (c == '\n') { + current_lineno++; + state = START; + } + putchar(c); + break; + case HAD_DOT: + if (c == 'T') + state = HAD_T; + else if (c == 'l') + state = HAD_l; + else { + putchar('.'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_T: + if (c == 'S') + state = HAD_TS; + else { + putchar('.'); + putchar('T'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_TS: + if (c == ' ' || c == '\n' || compatible_flag) { + putchar('.'); + putchar('T'); + putchar('S'); + while (c != '\n') { + if (c == EOF) { + error("end of file at beginning of table"); + return; + } + putchar(c); + c = getc(fp); + } + putchar('\n'); + current_lineno++; + { + table_input input(fp); + process_table(input); + set_troff_location(current_filename, current_lineno); + if (input.ended()) { + fputs(".TE", stdout); + while ((c = getc(fp)) != '\n') { + if (c == EOF) { + putchar('\n'); + return; + } + putchar(c); + } + putchar('\n'); + current_lineno++; + } + } + state = START; + } + else { + fputs(".TS", stdout); + putchar(c); + state = MIDDLE; + } + break; + case HAD_l: + if (c == 'f') + state = HAD_lf; + else { + putchar('.'); + putchar('l'); + putchar(c); + if (c == '\n') { + current_lineno++; + state = START; + } + else + state = MIDDLE; + } + break; + case HAD_lf: + if (c == ' ' || c == '\n' || compatible_flag) { + string line; + while (c != EOF) { + line += c; + if (c == '\n') { + current_lineno++; + break; + } + c = getc(fp); + } + line += '\0'; + interpret_lf_args(line.contents()); + printf(".lf%s", line.contents()); + state = START; + } + else { + fputs(".lf", stdout); + putchar(c); + state = MIDDLE; + } + break; + default: + assert(0 == "invalid `state` in switch"); + } + switch(state) { + case START: + break; + case MIDDLE: + putchar('\n'); + break; + case HAD_DOT: + fputs(".\n", stdout); + break; + case HAD_l: + fputs(".l\n", stdout); + break; + case HAD_T: + fputs(".T\n", stdout); + break; + case HAD_lf: + fputs(".lf\n", stdout); + break; + case HAD_TS: + fputs(".TS\n", stdout); + break; + } + if (fp != stdin) + fclose(fp); +} + +struct options { + unsigned flags; + int linesize; + char delim[2]; + char tab_char; + char decimal_point_char; + + options(); +}; + +options::options() +: flags(0), linesize(0), tab_char('\t'), decimal_point_char('.') +{ + delim[0] = delim[1] = '\0'; +} + +// Return non-zero if p and q are the same ignoring case. + +int strieq(const char *p, const char *q) +{ + for (; cmlower(*p) == cmlower(*q); p++, q++) + if (*p == '\0') + return 1; + return 0; +} + +// Handle region options. Return a null pointer if we should give up on +// this table. +options *process_options(table_input &in) +{ + options *opt = new options; + string line; + int level = 0; + for (;;) { + int c = in.get(); + if (c == EOF) { + int i = line.length(); + while (--i >= 0) + in.unget(line[i]); + return opt; + } + if (c == '\n') { + in.unget(c); + int i = line.length(); + while (--i >= 0) + in.unget(line[i]); + return opt; + } + else if (c == '(') + level++; + else if (c == ')') + level--; + else if (c == ';' && 0 == level) { + line += '\0'; + break; + } + line += c; + } + if (line.empty()) + return opt; + char *p = &line[0]; + for (;;) { + while (!csalpha(*p) && *p != '\0') + p++; + if (*p == '\0') + break; + char *q = p; + while (csalpha(*q)) + q++; + char *arg = 0; + if (*q != '(' && *q != '\0') + *q++ = '\0'; + while (csspace(*q)) + q++; + if (*q == '(') { + *q++ = '\0'; + arg = q; + while (*q != ')' && *q != '\0') + q++; + if (*q == '\0') + error("'%1' region option argument missing closing parenthesis", + arg); + else + *q++ = '\0'; + } + if (*p == '\0') { + if (arg) + error("'%1' region option argument cannot be empty", arg); + } + else if (strieq(p, "tab")) { + if (!arg) + error("'tab' region option requires argument in parentheses"); + else { + if (arg[0] == '\0' || arg[1] != '\0') + error("'tab' region option argument must be a single" + " character"); + else + opt->tab_char = arg[0]; + } + } + else if (strieq(p, "linesize")) { + if (!arg) + error("'linesize' region option requires argument in" + " parentheses"); + else { + if (sscanf(arg, "%d", &opt->linesize) != 1) + error("invalid argument to 'linesize' region option: '%1'", + arg); + else if (opt->linesize <= 0) { + error("'linesize' region option argument must be positive"); + opt->linesize = 0; + } + } + } + else if (strieq(p, "delim")) { + if (!arg) + error("'delim' region option requires argument in parentheses"); + else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0') + error("argument to 'delim' option must be two characters"); + else { + opt->delim[0] = arg[0]; + opt->delim[1] = arg[1]; + } + } + else if (strieq(p, "center") || strieq(p, "centre")) { + if (arg) + error("'center' region option does not take an argument"); + opt->flags |= table::CENTER; + } + else if (strieq(p, "expand")) { + if (arg) + error("'expand' region option does not take an argument"); + opt->flags |= table::EXPAND; + opt->flags |= table::GAP_EXPAND; + } + else if (strieq(p, "box") || strieq(p, "frame")) { + if (arg) + error("'box' region option does not take an argument"); + opt->flags |= table::BOX; + } + else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) { + if (arg) + error("'doublebox' region option does not take an argument"); + opt->flags |= table::DOUBLEBOX; + } + else if (strieq(p, "allbox")) { + if (arg) + error("'allbox' region option does not take an argument"); + opt->flags |= table::ALLBOX; + } + else if (strieq(p, "nokeep")) { + if (arg) + error("'nokeep' region option does not take an argument"); + opt->flags |= table::NOKEEP; + } + else if (strieq(p, "nospaces")) { + if (arg) + error("'nospaces' region option does not take an argument"); + opt->flags |= table::NOSPACES; + } + else if (strieq(p, "nowarn")) { + if (arg) + error("'nowarn' region option does not take an argument"); + opt->flags |= table::NOWARN; + } + else if (strieq(p, "decimalpoint")) { + if (!arg) + error("'decimalpoint' region option requires argument in" + " parentheses"); + else { + if (arg[0] == '\0' || arg[1] != '\0') + error("'decimalpoint' region option argument must be a single" + " character"); + else + opt->decimal_point_char = arg[0]; + } + } + else if (strieq(p, "experimental")) { + opt->flags |= table::EXPERIMENTAL; + } + else { + error("unrecognized region option '%1'", p); + // delete opt; + // return 0; + } + p = q; + } + return opt; +} + +entry_modifier::entry_modifier() +: vertical_alignment(CENTER), zero_width(0), stagger(0) +{ + vertical_spacing.inc = vertical_spacing.val = 0; + point_size.inc = point_size.val = 0; +} + +entry_modifier::~entry_modifier() +{ +} + +entry_format::entry_format() : type(FORMAT_LEFT) +{ +} + +entry_format::entry_format(format_type t) : type(t) +{ +} + +void entry_format::debug_print() const +{ + switch (type) { + case FORMAT_LEFT: + putc('l', stderr); + break; + case FORMAT_CENTER: + putc('c', stderr); + break; + case FORMAT_RIGHT: + putc('r', stderr); + break; + case FORMAT_NUMERIC: + putc('n', stderr); + break; + case FORMAT_ALPHABETIC: + putc('a', stderr); + break; + case FORMAT_SPAN: + putc('s', stderr); + break; + case FORMAT_VSPAN: + putc('^', stderr); + break; + case FORMAT_HLINE: + putc('_', stderr); + break; + case FORMAT_DOUBLE_HLINE: + putc('=', stderr); + break; + default: + assert(0 == "invalid column classifier in switch"); + break; + } + if (point_size.val != 0) { + putc('p', stderr); + if (point_size.inc > 0) + putc('+', stderr); + else if (point_size.inc < 0) + putc('-', stderr); + fprintf(stderr, "%d ", point_size.val); + } + if (vertical_spacing.val != 0) { + putc('v', stderr); + if (vertical_spacing.inc > 0) + putc('+', stderr); + else if (vertical_spacing.inc < 0) + putc('-', stderr); + fprintf(stderr, "%d ", vertical_spacing.val); + } + if (!font.empty()) { + putc('f', stderr); + put_string(font, stderr); + putc(' ', stderr); + } + if (!macro.empty()) { + putc('m', stderr); + put_string(macro, stderr); + putc(' ', stderr); + } + switch (vertical_alignment) { + case entry_modifier::CENTER: + break; + case entry_modifier::TOP: + putc('t', stderr); + break; + case entry_modifier::BOTTOM: + putc('d', stderr); + break; + } + if (zero_width) + putc('z', stderr); + if (stagger) + putc('u', stderr); +} + +struct format { + int nrows; + int ncolumns; + int *separation; + string *width; + char *equal; + char *expand; + entry_format **entry; + char **vline; + + format(int nr, int nc); + ~format(); + void add_rows(int n); +}; + +format::format(int nr, int nc) : nrows(nr), ncolumns(nc) +{ + int i; + separation = ncolumns > 1 ? new int[ncolumns - 1] : 0; + for (i = 0; i < ncolumns-1; i++) + separation[i] = -1; + width = new string[ncolumns]; + equal = new char[ncolumns]; + expand = new char[ncolumns]; + for (i = 0; i < ncolumns; i++) { + equal[i] = 0; + expand[i] = 0; + } + entry = new entry_format *[nrows]; + for (i = 0; i < nrows; i++) + entry[i] = new entry_format[ncolumns]; + vline = new char*[nrows]; + for (i = 0; i < nrows; i++) { + vline[i] = new char[ncolumns+1]; + for (int j = 0; j < ncolumns+1; j++) + vline[i][j] = 0; + } +} + +void format::add_rows(int n) +{ + int i; + char **old_vline = vline; + vline = new char*[nrows + n]; + for (i = 0; i < nrows; i++) + vline[i] = old_vline[i]; + delete[] old_vline; + for (i = 0; i < n; i++) { + vline[nrows + i] = new char[ncolumns + 1]; + for (int j = 0; j < ncolumns + 1; j++) + vline[nrows + i][j] = 0; + } + entry_format **old_entry = entry; + entry = new entry_format *[nrows + n]; + for (i = 0; i < nrows; i++) + entry[i] = old_entry[i]; + delete[] old_entry; + for (i = 0; i < n; i++) + entry[nrows + i] = new entry_format[ncolumns]; + nrows += n; +} + +format::~format() +{ + delete[] separation; + delete[] width; + delete[] equal; + delete[] expand; + for (int i = 0; i < nrows; i++) { + delete[] vline[i]; + delete[] entry[i]; + } + delete[] vline; + delete[] entry; +} + +struct input_entry_format : public entry_format { + input_entry_format *next; + string width; + int separation; + int vline; + int vline_count; + bool is_last_column; + bool is_equal_width; + int expand; + input_entry_format(format_type, input_entry_format * = 0); + ~input_entry_format(); + void debug_print(); +}; + +input_entry_format::input_entry_format(format_type t, input_entry_format *p) +: entry_format(t), next(p) +{ + separation = -1; + is_last_column = false; + vline = 0; + vline_count = 0; + is_equal_width = false; + expand = 0; +} + +input_entry_format::~input_entry_format() +{ +} + +void free_input_entry_format_list(input_entry_format *list) +{ + while (list) { + input_entry_format *tem = list; + list = list->next; + delete tem; + } +} + +void input_entry_format::debug_print() +{ + int i; + for (i = 0; i < vline_count; i++) + putc('|', stderr); + entry_format::debug_print(); + if (!width.empty()) { + putc('w', stderr); + putc('(', stderr); + put_string(width, stderr); + putc(')', stderr); + } + if (is_equal_width) + putc('e', stderr); + if (expand) + putc('x', stderr); + if (separation >= 0) + fprintf(stderr, "%d", separation); + for (i = 0; i < vline; i++) + putc('|', stderr); + if (is_last_column) + putc(',', stderr); +} + +// Interpret a table format specification, like "CC,LR.". Return null +// pointer if we should give up on this table. If this is a +// continuation format line, `current_format` will be the current format +// line. +format *process_format(table_input &in, options *opt, + format *current_format = 0) +{ + input_entry_format *list = 0 /* nullptr */; + bool have_expand = false; + bool is_first_row = true; + int c = in.get(); + for (;;) { + int vline_count = 0; + bool got_format = false; + bool got_period = false; + format_type t = FORMAT_LEFT; + for (;;) { + if (c == EOF) { + error("end of input while processing table format" + " specification"); + free_input_entry_format_list(list); + list = 0 /* nullptr */; + return 0 /* nullptr */; + } + switch (c) { + case 'n': + case 'N': + t = FORMAT_NUMERIC; + got_format = true; + break; + case 'a': + case 'A': + got_format = true; + t = FORMAT_ALPHABETIC; + break; + case 'c': + case 'C': + got_format = true; + t = FORMAT_CENTER; + break; + case 'l': + case 'L': + got_format = true; + t = FORMAT_LEFT; + break; + case 'r': + case 'R': + got_format = true; + t = FORMAT_RIGHT; + break; + case 's': + case 'S': + got_format = true; + t = FORMAT_SPAN; + break; + case '^': + got_format = true; + t = FORMAT_VSPAN; + break; + case '_': + case '-': // tbl also accepts this + got_format = true; + t = FORMAT_HLINE; + if (is_first_row) + opt->flags |= table::HAS_TOP_HLINE; + break; + case '=': + got_format = true; + t = FORMAT_DOUBLE_HLINE; + break; + case '.': + got_period = true; + break; + case '|': + // leading vertical line in row + opt->flags |= table::HAS_TOP_VLINE; + vline_count++; + // list->vline_count is updated later + break; + case ' ': + case '\t': + case '\n': + break; + default: + if (c == opt->tab_char) + break; + error("invalid column classifier '%1'", char(c)); + free_input_entry_format_list(list); + list = 0 /* nullptr */; + return 0 /* nullptr */; + } + if (got_period) + break; + c = in.get(); + if (got_format) + break; + } + if (got_period) + break; + list = new input_entry_format(t, list); + if (vline_count > 2) { + vline_count = 2; + error("more than 2 vertical lines at beginning of row description"); + } + list->vline_count = vline_count; + // Now handle modifiers. + vline_count = 0; + bool is_valid_modifier_sequence = true; + do { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int w = 0; + do { + w = w*10 + (c - '0'); + c = in.get(); + } while (c != EOF && csdigit(c)); + list->separation = w; + } + break; + case 'B': + case 'b': + c = in.get(); + list->font = "B"; + break; + case 'd': + case 'D': + c = in.get(); + list->vertical_alignment = entry_modifier::BOTTOM; + break; + case 'e': + case 'E': + c = in.get(); + list->is_equal_width = true; + // 'e' and 'x' are mutually exclusive + list->expand = 0; + break; + case 'f': + case 'F': + do { + c = in.get(); + } while (c == ' ' || c == '\t'); + if (c == EOF) { + error("'f' column modifier missing font name or mounting" + " position"); + break; + } + if (c == '(') { + for (;;) { + c = in.get(); + if (c == EOF || c == ' ' || c == '\t') { + error("'f' column modifier missing closing parenthesis"); + break; + } + if (c == ')') { + c = in.get(); + break; + } + list->font += char(c); + } + } + else { + list->font = c; + char cc = c; + c = in.get(); + if (!csdigit(cc) + && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') { + list->font += char(c); + c = in.get(); + } + } + break; + case 'I': + case 'i': + c = in.get(); + list->font = "I"; + break; + case 'm': + case 'M': + do { + c = in.get(); + } while (c == ' ' || c == '\t'); + if (c == EOF) { + error("'m' column modifier missing macro name"); + break; + } + if (c == '(') { + for (;;) { + c = in.get(); + if (c == EOF || c == ' ' || c == '\t') { + error("'m' column modifier missing closing parenthesis"); + break; + } + if (c == ')') { + c = in.get(); + break; + } + list->macro += char(c); + } + } + else { + list->macro = c; + char cc = c; + c = in.get(); + if (!csdigit(cc) + && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') { + list->macro += char(c); + c = in.get(); + } + } + break; + case 'p': + case 'P': + { + inc_number &ps = list->point_size; + ps.val = 0; + ps.inc = 0; + c = in.get(); + if (c == '+' || c == '-') { + ps.inc = (c == '+' ? 1 : -1); + c = in.get(); + } + if (c == EOF || !csdigit(c)) { + warning("'p' column modifier must be followed by" + " (optionally signed) integer; ignoring"); + ps.inc = 0; + } + else { + do { + ps.val *= 10; + ps.val += c - '0'; + c = in.get(); + } while (c != EOF && csdigit(c)); + } + if (ps.val > MAX_POINT_SIZE || ps.val < -MAX_POINT_SIZE) { + warning("'p' column modifier argument magnitude of %1" + " points out of range (> %2); ignoring", ps.val, + MAX_POINT_SIZE); + ps.val = 0; + ps.inc = 0; + } + break; + } + case 't': + case 'T': + c = in.get(); + list->vertical_alignment = entry_modifier::TOP; + break; + case 'u': + case 'U': + c = in.get(); + list->stagger = 1; + break; + case 'v': + case 'V': + { + inc_number &vs = list->vertical_spacing; + vs.val = 0; + vs.inc = 0; + c = in.get(); + if (c == '+' || c == '-') { + vs.inc = (c == '+' ? 1 : -1); + c = in.get(); + } + if (c == EOF || !csdigit(c)) { + warning("'v' column modifier must be followed by" + " (optionally signed) integer; ignoring"); + vs.inc = 0; + } + else { + do { + vs.val *= 10; + vs.val += c - '0'; + c = in.get(); + } while (c != EOF && csdigit(c)); + } + if (vs.val > MAX_VERTICAL_SPACING + || vs.val < -MAX_VERTICAL_SPACING) { + warning("'v' column modifier argument magnitude of %1" + " points out of range (> %2); ignoring", vs.val, + MAX_VERTICAL_SPACING); + vs.val = 0; + vs.inc = 0; + } + break; + } + case 'w': + case 'W': + c = in.get(); + while (c == ' ' || c == '\t') + c = in.get(); + if (c == '(') { + list->width = ""; + c = in.get(); + while (c != ')') { + if (c == EOF || c == '\n') { + error("'w' column modifier missing closing parenthesis"); + free_input_entry_format_list(list); + list = 0 /* nullptr */; + return 0 /* nullptr */; + } + list->width += c; + c = in.get(); + } + c = in.get(); + } + else { + if (c == '+' || c == '-') { + list->width = char(c); + c = in.get(); + } + else + list->width = ""; + if (c == EOF || !csdigit(c)) + error("invalid argument to 'w' modifier"); + else { + do { + list->width += char(c); + c = in.get(); + } while (c != EOF && csdigit(c)); + } + } + // 'w' and 'x' are mutually exclusive + list->expand = 0; + break; + case 'x': + case 'X': + c = in.get(); + list->expand = 1; + // 'x' and 'e' are mutually exclusive + list->is_equal_width = false; + // 'x' and 'w' are mutually exclusive + list->width = ""; + break; + case 'z': + case 'Z': + c = in.get(); + list->zero_width = 1; + break; + case '|': + if (is_first_row) + opt->flags |= table::HAS_TOP_VLINE; + c = in.get(); + vline_count++; + break; + case ' ': + case '\t': + c = in.get(); + break; + default: + if (c == opt->tab_char) + c = in.get(); + else + is_valid_modifier_sequence = false; + break; + } + } while (is_valid_modifier_sequence); + if (vline_count > 2) { + vline_count = 2; + error("more than 2 vertical lines after column descriptor"); + } + list->vline += vline_count; + if (c == '\n' || c == ',') { + vline_count = 0; + is_first_row = false; + c = in.get(); + list->is_last_column = true; + } + } + if (c == '.') { + do { + c = in.get(); + } while (c == ' ' || c == '\t'); + if (c != '\n') { + error("'.' is not the last character of the table format"); + free_input_entry_format_list(list); + list = 0 /* nullptr */; + return 0 /* nullptr */; + } + } + if (!list) { + error("table format specification is empty"); + free_input_entry_format_list(list); + list = 0 /* nullptr */; + return 0 /* nullptr */; + } + list->is_last_column = true; + // now reverse the list so that the first row is at the beginning + input_entry_format *rev = 0; + while (list != 0) { + input_entry_format *tem = list->next; + list->next = rev; + rev = list; + list = tem; + } + list = rev; + input_entry_format *tem; + +#if 0 + for (tem = list; tem; tem = tem->next) + tem->debug_print(); + putc('\n', stderr); +#endif + // compute number of columns and rows + int ncolumns = 0; + int nrows = 0; + int col = 0; + for (tem = list; tem; tem = tem->next) { + if (tem->is_last_column) { + if (col >= ncolumns) + ncolumns = col + 1; + col = 0; + nrows++; + } + else + col++; + } + int row; + format *f; + if (current_format) { + if (ncolumns > current_format->ncolumns) { + error("cannot increase the number of columns in a continued format"); + free_input_entry_format_list(list); + list = 0 /* nullptr */; + return 0 /* nullptr */; + } + f = current_format; + row = f->nrows; + f->add_rows(nrows); + } + else { + f = new format(nrows, ncolumns); + row = 0; + } + col = 0; + for (tem = list; tem; tem = tem->next) { + f->entry[row][col] = *tem; + if (col < ncolumns - 1) { + // use the greatest separation + if (tem->separation > f->separation[col]) { + if (current_format) + error("cannot change column separation in continued format"); + else + f->separation[col] = tem->separation; + } + } + else if (tem->separation >= 0) + error("column separation specified for last column"); + if (tem->is_equal_width && !f->equal[col]) { + if (current_format) + error("cannot change which columns are equal in continued format"); + else + f->equal[col] = 1; + } + if (tem->expand && !f->expand[col]) { + if (current_format) + error("cannot change which columns are expanded in continued format"); + else { + f->expand[col] = 1; + have_expand = true; + } + } + if (!tem->width.empty()) { + // use the last width + if (!f->width[col].empty() && f->width[col] != tem->width) + error("multiple widths for column %1", col + 1); + f->width[col] = tem->width; + } + if (tem->vline_count) + f->vline[row][col] = tem->vline_count; + f->vline[row][col + 1] = tem->vline; + if (tem->is_last_column) { + row++; + col = 0; + } + else + col++; + } + free_input_entry_format_list(list); + list = 0 /* nullptr */; + for (col = 0; col < ncolumns; col++) { + entry_format *e = f->entry[f->nrows - 1] + col; + if (e->type != FORMAT_HLINE + && e->type != FORMAT_DOUBLE_HLINE + && e->type != FORMAT_SPAN) + break; + } + if (col >= ncolumns) { + error("last row of format is all lines"); + delete f; + return 0 /* nullptr */; + } + if (have_expand && (opt->flags & table::EXPAND)) { + error("'x' column modifier encountered; ignoring region option" + " 'expand'"); + opt->flags &= ~table::EXPAND; + } + return f; +} + +table *process_data(table_input &in, format *f, options *opt) +{ + char tab_char = opt->tab_char; + int ncolumns = f->ncolumns; + int current_row = 0; + int format_index = 0; + bool give_up = false; + enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type; + table *tbl = new table(ncolumns, opt->flags, opt->linesize, + opt->decimal_point_char); + if (opt->delim[0] != '\0') + tbl->set_delim(opt->delim[0], opt->delim[1]); + for (;;) { + // first determine what type of line this is + int c = in.get(); + if (c == EOF) + break; + if (c == '.') { + int d = in.get(); + if (d != EOF && csdigit(d)) { + in.unget(d); + type = DATA_INPUT_LINE; + } + else { + in.unget(d); + type = TROFF_INPUT_LINE; + } + } + else if (c == '_' || c == '=') { + int d = in.get(); + if (d == '\n') { + if (c == '_') + type = SINGLE_HLINE; + else + type = DOUBLE_HLINE; + if (0 == current_row) + tbl->flags |= table::HAS_TOP_HLINE; + } + else { + in.unget(d); + type = DATA_INPUT_LINE; + } + } + else { + type = DATA_INPUT_LINE; + } + switch (type) { + case DATA_INPUT_LINE: + { + string input_entry; + if (format_index >= f->nrows) + format_index = f->nrows - 1; + // A format row that is all lines doesn't use up a data line. + while (format_index < f->nrows - 1) { + int cnt; + for (cnt = 0; cnt < ncolumns; cnt++) { + entry_format *e = f->entry[format_index] + cnt; + if (e->type != FORMAT_HLINE + && e->type != FORMAT_DOUBLE_HLINE + // Unfortunately tbl treats a span as needing data. + // && e->type != FORMAT_SPAN + ) + break; + } + if (cnt < ncolumns) + break; + for (cnt = 0; cnt < ncolumns; cnt++) + tbl->add_entry(current_row, cnt, input_entry, + f->entry[format_index] + cnt, current_filename, + current_lineno); + tbl->add_vlines(current_row, f->vline[format_index]); + format_index++; + current_row++; + } + entry_format *line_format = f->entry[format_index]; + int col = 0; + bool seen_row_comment = false; + for (;;) { + if (c == tab_char || c == '\n') { + int ln = current_lineno; + if (c == '\n') + --ln; + if ((opt->flags & table::NOSPACES)) + input_entry.remove_spaces(); + while (col < ncolumns + && line_format[col].type == FORMAT_SPAN) { + tbl->add_entry(current_row, col, "", &line_format[col], + current_filename, ln); + col++; + } + if (c == '\n' && input_entry.length() == 2 + && input_entry[0] == 'T' && input_entry[1] == '{') { + input_entry = ""; + ln++; + enum { + START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT, + GOT_l, GOT_lf, END + } state = START; + while (state != END) { + c = in.get(); + if (c == EOF) + break; + switch (state) { + case START: + if (c == 'T') + state = GOT_T; + else if (c == '.') + state = GOT_DOT; + else { + input_entry += c; + if (c != '\n') + state = MIDDLE; + } + break; + case GOT_T: + if (c == '}') + state = GOT_RIGHT_BRACE; + else { + input_entry += 'T'; + input_entry += c; + state = c == '\n' ? START : MIDDLE; + } + break; + case GOT_DOT: + if (c == 'l') + state = GOT_l; + else { + input_entry += '.'; + input_entry += c; + state = c == '\n' ? START : MIDDLE; + } + break; + case GOT_l: + if (c == 'f') + state = GOT_lf; + else { + input_entry += ".l"; + input_entry += c; + state = c == '\n' ? START : MIDDLE; + } + break; + case GOT_lf: + if (c == ' ' || c == '\n' || compatible_flag) { + string args; + input_entry += ".lf"; + while (c != EOF) { + args += c; + if (c == '\n') + break; + c = in.get(); + } + args += '\0'; + interpret_lf_args(args.contents()); + // remove the '\0' + args.set_length(args.length() - 1); + input_entry += args; + state = START; + } + else { + input_entry += ".lf"; + input_entry += c; + state = MIDDLE; + } + break; + case GOT_RIGHT_BRACE: + if ((opt->flags & table::NOSPACES)) { + while (c == ' ') + c = in.get(); + if (c == EOF) + break; + } + if (c == '\n' || c == tab_char) + state = END; + else { + input_entry += 'T'; + input_entry += '}'; + input_entry += c; + state = MIDDLE; + } + break; + case MIDDLE: + if (c == '\n') + state = START; + input_entry += c; + break; + case END: + default: + assert(0 == "invalid `state` in switch"); + } + } + if (c == EOF) { + error("end of data in middle of text block"); + give_up = true; + break; + } + } + if (col >= ncolumns) { + if (!input_entry.empty()) { + if (input_entry.length() >= 2 + && input_entry[0] == '\\' + && input_entry[1] == '"') + seen_row_comment = true; + else if (!seen_row_comment) { + if (c == '\n') + in.unget(c); + input_entry += '\0'; + error("excess table entry '%1' discarded", + input_entry.contents()); + if (c == '\n') + (void)in.get(); + } + } + } + else + tbl->add_entry(current_row, col, input_entry, + &line_format[col], current_filename, ln); + col++; + if (c == '\n') + break; + input_entry = ""; + } + else + input_entry += c; + c = in.get(); + if (c == EOF) + break; + } + if (give_up) + break; + input_entry = ""; + for (; col < ncolumns; col++) + tbl->add_entry(current_row, col, input_entry, &line_format[col], + current_filename, current_lineno - 1); + tbl->add_vlines(current_row, f->vline[format_index]); + current_row++; + format_index++; + } + break; + case TROFF_INPUT_LINE: + { + string line; + int ln = current_lineno; + for (;;) { + line += c; + if (c == '\n') + break; + c = in.get(); + if (c == EOF) { + break; + } + } + tbl->add_text_line(current_row, line, current_filename, ln); + if (line.length() >= 4 + && line[0] == '.' && line[1] == 'T' && line[2] == '&') { + format *newf = process_format(in, opt, f); + if (newf == 0) + give_up = true; + else + f = newf; + } + if (line.length() >= 3 + && line[0] == '.' && line[1] == 'l' && line[2] == 'f') { + line += '\0'; + interpret_lf_args(line.contents() + 3); + } + } + break; + case SINGLE_HLINE: + tbl->add_single_hline(current_row); + break; + case DOUBLE_HLINE: + tbl->add_double_hline(current_row); + break; + default: + assert(0 == "invalid `type` in switch"); + } + if (give_up) + break; + } + if (!give_up && current_row == 0) { + error("no real data"); + give_up = true; + } + if (give_up) { + delete tbl; + return 0; + } + // Do this here rather than at the beginning in case continued formats + // change it. + int i; + for (i = 0; i < ncolumns - 1; i++) + if (f->separation[i] >= 0) + tbl->set_column_separation(i, f->separation[i]); + for (i = 0; i < ncolumns; i++) + if (!f->width[i].empty()) + tbl->set_minimum_width(i, f->width[i]); + for (i = 0; i < ncolumns; i++) + if (f->equal[i]) + tbl->set_equal_column(i); + for (i = 0; i < ncolumns; i++) + if (f->expand[i]) + tbl->set_expand_column(i); + return tbl; +} + +void process_table(table_input &in) +{ + options *opt = 0 /* nullptr */; + format *fmt = 0 /* nullptr */; + table *tbl = 0 /* nullptr */; + if ((opt = process_options(in)) != 0 /* nullptr */ + && (fmt = process_format(in, opt)) != 0 /* nullptr */ + && (tbl = process_data(in, fmt, opt)) != 0 /* nullptr */) { + tbl->print(); + delete tbl; + } + else { + error("giving up on this table region"); + while (in.get() != EOF) + ; + } + delete opt; + delete fmt; + if (!in.ended()) + error("premature end of file"); +} + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-C] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); +} + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int opt; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((opt = getopt_long(argc, argv, "vC", long_options, NULL)) + != EOF) + switch (opt) { + case 'C': + compatible_flag = 1; + break; + case 'v': + { + printf("GNU tbl (groff) version %s\n", Version_string); + exit(EXIT_SUCCESS); + break; + } + case CHAR_MAX + 1: // --help + usage(stdout); + exit(EXIT_SUCCESS); + break; + case '?': + usage(stderr); + exit(EXIT_FAILURE); + break; + default: + assert(0 == "unhandled getopt_long return value"); + } + printf(".if !\\n(.g .ab GNU tbl requires groff extensions; aborting\n" + ".do if !dTS .ds TS\n" + ".do if !dT& .ds T&\n" + ".do if !dTE .ds TE\n"); + if (argc > optind) { + for (int i = optind; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '\0') { + current_filename = "-"; + current_lineno = 1; + printf(".lf 1 -\n"); + process_input_file(stdin); + } + else { + errno = 0; + FILE *fp = fopen(argv[i], "r"); + if (fp == 0) { + current_filename = 0 /* nullptr */; + fatal("can't open '%1': %2", argv[i], strerror(errno)); + } + else { + current_lineno = 1; + string fn(argv[i]); + fn += '\0'; + normalize_for_lf(fn); + current_filename = fn.contents(); + printf(".lf 1 %s\n", current_filename); + process_input_file(fp); + } + } + } + else { + current_filename = "-"; + current_lineno = 1; + printf(".lf 1 -\n"); + process_input_file(stdin); + } + if (ferror(stdout) || fflush(stdout) < 0) + fatal("output error"); + return 0; +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/tbl/table.cpp b/src/preproc/tbl/table.cpp new file mode 100644 index 0000000..c391c90 --- /dev/null +++ b/src/preproc/tbl/table.cpp @@ -0,0 +1,3161 @@ +/* Copyright (C) 1989-2023 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +groff is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or +(at your option) any later version. + +groff is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "table.h" + +#define BAR_HEIGHT ".25m" +#define DOUBLE_LINE_SEP "2p" +#define HALF_DOUBLE_LINE_SEP "1p" +#define LINE_SEP "2p" +#define BODY_DEPTH ".25m" + +const int DEFAULT_COLUMN_SEPARATION = 3; + +#define DELIMITER_CHAR "\\[tbl]" +#define SEPARATION_FACTOR_REG PREFIX "sep" +#define LEFTOVER_FACTOR_REG PREFIX "leftover" +#define BOTTOM_REG PREFIX "bot" +#define RESET_MACRO_NAME PREFIX "init" +#define LINESIZE_REG PREFIX "lps" +#define TOP_REG PREFIX "top" +#define CURRENT_ROW_REG PREFIX "crow" +#define LAST_PASSED_ROW_REG PREFIX "passed" +#define TRANSPARENT_STRING_NAME PREFIX "trans" +#define QUOTE_STRING_NAME PREFIX "quote" +#define SECTION_DIVERSION_NAME PREFIX "section" +#define SECTION_DIVERSION_FLAG_REG PREFIX "sflag" +#define SAVED_VERTICAL_POS_REG PREFIX "vert" +#define NEED_BOTTOM_RULE_REG PREFIX "brule" +#define USE_KEEPS_REG PREFIX "usekeeps" +#define KEEP_MACRO_NAME PREFIX "keep" +#define RELEASE_MACRO_NAME PREFIX "release" +#define SAVED_FONT_REG PREFIX "fnt" +#define SAVED_SIZE_REG PREFIX "sz" +#define SAVED_FILL_REG PREFIX "fll" +#define SAVED_INDENT_REG PREFIX "ind" +#define SAVED_CENTER_REG PREFIX "cent" +#define SAVED_TABS_NAME PREFIX "tabs" +#define SAVED_INTER_WORD_SPACE_SIZE PREFIX "ss" +#define SAVED_INTER_SENTENCE_SPACE_SIZE PREFIX "sss" +#define TABLE_DIVERSION_NAME PREFIX "table" +#define TABLE_DIVERSION_FLAG_REG PREFIX "tflag" +#define TABLE_KEEP_MACRO_NAME PREFIX "tkeep" +#define TABLE_RELEASE_MACRO_NAME PREFIX "trelease" +#define NEEDED_REG PREFIX "needed" +#define REPEATED_MARK_MACRO PREFIX "rmk" +#define REPEATED_VPT_MACRO PREFIX "rvpt" +#define SUPPRESS_BOTTOM_REG PREFIX "supbot" +#define SAVED_DN_REG PREFIX "dn" +#define SAVED_HYPHENATION_MODE_REG PREFIX "hyphmode" +#define SAVED_HYPHENATION_LANG_NAME PREFIX "hyphlang" +#define SAVED_HYPHENATION_MAX_LINES_REG PREFIX "hyphmaxlines" +#define SAVED_HYPHENATION_MARGIN_REG PREFIX "hyphmargin" +#define SAVED_HYPHENATION_SPACE_REG PREFIX "hyphspace" +#define SAVED_NUMBERING_LINENO PREFIX "linenumber" +#define SAVED_NUMBERING_SUPPRESSION_COUNT PREFIX "linenumbersuppresscnt" +#define STARTING_PAGE_REG PREFIX "starting-page" +#define IS_BOXED_REG PREFIX "is-boxed" +#define PREVIOUS_PAGE_REG PREFIX "previous-page" + +// this must be one character +#define COMPATIBLE_REG PREFIX "c" + +// for use with `ig` requests embedded inside macro definitions +#define NOP_NAME PREFIX "nop" + +#define AVAILABLE_WIDTH_REG PREFIX "available-width" +#define EXPAND_REG PREFIX "expansion-amount" + +#define LEADER_REG PREFIX LEADER + +#define BLOCK_WIDTH_PREFIX PREFIX "tbw" +#define BLOCK_DIVERSION_PREFIX PREFIX "tbd" +#define BLOCK_HEIGHT_PREFIX PREFIX "tbh" +#define SPAN_WIDTH_PREFIX PREFIX "w" +#define SPAN_LEFT_NUMERIC_WIDTH_PREFIX PREFIX "lnw" +#define SPAN_RIGHT_NUMERIC_WIDTH_PREFIX PREFIX "rnw" +#define SPAN_ALPHABETIC_WIDTH_PREFIX PREFIX "aw" +#define COLUMN_SEPARATION_PREFIX PREFIX "cs" +#define ROW_START_PREFIX PREFIX "rs" +#define COLUMN_START_PREFIX PREFIX "cl" +#define COLUMN_END_PREFIX PREFIX "ce" +#define COLUMN_DIVIDE_PREFIX PREFIX "cd" +#define ROW_TOP_PREFIX PREFIX "rt" + +string block_width_reg(int, int); +string block_diversion_name(int, int); +string block_height_reg(int, int); +string span_width_reg(int, int); +string span_left_numeric_width_reg(int, int); +string span_right_numeric_width_reg(int, int); +string span_alphabetic_width_reg(int, int); +string column_separation_reg(int); +string row_start_reg(int); +string column_start_reg(int); +string column_end_reg(int); +string column_divide_reg(int); +string row_top_reg(int); + +void set_inline_modifier(const entry_modifier *); +void restore_inline_modifier(const entry_modifier *); +void set_modifier(const entry_modifier *); +int find_decimal_point(const char *, char, const char *); + +string an_empty_string; +int location_force_filename = 0; + +void printfs(const char *, + const string &arg1 = an_empty_string, + const string &arg2 = an_empty_string, + const string &arg3 = an_empty_string, + const string &arg4 = an_empty_string, + const string &arg5 = an_empty_string); + +void prints(const string &); + +inline void prints(char c) +{ + putchar(c); +} + +inline void prints(const char *s) +{ + fputs(s, stdout); +} + +void prints(const string &s) +{ + if (!s.empty()) + fwrite(s.contents(), 1, s.length(), stdout); +} + +struct horizontal_span { + horizontal_span *next; + int start_col; + int end_col; + horizontal_span(int, int, horizontal_span *); +}; + +class single_line_entry; +class double_line_entry; +class simple_entry; + +class table_entry { +friend class table; + table_entry *next; + int input_lineno; + const char *input_filename; +protected: + int start_row; + int end_row; + int start_col; + int end_col; + const table *parent; + const entry_modifier *mod; +public: + void set_location(); + table_entry(const table *, const entry_modifier *); + virtual ~table_entry(); + virtual int divert(int, const string *, int *, int); + virtual void do_width(); + virtual void do_depth(); + virtual void print() = 0; + virtual void position_vertically() = 0; + virtual single_line_entry *to_single_line_entry(); + virtual double_line_entry *to_double_line_entry(); + virtual simple_entry *to_simple_entry(); + virtual int line_type(); + virtual void note_double_vrule_on_right(int); + virtual void note_double_vrule_on_left(int); +}; + +class simple_entry : public table_entry { +public: + simple_entry(const table *, const entry_modifier *); + void print(); + void position_vertically(); + simple_entry *to_simple_entry(); + virtual void add_tab(); + virtual void simple_print(int); +}; + +class empty_entry : public simple_entry { +public: + empty_entry(const table *, const entry_modifier *); + int line_type(); +}; + +class text_entry : public simple_entry { +protected: + char *contents; + void print_contents(); +public: + text_entry(const table *, const entry_modifier *, char *); + ~text_entry(); +}; + +void text_entry::print_contents() +{ + set_inline_modifier(mod); + prints(contents); + restore_inline_modifier(mod); +} + +class repeated_char_entry : public text_entry { +public: + repeated_char_entry(const table *, const entry_modifier *, char *); + void simple_print(int); +}; + +class simple_text_entry : public text_entry { +public: + simple_text_entry(const table *, const entry_modifier *, char *); + void do_width(); +}; + +class left_text_entry : public simple_text_entry { +public: + left_text_entry(const table *, const entry_modifier *, char *); + void simple_print(int); + void add_tab(); +}; + +class right_text_entry : public simple_text_entry { +public: + right_text_entry(const table *, const entry_modifier *, char *); + void simple_print(int); + void add_tab(); +}; + +class center_text_entry : public simple_text_entry { +public: + center_text_entry(const table *, const entry_modifier *, char *); + void simple_print(int); + void add_tab(); +}; + +class numeric_text_entry : public text_entry { + int dot_pos; +public: + numeric_text_entry(const table *, const entry_modifier *, char *, int); + void do_width(); + void simple_print(int); +}; + +class alphabetic_text_entry : public text_entry { +public: + alphabetic_text_entry(const table *, const entry_modifier *, char *); + void do_width(); + void simple_print(int); + void add_tab(); +}; + +class line_entry : public simple_entry { +protected: + char double_vrule_on_right; + char double_vrule_on_left; +public: + line_entry(const table *, const entry_modifier *); + void note_double_vrule_on_right(int); + void note_double_vrule_on_left(int); + void simple_print(int) = 0; +}; + +class single_line_entry : public line_entry { +public: + single_line_entry(const table *, const entry_modifier *); + void simple_print(int); + single_line_entry *to_single_line_entry(); + int line_type(); +}; + +class double_line_entry : public line_entry { +public: + double_line_entry(const table *, const entry_modifier *); + void simple_print(int); + double_line_entry *to_double_line_entry(); + int line_type(); +}; + +class short_line_entry : public simple_entry { +public: + short_line_entry(const table *, const entry_modifier *); + void simple_print(int); + int line_type(); +}; + +class short_double_line_entry : public simple_entry { +public: + short_double_line_entry(const table *, const entry_modifier *); + void simple_print(int); + int line_type(); +}; + +class block_entry : public table_entry { + char *contents; +protected: + void do_divert(int, int, const string *, int *, int); +public: + block_entry(const table *, const entry_modifier *, char *); + ~block_entry(); + int divert(int, const string *, int *, int); + void do_depth(); + void position_vertically(); + void print() = 0; +}; + +class left_block_entry : public block_entry { +public: + left_block_entry(const table *, const entry_modifier *, char *); + void print(); +}; + +class right_block_entry : public block_entry { +public: + right_block_entry(const table *, const entry_modifier *, char *); + void print(); +}; + +class center_block_entry : public block_entry { +public: + center_block_entry(const table *, const entry_modifier *, char *); + void print(); +}; + +class alphabetic_block_entry : public block_entry { +public: + alphabetic_block_entry(const table *, const entry_modifier *, char *); + void print(); + int divert(int, const string *, int *, int); +}; + +table_entry::table_entry(const table *p, const entry_modifier *m) +: next(0), input_lineno(-1), input_filename(0), + start_row(-1), end_row(-1), start_col(-1), end_col(-1), parent(p), mod(m) +{ +} + +table_entry::~table_entry() +{ +} + +int table_entry::divert(int, const string *, int *, int) +{ + return 0; +} + +void table_entry::do_width() +{ +} + +single_line_entry *table_entry::to_single_line_entry() +{ + return 0; +} + +double_line_entry *table_entry::to_double_line_entry() +{ + return 0; +} + +simple_entry *table_entry::to_simple_entry() +{ + return 0; +} + +void table_entry::do_depth() +{ +} + +void table_entry::set_location() +{ + set_troff_location(input_filename, input_lineno); +} + +int table_entry::line_type() +{ + return -1; +} + +void table_entry::note_double_vrule_on_right(int) +{ +} + +void table_entry::note_double_vrule_on_left(int) +{ +} + +simple_entry::simple_entry(const table *p, const entry_modifier *m) +: table_entry(p, m) +{ +} + +void simple_entry::add_tab() +{ + // do nothing +} + +void simple_entry::simple_print(int) +{ + // do nothing +} + +void simple_entry::position_vertically() +{ + if (start_row != end_row) + switch (mod->vertical_alignment) { + case entry_modifier::TOP: + printfs(".sp |\\n[%1]u\n", row_start_reg(start_row)); + break; + case entry_modifier::CENTER: + // Perform the motion in two stages so that the center is rounded + // vertically upwards even if net vertical motion is upwards. + printfs(".sp |\\n[%1]u\n", row_start_reg(start_row)); + printfs(".sp \\n[" BOTTOM_REG "]u-\\n[%1]u-1v/2u\n", + row_start_reg(start_row)); + break; + case entry_modifier::BOTTOM: + printfs(".sp |\\n[%1]u+\\n[" BOTTOM_REG "]u-\\n[%1]u-1v\n", + row_start_reg(start_row)); + break; + default: + assert(0 == "simple entry vertical position modifier not TOP," + " CENTER, or BOTTOM"); + } +} + +void simple_entry::print() +{ + prints(".ta"); + add_tab(); + prints('\n'); + set_location(); + prints("\\&"); + simple_print(0); + prints('\n'); +} + +simple_entry *simple_entry::to_simple_entry() +{ + return this; +} + +empty_entry::empty_entry(const table *p, const entry_modifier *m) +: simple_entry(p, m) +{ +} + +int empty_entry::line_type() +{ + return 0; +} + +text_entry::text_entry(const table *p, const entry_modifier *m, char *s) +: simple_entry(p, m), contents(s) +{ +} + +text_entry::~text_entry() +{ + free(contents); +} + +repeated_char_entry::repeated_char_entry(const table *p, + const entry_modifier *m, char *s) +: text_entry(p, m, s) +{ +} + +void repeated_char_entry::simple_print(int) +{ + printfs("\\h'|\\n[%1]u'", column_start_reg(start_col)); + set_inline_modifier(mod); + printfs("\\l" DELIMITER_CHAR "\\n[%1]u\\&", + span_width_reg(start_col, end_col)); + prints(contents); + prints(DELIMITER_CHAR); + restore_inline_modifier(mod); +} + +simple_text_entry::simple_text_entry(const table *p, + const entry_modifier *m, char *s) +: text_entry(p, m, s) +{ +} + +void simple_text_entry::do_width() +{ + set_location(); + printfs(".nr %1 \\n[%1]>?\\w" DELIMITER_CHAR, + span_width_reg(start_col, end_col)); + print_contents(); + prints(DELIMITER_CHAR "\n"); +} + +left_text_entry::left_text_entry(const table *p, + const entry_modifier *m, char *s) +: simple_text_entry(p, m, s) +{ +} + +void left_text_entry::simple_print(int) +{ + printfs("\\h'|\\n[%1]u'", column_start_reg(start_col)); + print_contents(); +} + +// The only point of this is to make '\a' "work" as in Unix tbl. Grrr. + +void left_text_entry::add_tab() +{ + printfs(" \\n[%1]u", column_end_reg(end_col)); +} + +right_text_entry::right_text_entry(const table *p, + const entry_modifier *m, char *s) +: simple_text_entry(p, m, s) +{ +} + +void right_text_entry::simple_print(int) +{ + printfs("\\h'|\\n[%1]u'", column_start_reg(start_col)); + prints("\002\003"); + print_contents(); + prints("\002"); +} + +void right_text_entry::add_tab() +{ + printfs(" \\n[%1]u", column_end_reg(end_col)); +} + +center_text_entry::center_text_entry(const table *p, + const entry_modifier *m, char *s) +: simple_text_entry(p, m, s) +{ +} + +void center_text_entry::simple_print(int) +{ + printfs("\\h'|\\n[%1]u'", column_start_reg(start_col)); + prints("\002\003"); + print_contents(); + prints("\003\002"); +} + +void center_text_entry::add_tab() +{ + printfs(" \\n[%1]u", column_end_reg(end_col)); +} + +numeric_text_entry::numeric_text_entry(const table *p, + const entry_modifier *m, + char *s, int pos) +: text_entry(p, m, s), dot_pos(pos) +{ +} + +void numeric_text_entry::do_width() +{ + if (dot_pos != 0) { + set_location(); + printfs(".nr %1 0\\w" DELIMITER_CHAR, + block_width_reg(start_row, start_col)); + set_inline_modifier(mod); + for (int i = 0; i < dot_pos; i++) + prints(contents[i]); + restore_inline_modifier(mod); + prints(DELIMITER_CHAR "\n"); + printfs(".nr %1 \\n[%1]>?\\n[%2]\n", + span_left_numeric_width_reg(start_col, end_col), + block_width_reg(start_row, start_col)); + } + else + printfs(".nr %1 0\n", block_width_reg(start_row, start_col)); + if (contents[dot_pos] != '\0') { + set_location(); + printfs(".nr %1 \\n[%1]>?\\w" DELIMITER_CHAR, + span_right_numeric_width_reg(start_col, end_col)); + set_inline_modifier(mod); + prints(contents + dot_pos); + restore_inline_modifier(mod); + prints(DELIMITER_CHAR "\n"); + } +} + +void numeric_text_entry::simple_print(int) +{ + printfs("\\h'|(\\n[%1]u-\\n[%2]u-\\n[%3]u/2u+\\n[%2]u+\\n[%4]u-\\n[%5]u)'", + span_width_reg(start_col, end_col), + span_left_numeric_width_reg(start_col, end_col), + span_right_numeric_width_reg(start_col, end_col), + column_start_reg(start_col), + block_width_reg(start_row, start_col)); + print_contents(); +} + +alphabetic_text_entry::alphabetic_text_entry(const table *p, + const entry_modifier *m, + char *s) +: text_entry(p, m, s) +{ +} + +void alphabetic_text_entry::do_width() +{ + set_location(); + printfs(".nr %1 \\n[%1]>?\\w" DELIMITER_CHAR, + span_alphabetic_width_reg(start_col, end_col)); + print_contents(); + prints(DELIMITER_CHAR "\n"); +} + +void alphabetic_text_entry::simple_print(int) +{ + printfs("\\h'|\\n[%1]u'", column_start_reg(start_col)); + printfs("\\h'\\n[%1]u-\\n[%2]u/2u'", + span_width_reg(start_col, end_col), + span_alphabetic_width_reg(start_col, end_col)); + print_contents(); +} + +// The only point of this is to make '\a' "work" as in Unix tbl. Grrr. + +void alphabetic_text_entry::add_tab() +{ + printfs(" \\n[%1]u", column_end_reg(end_col)); +} + +block_entry::block_entry(const table *p, const entry_modifier *m, char *s) +: table_entry(p, m), contents(s) +{ +} + +block_entry::~block_entry() +{ + delete[] contents; +} + +void block_entry::position_vertically() +{ + if (start_row != end_row) + switch(mod->vertical_alignment) { + case entry_modifier::TOP: + printfs(".sp |\\n[%1]u\n", row_start_reg(start_row)); + break; + case entry_modifier::CENTER: + // Perform the motion in two stages so that the center is rounded + // vertically upwards even if net vertical motion is upwards. + printfs(".sp |\\n[%1]u\n", row_start_reg(start_row)); + printfs(".sp \\n[" BOTTOM_REG "]u-\\n[%1]u-\\n[%2]u/2u\n", + row_start_reg(start_row), + block_height_reg(start_row, start_col)); + break; + case entry_modifier::BOTTOM: + printfs(".sp |\\n[%1]u+\\n[" BOTTOM_REG "]u-\\n[%1]u-\\n[%2]u\n", + row_start_reg(start_row), + block_height_reg(start_row, start_col)); + break; + default: + assert(0 == "block entry vertical position modifier not TOP," + " CENTER, or BOTTOM"); + } + if (mod->stagger) + prints(".sp -.5v\n"); +} + +int block_entry::divert(int ncols, const string *mw, int *sep, int do_expand) +{ + do_divert(0, ncols, mw, sep, do_expand); + return 1; +} + +void block_entry::do_divert(int alphabetic, int ncols, const string *mw, + int *sep, int do_expand) +{ + int i; + for (i = start_col; i <= end_col; i++) + if (parent->expand[i]) + break; + if (i > end_col) { + if (do_expand) + return; + } + else { + if (!do_expand) + return; + } + printfs(".di %1\n", block_diversion_name(start_row, start_col)); + prints(".if \\n[" SAVED_FILL_REG "] .fi\n" + ".in 0\n"); + prints(".ll "); + for (i = start_col; i <= end_col; i++) + if (mw[i].empty() && !parent->expand[i]) + break; + if (i > end_col) { + // Every column spanned by this entry has a minimum width. + for (int j = start_col; j <= end_col; j++) { + if (j > start_col) { + if (sep) + printfs("+%1n", as_string(sep[j - 1])); + prints('+'); + } + if (parent->expand[j]) + prints("\\n[" EXPAND_REG "]u"); + else + printfs("(n;%1)", mw[j]); + } + printfs(">?\\n[%1]u", span_width_reg(start_col, end_col)); + } + else + // Assign each column with a block entry 1/(n+1) of the line + // width, where n is the column count. + printfs("(u;\\n[%1]>?(\\n[.l]*%2/%3))", + span_width_reg(start_col, end_col), + as_string(end_col - start_col + 1), + as_string(ncols + 1)); + if (alphabetic) + prints("-2n"); + prints("\n"); + prints(".ss \\n[" SAVED_INTER_WORD_SPACE_SIZE "]" + " \\n[" SAVED_INTER_SENTENCE_SPACE_SIZE "]\n"); + prints(".cp \\n(" COMPATIBLE_REG "\n"); + set_modifier(mod); + set_location(); + prints(contents); + prints(".br\n.di\n.cp 0\n"); + if (!mod->zero_width) { + if (alphabetic) { + printfs(".nr %1 \\n[%1]>?(\\n[dl]+2n)\n", + span_width_reg(start_col, end_col)); + printfs(".nr %1 \\n[%1]>?\\n[dl]\n", + span_alphabetic_width_reg(start_col, end_col)); + } + else + printfs(".nr %1 \\n[%1]>?\\n[dl]\n", + span_width_reg(start_col, end_col)); + } + printfs(".nr %1 \\n[dn]\n", block_height_reg(start_row, start_col)); + printfs(".nr %1 \\n[dl]\n", block_width_reg(start_row, start_col)); + prints("." RESET_MACRO_NAME "\n" + ".in \\n[" SAVED_INDENT_REG "]u\n" + ".nf\n"); + // the block might have contained .lf commands + location_force_filename = 1; +} + +void block_entry::do_depth() +{ + printfs(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?(\\n[%1]+\\n[%2])\n", + row_start_reg(start_row), + block_height_reg(start_row, start_col)); +} + +left_block_entry::left_block_entry(const table *p, + const entry_modifier *m, char *s) +: block_entry(p, m, s) +{ +} + +void left_block_entry::print() +{ + printfs(".in +\\n[%1]u\n", column_start_reg(start_col)); + printfs(".%1\n", block_diversion_name(start_row, start_col)); + prints(".in\n"); +} + +right_block_entry::right_block_entry(const table *p, + const entry_modifier *m, char *s) +: block_entry(p, m, s) +{ +} + +void right_block_entry::print() +{ + printfs(".in +\\n[%1]u+\\n[%2]u-\\n[%3]u\n", + column_start_reg(start_col), + span_width_reg(start_col, end_col), + block_width_reg(start_row, start_col)); + printfs(".%1\n", block_diversion_name(start_row, start_col)); + prints(".in\n"); +} + +center_block_entry::center_block_entry(const table *p, + const entry_modifier *m, char *s) +: block_entry(p, m, s) +{ +} + +void center_block_entry::print() +{ + printfs(".in +\\n[%1]u+(\\n[%2]u-\\n[%3]u/2u)\n", + column_start_reg(start_col), + span_width_reg(start_col, end_col), + block_width_reg(start_row, start_col)); + printfs(".%1\n", block_diversion_name(start_row, start_col)); + prints(".in\n"); +} + +alphabetic_block_entry::alphabetic_block_entry(const table *p, + const entry_modifier *m, + char *s) +: block_entry(p, m, s) +{ +} + +int alphabetic_block_entry::divert(int ncols, const string *mw, int *sep, + int do_expand) +{ + do_divert(1, ncols, mw, sep, do_expand); + return 1; +} + +void alphabetic_block_entry::print() +{ + printfs(".in +\\n[%1]u+(\\n[%2]u-\\n[%3]u/2u)\n", + column_start_reg(start_col), + span_width_reg(start_col, end_col), + span_alphabetic_width_reg(start_col, end_col)); + printfs(".%1\n", block_diversion_name(start_row, start_col)); + prints(".in\n"); +} + +line_entry::line_entry(const table *p, const entry_modifier *m) +: simple_entry(p, m), double_vrule_on_right(0), double_vrule_on_left(0) +{ +} + +void line_entry::note_double_vrule_on_right(int is_corner) +{ + double_vrule_on_right = is_corner ? 1 : 2; +} + +void line_entry::note_double_vrule_on_left(int is_corner) +{ + double_vrule_on_left = is_corner ? 1 : 2; +} + +single_line_entry::single_line_entry(const table *p, const entry_modifier *m) +: line_entry(p, m) +{ +} + +int single_line_entry::line_type() +{ + return 1; +} + +void single_line_entry::simple_print(int dont_move) +{ + printfs("\\h'|\\n[%1]u", + column_divide_reg(start_col)); + if (double_vrule_on_left) { + prints(double_vrule_on_left == 1 ? "-" : "+"); + prints(HALF_DOUBLE_LINE_SEP); + } + prints("'"); + if (!dont_move) + prints("\\v'-" BAR_HEIGHT "'"); + printfs("\\s[\\n[" LINESIZE_REG "]]" "\\D'l |\\n[%1]u", + column_divide_reg(end_col+1)); + if (double_vrule_on_right) { + prints(double_vrule_on_left == 1 ? "+" : "-"); + prints(HALF_DOUBLE_LINE_SEP); + } + prints("0'\\s0"); + if (!dont_move) + prints("\\v'" BAR_HEIGHT "'"); +} + +single_line_entry *single_line_entry::to_single_line_entry() +{ + return this; +} + +double_line_entry::double_line_entry(const table *p, + const entry_modifier *m) +: line_entry(p, m) +{ +} + +int double_line_entry::line_type() +{ + return 2; +} + +void double_line_entry::simple_print(int dont_move) +{ + if (!dont_move) + prints("\\v'-" BAR_HEIGHT "'"); + printfs("\\h'|\\n[%1]u", + column_divide_reg(start_col)); + if (double_vrule_on_left) { + prints(double_vrule_on_left == 1 ? "-" : "+"); + prints(HALF_DOUBLE_LINE_SEP); + } + prints("'"); + printfs("\\v'-" HALF_DOUBLE_LINE_SEP "'" + "\\s[\\n[" LINESIZE_REG "]]" + "\\D'l |\\n[%1]u", + column_divide_reg(end_col+1)); + if (double_vrule_on_right) + prints("-" HALF_DOUBLE_LINE_SEP); + prints(" 0'"); + printfs("\\v'" DOUBLE_LINE_SEP "'" + "\\D'l |\\n[%1]u", + column_divide_reg(start_col)); + if (double_vrule_on_right) { + prints(double_vrule_on_left == 1 ? "+" : "-"); + prints(HALF_DOUBLE_LINE_SEP); + } + prints(" 0'"); + prints("\\s0" + "\\v'-" HALF_DOUBLE_LINE_SEP "'"); + if (!dont_move) + prints("\\v'" BAR_HEIGHT "'"); +} + +double_line_entry *double_line_entry::to_double_line_entry() +{ + return this; +} + +short_line_entry::short_line_entry(const table *p, const entry_modifier *m) +: simple_entry(p, m) +{ +} + +int short_line_entry::line_type() +{ + return 1; +} + +void short_line_entry::simple_print(int dont_move) +{ + if (mod->stagger) + prints("\\v'-.5v'"); + if (!dont_move) + prints("\\v'-" BAR_HEIGHT "'"); + printfs("\\h'|\\n[%1]u'", column_start_reg(start_col)); + printfs("\\s[\\n[" LINESIZE_REG "]]" + "\\D'l \\n[%1]u 0'" + "\\s0", + span_width_reg(start_col, end_col)); + if (!dont_move) + prints("\\v'" BAR_HEIGHT "'"); + if (mod->stagger) + prints("\\v'.5v'"); +} + +short_double_line_entry::short_double_line_entry(const table *p, + const entry_modifier *m) +: simple_entry(p, m) +{ +} + +int short_double_line_entry::line_type() +{ + return 2; +} + +void short_double_line_entry::simple_print(int dont_move) +{ + if (mod->stagger) + prints("\\v'-.5v'"); + if (!dont_move) + prints("\\v'-" BAR_HEIGHT "'"); + printfs("\\h'|\\n[%2]u'" + "\\v'-" HALF_DOUBLE_LINE_SEP "'" + "\\s[\\n[" LINESIZE_REG "]]" + "\\D'l \\n[%1]u 0'" + "\\v'" DOUBLE_LINE_SEP "'" + "\\D'l |\\n[%2]u 0'" + "\\s0" + "\\v'-" HALF_DOUBLE_LINE_SEP "'", + span_width_reg(start_col, end_col), + column_start_reg(start_col)); + if (!dont_move) + prints("\\v'" BAR_HEIGHT "'"); + if (mod->stagger) + prints("\\v'.5v'"); +} + +void set_modifier(const entry_modifier *m) +{ + if (!m->font.empty()) + printfs(".ft %1\n", m->font); + if (m->point_size.val != 0) { + prints(".ps "); + if (m->point_size.inc > 0) + prints('+'); + else if (m->point_size.inc < 0) + prints('-'); + printfs("%1\n", as_string(m->point_size.val)); + } + if (m->vertical_spacing.val != 0) { + prints(".vs "); + if (m->vertical_spacing.inc > 0) + prints('+'); + else if (m->vertical_spacing.inc < 0) + prints('-'); + printfs("%1\n", as_string(m->vertical_spacing.val)); + } + if (!m->macro.empty()) + printfs(".%1\n", m->macro); +} + +void set_inline_modifier(const entry_modifier *m) +{ + if (!m->font.empty()) + printfs("\\f[%1]", m->font); + if (m->point_size.val != 0) { + prints("\\s["); + if (m->point_size.inc > 0) + prints('+'); + else if (m->point_size.inc < 0) + prints('-'); + printfs("%1]", as_string(m->point_size.val)); + } + if (m->stagger) + prints("\\v'-.5v'"); +} + +void restore_inline_modifier(const entry_modifier *m) +{ + if (!m->font.empty()) + prints("\\f[\\n[" SAVED_FONT_REG "]]"); + if (m->point_size.val != 0) + prints("\\s[\\n[" SAVED_SIZE_REG "]]"); + if (m->stagger) + prints("\\v'.5v'"); +} + +struct stuff { + stuff *next; + int row; // occurs before row 'row' + char printed; // has it been printed? + + stuff(int); + virtual void print(table *) = 0; + virtual ~stuff(); + virtual int is_single_line() { return 0; }; + virtual int is_double_line() { return 0; }; +}; + +stuff::stuff(int r) : next(0), row(r), printed(0) +{ +} + +stuff::~stuff() +{ +} + +struct text_stuff : public stuff { + string contents; + const char *filename; + int lineno; + + text_stuff(const string &, int, const char *, int); + ~text_stuff(); + void print(table *); +}; + +text_stuff::text_stuff(const string &s, int r, const char *fn, int ln) +: stuff(r), contents(s), filename(fn), lineno(ln) +{ +} + +text_stuff::~text_stuff() +{ +} + +void text_stuff::print(table *) +{ + printed = 1; + prints(".cp \\n(" COMPATIBLE_REG "\n"); + set_troff_location(filename, lineno); + prints(contents); + prints(".cp 0\n"); + location_force_filename = 1; // it might have been a .lf command +} + +struct single_hline_stuff : public stuff { + single_hline_stuff(int); + void print(table *); + int is_single_line(); +}; + +single_hline_stuff::single_hline_stuff(int r) : stuff(r) +{ +} + +void single_hline_stuff::print(table *tbl) +{ + printed = 1; + tbl->print_single_hline(row); +} + +int single_hline_stuff::is_single_line() +{ + return 1; +} + +struct double_hline_stuff : stuff { + double_hline_stuff(int); + void print(table *); + int is_double_line(); +}; + +double_hline_stuff::double_hline_stuff(int r) : stuff(r) +{ +} + +void double_hline_stuff::print(table *tbl) +{ + printed = 1; + tbl->print_double_hline(row); +} + +int double_hline_stuff::is_double_line() +{ + return 1; +} + +struct vertical_rule { + vertical_rule *next; + int start_row; + int end_row; + int col; + char is_double; + string top_adjust; + string bot_adjust; + + vertical_rule(int, int, int, int, vertical_rule *); + ~vertical_rule(); + void contribute_to_bottom_macro(table *); + void print(); +}; + +vertical_rule::vertical_rule(int sr, int er, int c, int dbl, + vertical_rule *p) +: next(p), start_row(sr), end_row(er), col(c), is_double(dbl) +{ +} + +vertical_rule::~vertical_rule() +{ +} + +void vertical_rule::contribute_to_bottom_macro(table *tbl) +{ + printfs(".if \\n[" CURRENT_ROW_REG "]>=%1", + as_string(start_row)); + if (end_row != tbl->get_nrows() - 1) + printfs("&(\\n[" CURRENT_ROW_REG "]<%1)", + as_string(end_row)); + prints(" \\{\\\n"); + printfs(". if %1<=\\n[" LAST_PASSED_ROW_REG "] .nr %2 \\n[#T]\n", + as_string(start_row), + row_top_reg(start_row)); + const char *offset_table[3]; + if (is_double) { + offset_table[0] = "-" HALF_DOUBLE_LINE_SEP; + offset_table[1] = "+" HALF_DOUBLE_LINE_SEP; + offset_table[2] = 0; + } + else { + offset_table[0] = ""; + offset_table[1] = 0; + } + for (const char **offsetp = offset_table; *offsetp; offsetp++) { + prints(". sp -1\n" + "\\v'" BODY_DEPTH); + if (!bot_adjust.empty()) + printfs("+%1", bot_adjust); + prints("'"); + printfs("\\h'\\n[%1]u%3'\\s[\\n[" LINESIZE_REG "]]\\D'l 0 |\\n[%2]u-1v", + column_divide_reg(col), + row_top_reg(start_row), + *offsetp); + if (!bot_adjust.empty()) + printfs("-(%1)", bot_adjust); + // don't perform the top adjustment if the top is actually #T + if (!top_adjust.empty()) + printfs("+((%1)*(%2>\\n[" LAST_PASSED_ROW_REG "]))", + top_adjust, + as_string(start_row)); + prints("'\\s0\n"); + } + prints(".\\}\n"); +} + +void vertical_rule::print() +{ + printfs("\\*[" TRANSPARENT_STRING_NAME "]" + ".if %1<=\\*[" QUOTE_STRING_NAME "]\\n[" LAST_PASSED_ROW_REG "] " + ".nr %2 \\*[" QUOTE_STRING_NAME "]\\n[#T]\n", + as_string(start_row), + row_top_reg(start_row)); + const char *offset_table[3]; + if (is_double) { + offset_table[0] = "-" HALF_DOUBLE_LINE_SEP; + offset_table[1] = "+" HALF_DOUBLE_LINE_SEP; + offset_table[2] = 0; + } + else { + offset_table[0] = ""; + offset_table[1] = 0; + } + for (const char **offsetp = offset_table; *offsetp; offsetp++) { + prints("\\*[" TRANSPARENT_STRING_NAME "].sp -1\n" + "\\*[" TRANSPARENT_STRING_NAME "]\\v'" BODY_DEPTH); + if (!bot_adjust.empty()) + printfs("+%1", bot_adjust); + prints("'"); + printfs("\\h'\\n[%1]u%3'" + "\\s[\\n[" LINESIZE_REG "]]" + "\\D'l 0 |\\*[" QUOTE_STRING_NAME "]\\n[%2]u-1v", + column_divide_reg(col), + row_top_reg(start_row), + *offsetp); + if (!bot_adjust.empty()) + printfs("-(%1)", bot_adjust); + // don't perform the top adjustment if the top is actually #T + if (!top_adjust.empty()) + printfs("+((%1)*(%2>\\*[" QUOTE_STRING_NAME "]\\n[" + LAST_PASSED_ROW_REG "]))", + top_adjust, + as_string(start_row)); + prints("'" + "\\s0\n"); + } +} + +table::table(int nc, unsigned f, int ls, char dpc) +: nrows(0), ncolumns(nc), linesize(ls), decimal_point_char(dpc), + vrule_list(0), stuff_list(0), span_list(0), + entry_list(0), entry_list_tailp(&entry_list), entry(0), + vline(0), row_is_all_lines(0), left_separation(0), + right_separation(0), total_separation(0), allocated_rows(0), flags(f) +{ + minimum_width = new string[ncolumns]; + column_separation = ncolumns > 1 ? new int[ncolumns - 1] : 0; + equal = new char[ncolumns]; + expand = new char[ncolumns]; + int i; + for (i = 0; i < ncolumns; i++) { + equal[i] = 0; + expand[i] = 0; + } + for (i = 0; i < ncolumns - 1; i++) + column_separation[i] = DEFAULT_COLUMN_SEPARATION; + delim[0] = delim[1] = '\0'; +} + +table::~table() +{ + for (int i = 0; i < nrows; i++) { + delete[] entry[i]; + delete[] vline[i]; + } + delete[] entry; + delete[] vline; + while (entry_list) { + table_entry *tem = entry_list; + entry_list = entry_list->next; + delete tem; + } + delete[] minimum_width; + delete[] column_separation; + delete[] equal; + delete[] expand; + while (stuff_list) { + stuff *tem = stuff_list; + stuff_list = stuff_list->next; + delete tem; + } + while (vrule_list) { + vertical_rule *tem = vrule_list; + vrule_list = vrule_list->next; + delete tem; + } + delete[] row_is_all_lines; + while (span_list) { + horizontal_span *tem = span_list; + span_list = span_list->next; + delete tem; + } +} + +void table::set_delim(char c1, char c2) +{ + delim[0] = c1; + delim[1] = c2; +} + +void table::set_minimum_width(int c, const string &w) +{ + assert(c >= 0 && c < ncolumns); + minimum_width[c] = w; +} + +void table::set_column_separation(int c, int n) +{ + assert(c >= 0 && c < ncolumns - 1); + column_separation[c] = n; +} + +void table::set_equal_column(int c) +{ + assert(c >= 0 && c < ncolumns); + equal[c] = 1; +} + +void table::set_expand_column(int c) +{ + assert(c >= 0 && c < ncolumns); + expand[c] = 1; +} + +void table::add_stuff(stuff *p) +{ + stuff **pp; + for (pp = &stuff_list; *pp; pp = &(*pp)->next) + ; + *pp = p; +} + +void table::add_text_line(int r, const string &s, const char *filename, + int lineno) +{ + add_stuff(new text_stuff(s, r, filename, lineno)); +} + +void table::add_single_hline(int r) +{ + add_stuff(new single_hline_stuff(r)); +} + +void table::add_double_hline(int r) +{ + add_stuff(new double_hline_stuff(r)); +} + +void table::allocate(int r) +{ + if (r >= nrows) { + typedef table_entry **PPtable_entry; // work around g++ 1.36.1 bug + if (r >= allocated_rows) { + if (allocated_rows == 0) { + allocated_rows = 16; + if (allocated_rows <= r) + allocated_rows = r + 1; + entry = new PPtable_entry[allocated_rows]; + vline = new char*[allocated_rows]; + } + else { + table_entry ***old_entry = entry; + int old_allocated_rows = allocated_rows; + allocated_rows *= 2; + if (allocated_rows <= r) + allocated_rows = r + 1; + entry = new PPtable_entry[allocated_rows]; + memcpy(entry, old_entry, sizeof(table_entry**)*old_allocated_rows); + delete[] old_entry; + char **old_vline = vline; + vline = new char*[allocated_rows]; + memcpy(vline, old_vline, sizeof(char*)*old_allocated_rows); + delete[] old_vline; + } + } + assert(allocated_rows > r); + while (nrows <= r) { + entry[nrows] = new table_entry*[ncolumns]; + int i; + for (i = 0; i < ncolumns; i++) + entry[nrows][i] = 0; + vline[nrows] = new char[ncolumns+1]; + for (i = 0; i < ncolumns+1; i++) + vline[nrows][i] = 0; + nrows++; + } + } +} + +void table::do_hspan(int r, int c) +{ + assert(r >= 0 && c >= 0 && r < nrows && c < ncolumns); + if (c == 0) { + error("first column cannot be horizontally spanned"); + return; + } + table_entry *e = entry[r][c]; + if (e) { + assert(e->start_row <= r && r <= e->end_row + && e->start_col <= c && c <= e->end_col + && e->end_row - e->start_row > 0 + && e->end_col - e->start_col > 0); + return; + } + e = entry[r][c-1]; + // e can be 0 if we had an empty entry or an error + if (e == 0) + return; + if (e->start_row != r) { + /* + l l + ^ s */ + error("impossible horizontal span at row %1, column %2", r + 1, + c + 1); + } + else { + e->end_col = c; + entry[r][c] = e; + } +} + +void table::do_vspan(int r, int c) +{ + assert(r >= 0 && c >= 0 && r < nrows && c < ncolumns); + if (0 == r) { + error("first row cannot be vertically spanned"); + return; + } + table_entry *e = entry[r][c]; + if (e) { + assert(e->start_row <= r); + assert(r <= e->end_row); + assert(e->start_col <= c); + assert(c <= e->end_col); + assert((e->end_row - e->start_row) > 0); + assert((e->end_col - e->start_col) > 0); + return; + } + e = entry[r-1][c]; + // e can be a null pointer if we had an empty entry or an error + if (0 == e) + return; + if (e->start_col != c) { + /* l s + l ^ */ + error("impossible vertical span at row %1, column %2", r + 1, + c + 1); + } + else { + for (int i = c; i <= e->end_col; i++) { + assert(entry[r][i] == 0); + entry[r][i] = e; + } + e->end_row = r; + } +} + +int find_decimal_point(const char *s, char decimal_point_char, + const char *delim) +{ + if (s == 0 || *s == '\0') + return -1; + const char *p; + int in_delim = 0; // is p within eqn delimiters? + // tbl recognises \& even within eqn delimiters; I don't + for (p = s; *p; p++) + if (in_delim) { + if (*p == delim[1]) + in_delim = 0; + } + else if (*p == delim[0]) + in_delim = 1; + else if (p[0] == '\\' && p[1] == '&') + return p - s; + int possible_pos = -1; + in_delim = 0; + for (p = s; *p; p++) + if (in_delim) { + if (*p == delim[1]) + in_delim = 0; + } + else if (*p == delim[0]) + in_delim = 1; + else if (p[0] == decimal_point_char && csdigit(p[1])) + possible_pos = p - s; + if (possible_pos >= 0) + return possible_pos; + in_delim = 0; + for (p = s; *p; p++) + if (in_delim) { + if (*p == delim[1]) + in_delim = 0; + } + else if (*p == delim[0]) + in_delim = 1; + else if (csdigit(*p)) + possible_pos = p + 1 - s; + return possible_pos; +} + +void table::add_entry(int r, int c, const string &str, + const entry_format *f, const char *fn, int ln) +{ + allocate(r); + table_entry *e = 0; + char *s = str.extract(); + if (str.search('\n') >= 0) { + bool was_changed = false; + for (int i = 0; s[i] != '\0'; i++) + if ((i > 0) && (s[(i - 1)] == '\\') && (s[i] == 'R')) { + s[i] = '&'; + was_changed = true; + } + if (was_changed) + error_with_file_and_line(fn, ln, "repeating a glyph with '\\R'" + " is not allowed in a text block"); + } + if (str == "\\_") { + e = new short_line_entry(this, f); + } + else if (str == "\\=") { + e = new short_double_line_entry(this, f); + } + else if (str == "_") { + single_line_entry *lefte; + if (c > 0 && entry[r][c-1] != 0 && + (lefte = entry[r][c-1]->to_single_line_entry()) != 0 + && lefte->start_row == r + && lefte->mod->stagger == f->stagger) { + lefte->end_col = c; + entry[r][c] = lefte; + } + else + e = new single_line_entry(this, f); + } + else if (str == "=") { + double_line_entry *lefte; + if (c > 0 && entry[r][c-1] != 0 && + (lefte = entry[r][c-1]->to_double_line_entry()) != 0 + && lefte->start_row == r + && lefte->mod->stagger == f->stagger) { + lefte->end_col = c; + entry[r][c] = lefte; + } + else + e = new double_line_entry(this, f); + } + else if (str == "\\^") { + if (r == 0) { + error("first row cannot contain a vertical span entry '\\^'"); + e = new empty_entry(this, f); + } + else + do_vspan(r, c); + } + else { + int is_block = str.search('\n') >= 0; + switch (f->type) { + case FORMAT_SPAN: + assert(str.empty()); + do_hspan(r, c); + break; + case FORMAT_LEFT: + if (!str.empty()) { + if (is_block) + e = new left_block_entry(this, f, s); + else + e = new left_text_entry(this, f, s); + } + else + e = new empty_entry(this, f); + break; + case FORMAT_CENTER: + if (!str.empty()) { + if (is_block) + e = new center_block_entry(this, f, s); + else + e = new center_text_entry(this, f, s); + } + else + e = new empty_entry(this, f); + break; + case FORMAT_RIGHT: + if (!str.empty()) { + if (is_block) + e = new right_block_entry(this, f, s); + else + e = new right_text_entry(this, f, s); + } + else + e = new empty_entry(this, f); + break; + case FORMAT_NUMERIC: + if (!str.empty()) { + if (is_block) { + error_with_file_and_line(fn, ln, "can't have numeric text block"); + e = new left_block_entry(this, f, s); + } + else { + int pos = find_decimal_point(s, decimal_point_char, delim); + if (pos < 0) + e = new center_text_entry(this, f, s); + else + e = new numeric_text_entry(this, f, s, pos); + } + } + else + e = new empty_entry(this, f); + break; + case FORMAT_ALPHABETIC: + if (!str.empty()) { + if (is_block) + e = new alphabetic_block_entry(this, f, s); + else + e = new alphabetic_text_entry(this, f, s); + } + else + e = new empty_entry(this, f); + break; + case FORMAT_VSPAN: + do_vspan(r, c); + break; + case FORMAT_HLINE: + if ((str.length() != 0) && (str != "\\&")) + error_with_file_and_line(fn, ln, + "ignoring non-empty data entry using" + " '_' column classifier"); + e = new single_line_entry(this, f); + break; + case FORMAT_DOUBLE_HLINE: + if ((str.length() != 0) && (str != "\\&")) + error_with_file_and_line(fn, ln, + "ignoring non-empty data entry using" + " '=' column classifier"); + e = new double_line_entry(this, f); + break; + default: + assert(0 == "table column format not in FORMAT_{SPAN,LEFT,CENTER," + "RIGHT,NUMERIC,ALPHABETIC,VSPAN,HLINE,DOUBLE_HLINE}"); + } + } + if (e) { + table_entry *preve = entry[r][c]; + if (preve) { + /* c s + ^ l */ + error_with_file_and_line(fn, ln, "row %1, column %2 already" + " spanned", + r + 1, c + 1); + delete e; + } + else { + e->input_lineno = ln; + e->input_filename = fn; + e->start_row = e->end_row = r; + e->start_col = e->end_col = c; + *entry_list_tailp = e; + entry_list_tailp = &e->next; + entry[r][c] = e; + } + } +} + +// add vertical lines for row r + +void table::add_vlines(int r, const char *v) +{ + allocate(r); + bool lwarned = false; + bool twarned = false; + for (int i = 0; i < ncolumns+1; i++) { + assert(v[i] < 3); + if (v[i] && (flags & (BOX | ALLBOX | DOUBLEBOX)) && (i == 0) + && (!lwarned)) { + error("ignoring vertical line at leading edge of boxed table"); + lwarned = true; + } + else if (v[i] && (flags & (BOX | ALLBOX | DOUBLEBOX)) + && (i == ncolumns) && (!twarned)) { + error("ignoring vertical line at trailing edge of boxed table"); + twarned = true; + } + else + vline[r][i] = v[i]; + } +} + +void table::check() +{ + table_entry *p = entry_list; + int i, j; + while (p) { + for (i = p->start_row; i <= p->end_row; i++) + for (j = p->start_col; j <= p->end_col; j++) + assert(entry[i][j] == p); + p = p->next; + } +} + +void table::print() +{ + location_force_filename = 1; + check(); + init_output(); + determine_row_type(); + compute_widths(); + if (!(flags & CENTER)) + prints(".if \\n[" SAVED_CENTER_REG "] \\{\\\n"); + prints(". in +(u;\\n[.l]-\\n[.i]-\\n[TW]/2>?-\\n[.i])\n" + ". nr " SAVED_INDENT_REG " \\n[.i]\n"); + if (!(flags & CENTER)) + prints(".\\}\n"); + build_vrule_list(); + define_bottom_macro(); + do_top(); + for (int i = 0; i < nrows; i++) + do_row(i); + do_bottom(); +} + +void table::determine_row_type() +{ + row_is_all_lines = new char[nrows]; + for (int i = 0; i < nrows; i++) { + bool had_single = false; + bool had_double = false; + bool had_non_line = false; + for (int c = 0; c < ncolumns; c++) { + table_entry *e = entry[i][c]; + if (e != 0) { + if (e->start_row == e->end_row) { + int t = e->line_type(); + switch (t) { + case -1: + had_non_line = true; + break; + case 0: + // empty + break; + case 1: + had_single = true; + break; + case 2: + had_double = true; + break; + default: + assert(0 == "table entry line type not in {-1, 0, 1, 2}"); + } + if (had_non_line) + break; + } + c = e->end_col; + } + } + if (had_non_line) + row_is_all_lines[i] = 0; + else if (had_double) + row_is_all_lines[i] = 2; + else if (had_single) + row_is_all_lines[i] = 1; + else + row_is_all_lines[i] = 0; + } +} + +int table::count_expand_columns() +{ + int count = 0; + for (int i = 0; i < ncolumns; i++) + if (expand[i]) + count++; + return count; +} + +void table::init_output() +{ + prints(".\\\" initialize output\n"); + prints(".nr " COMPATIBLE_REG " \\n(.C\n" + ".cp 0\n"); + if (linesize > 0) + printfs(".nr " LINESIZE_REG " %1\n", as_string(linesize)); + else + prints(".nr " LINESIZE_REG " \\n[.s]\n"); + if (!(flags & CENTER)) + prints(".nr " SAVED_CENTER_REG " \\n[.ce]\n"); + if (compatible_flag) + prints(".ds " LEADER_REG " \\a\n"); + if (!(flags & NOKEEP)) + prints(".if !r " USE_KEEPS_REG " .nr " USE_KEEPS_REG " 1\n"); + prints(".de " RESET_MACRO_NAME "\n" + ". ft \\n[.f]\n" + ". ps \\n[.s]\n" + ". vs \\n[.v]u\n" + ". in \\n[.i]u\n" + ". ll \\n[.l]u\n" + ". ls \\n[.L]\n" + ". hy \\\\n[" SAVED_HYPHENATION_MODE_REG "]\n" + ". hla \\\\*[" SAVED_HYPHENATION_LANG_NAME "]\n" + ". hlm \\\\n[" SAVED_HYPHENATION_MAX_LINES_REG "]\n" + ". hym \\\\n[" SAVED_HYPHENATION_MARGIN_REG "]\n" + ". hys \\\\n[" SAVED_HYPHENATION_SPACE_REG "]\n" + ". ad \\n[.j]\n" + ". ie \\n[.u] .fi\n" + ". el .nf\n" + ". ce \\n[.ce]\n" + ". ta \\\\*[" SAVED_TABS_NAME "]\n" + ". ss \\\\n[" SAVED_INTER_WORD_SPACE_SIZE "]" + " \\\\n[" SAVED_INTER_SENTENCE_SPACE_SIZE "]\n" + "..\n" + ".nr " SAVED_INDENT_REG " \\n[.i]\n" + ".nr " SAVED_FONT_REG " \\n[.f]\n" + ".nr " SAVED_SIZE_REG " \\n[.s]\n" + ".nr " SAVED_FILL_REG " \\n[.u]\n" + ".ds " SAVED_TABS_NAME " \\n[.tabs]\n" + ".nr " SAVED_INTER_WORD_SPACE_SIZE " \\n[.ss]\n" + ".nr " SAVED_INTER_SENTENCE_SPACE_SIZE " \\n[.sss]\n" + ".nr " SAVED_HYPHENATION_MODE_REG " \\n[.hy]\n" + ".ds " SAVED_HYPHENATION_LANG_NAME " \\n[.hla]\n" + ".nr " SAVED_HYPHENATION_MAX_LINES_REG " (\\n[.hlm])\n" + ".nr " SAVED_HYPHENATION_MARGIN_REG " \\n[.hym]\n" + ".nr " SAVED_HYPHENATION_SPACE_REG " \\n[.hys]\n" + ".nr T. 0\n" + ".nr " CURRENT_ROW_REG " 0-1\n" + ".nr " LAST_PASSED_ROW_REG " 0-1\n" + ".nr " SECTION_DIVERSION_FLAG_REG " 0\n" + ".ds " TRANSPARENT_STRING_NAME "\n" + ".ds " QUOTE_STRING_NAME "\n" + ".nr " NEED_BOTTOM_RULE_REG " 1\n" + ".nr " SUPPRESS_BOTTOM_REG " 0\n" + ".eo\n" + ".de " REPEATED_MARK_MACRO "\n" + ". mk \\$1\n" + ". if !'\\n(.z'' \\!." REPEATED_MARK_MACRO " \"\\$1\"\n" + "..\n" + ".de " REPEATED_VPT_MACRO "\n" + ". vpt \\$1\n" + ". if !'\\n(.z'' \\!." REPEATED_VPT_MACRO " \"\\$1\"\n" + "..\n"); + if (!(flags & NOKEEP)) { + prints(".de " KEEP_MACRO_NAME "\n" + ". if '\\n[.z]'' \\{\\\n" + ". ds " QUOTE_STRING_NAME " \\\\\n" + ". ds " TRANSPARENT_STRING_NAME " \\!\n" + ". di " SECTION_DIVERSION_NAME "\n" + ". nr " SECTION_DIVERSION_FLAG_REG " 1\n" + ". in 0\n" + ". \\}\n" + "..\n" + // Protect '#' in macro name from being interpreted by eqn. + ".ig\n" + ".EQ\n" + "delim off\n" + ".EN\n" + "..\n" + ".de " RELEASE_MACRO_NAME "\n" + ". if \\n[" SECTION_DIVERSION_FLAG_REG "] \\{\\\n" + ". di\n" + ". in \\n[" SAVED_INDENT_REG "]u\n" + ". nr " SAVED_DN_REG " \\n[dn]\n" + ". ds " QUOTE_STRING_NAME "\n" + ". ds " TRANSPARENT_STRING_NAME "\n" + ". nr " SECTION_DIVERSION_FLAG_REG " 0\n" + ". if \\n[.t]<=\\n[dn] \\{\\\n" + ". nr T. 1\n" + ". T#\n" + ". nr " SUPPRESS_BOTTOM_REG " 1\n" + ". sp \\n[.t]u\n" + ". nr " SUPPRESS_BOTTOM_REG " 0\n" + ". mk #T\n" + ". \\}\n"); + if (!(flags & NOWARN)) { + prints(". if \\n[.t]<=\\n[" SAVED_DN_REG "] \\{\\\n"); + // eqn(1) delimiters have already been switched off. + entry_list->set_location(); + // Since we turn off traps, troff won't go into an infinite loop + // when we output the table row; it will just flow off the bottom + // of the page. + prints(". tmc \\n[.F]:\\n[.c]: warning:\n" + ". tm1 \" table row does not fit on page \\n%\n"); + prints(". \\}\n"); + } + prints(". nf\n" + ". ls 1\n" + ". " SECTION_DIVERSION_NAME "\n" + ". ls\n" + ". rm " SECTION_DIVERSION_NAME "\n" + ". \\}\n" + "..\n" + ".ig\n" + ".EQ\n" + "delim on\n" + ".EN\n" + "..\n" + ".nr " TABLE_DIVERSION_FLAG_REG " 0\n" + ".de " TABLE_KEEP_MACRO_NAME "\n" + ". if '\\n[.z]'' \\{\\\n" + ". di " TABLE_DIVERSION_NAME "\n" + ". nr " TABLE_DIVERSION_FLAG_REG " 1\n" + ". \\}\n" + "..\n" + ".de " TABLE_RELEASE_MACRO_NAME "\n" + ". if \\n[" TABLE_DIVERSION_FLAG_REG "] \\{\\\n" + ". br\n" + ". di\n" + ". nr " SAVED_DN_REG " \\n[dn]\n" + ". ne \\n[dn]u+\\n[.V]u\n" + ". ie \\n[.t]<=\\n[" SAVED_DN_REG "] \\{\\\n"); + // Protect characters in diagnostic message (especially :, [, ]) + // from being interpreted by eqn. + prints(". ds " NOP_NAME " \\\" empty\n"); + prints(". ig " NOP_NAME "\n" + ".EQ\n" + "delim off\n" + ".EN\n" + ". " NOP_NAME "\n"); + entry_list->set_location(); + prints(". nr " PREVIOUS_PAGE_REG " (\\n% - 1)\n" + ". tmc \\n[.F]:\\n[.c]: error:\n" + ". tmc \" boxed table does not fit on page" + " \\n[" PREVIOUS_PAGE_REG "];\n" + ". tm1 \" use .TS H/.TH with a supporting macro package" + "\n" + ". rr " PREVIOUS_PAGE_REG "\n"); + prints(". ig " NOP_NAME "\n" + ".EQ\n" + "delim on\n" + ".EN\n" + ". " NOP_NAME "\n"); + prints(". \\}\n" + ". el \\{\\\n" + ". in 0\n" + ". ls 1\n" + ". nf\n" + ". " TABLE_DIVERSION_NAME "\n" + ". \\}\n" + ". rm " TABLE_DIVERSION_NAME "\n" + ". \\}\n" + "..\n"); + } + prints(".ec\n" + ".ce 0\n"); + prints(".nr " SAVED_NUMBERING_LINENO " \\n[ln]\n" + ".nr ln 0\n" + ".nr " SAVED_NUMBERING_SUPPRESSION_COUNT " \\n[.nn]\n" + ".nn 2147483647\n"); // 2^31-1; inelegant but effective + prints(".nf\n"); +} + +string block_width_reg(int r, int c) +{ + static char name[sizeof(BLOCK_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, BLOCK_WIDTH_PREFIX "%d,%d", r, c); + return string(name); +} + +string block_diversion_name(int r, int c) +{ + static char name[sizeof(BLOCK_DIVERSION_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, BLOCK_DIVERSION_PREFIX "%d,%d", r, c); + return string(name); +} + +string block_height_reg(int r, int c) +{ + static char name[sizeof(BLOCK_HEIGHT_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, BLOCK_HEIGHT_PREFIX "%d,%d", r, c); + return string(name); +} + +string span_width_reg(int start_col, int end_col) +{ + static char name[sizeof(SPAN_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, SPAN_WIDTH_PREFIX "%d", start_col); + if (end_col != start_col) + sprintf(strchr(name, '\0'), ",%d", end_col); + return string(name); +} + +string span_left_numeric_width_reg(int start_col, int end_col) +{ + static char name[sizeof(SPAN_LEFT_NUMERIC_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, SPAN_LEFT_NUMERIC_WIDTH_PREFIX "%d", start_col); + if (end_col != start_col) + sprintf(strchr(name, '\0'), ",%d", end_col); + return string(name); +} + +string span_right_numeric_width_reg(int start_col, int end_col) +{ + static char name[sizeof(SPAN_RIGHT_NUMERIC_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, SPAN_RIGHT_NUMERIC_WIDTH_PREFIX "%d", start_col); + if (end_col != start_col) + sprintf(strchr(name, '\0'), ",%d", end_col); + return string(name); +} + +string span_alphabetic_width_reg(int start_col, int end_col) +{ + static char name[sizeof(SPAN_ALPHABETIC_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS]; + sprintf(name, SPAN_ALPHABETIC_WIDTH_PREFIX "%d", start_col); + if (end_col != start_col) + sprintf(strchr(name, '\0'), ",%d", end_col); + return string(name); +} + +string column_separation_reg(int col) +{ + static char name[sizeof(COLUMN_SEPARATION_PREFIX)+INT_DIGITS]; + sprintf(name, COLUMN_SEPARATION_PREFIX "%d", col); + return string(name); +} + +string row_start_reg(int row) +{ + static char name[sizeof(ROW_START_PREFIX)+INT_DIGITS]; + sprintf(name, ROW_START_PREFIX "%d", row); + return string(name); +} + +string column_start_reg(int col) +{ + static char name[sizeof(COLUMN_START_PREFIX)+INT_DIGITS]; + sprintf(name, COLUMN_START_PREFIX "%d", col); + return string(name); +} + +string column_end_reg(int col) +{ + static char name[sizeof(COLUMN_END_PREFIX)+INT_DIGITS]; + sprintf(name, COLUMN_END_PREFIX "%d", col); + return string(name); +} + +string column_divide_reg(int col) +{ + static char name[sizeof(COLUMN_DIVIDE_PREFIX)+INT_DIGITS]; + sprintf(name, COLUMN_DIVIDE_PREFIX "%d", col); + return string(name); +} + +string row_top_reg(int row) +{ + static char name[sizeof(ROW_TOP_PREFIX)+INT_DIGITS]; + sprintf(name, ROW_TOP_PREFIX "%d", row); + return string(name); +} + +void init_span_reg(int start_col, int end_col) +{ + printfs(".nr %1 \\n(.H\n.nr %2 0\n.nr %3 0\n.nr %4 0\n", + span_width_reg(start_col, end_col), + span_alphabetic_width_reg(start_col, end_col), + span_left_numeric_width_reg(start_col, end_col), + span_right_numeric_width_reg(start_col, end_col)); +} + +void compute_span_width(int start_col, int end_col) +{ + printfs(".nr %1 \\n[%1]>?(\\n[%2]+\\n[%3])\n" + ".if \\n[%4] .nr %1 \\n[%1]>?(\\n[%4]+2n)\n", + span_width_reg(start_col, end_col), + span_left_numeric_width_reg(start_col, end_col), + span_right_numeric_width_reg(start_col, end_col), + span_alphabetic_width_reg(start_col, end_col)); +} + +// Increase the widths of columns so that the width of any spanning +// entry is not greater than the sum of the widths of the columns that +// it spans. Ensure that the widths of columns remain equal. + +void table::divide_span(int start_col, int end_col) +{ + assert(end_col > start_col); + printfs(".nr " NEEDED_REG " \\n[%1]-(\\n[%2]", + span_width_reg(start_col, end_col), + span_width_reg(start_col, start_col)); + int i; + for (i = start_col + 1; i <= end_col; i++) { + // The column separation may shrink with the expand option. + if (!(flags & EXPAND)) + printfs("+%1n", as_string(column_separation[i - 1])); + printfs("+\\n[%1]", span_width_reg(i, i)); + } + prints(")\n"); + printfs(".nr " NEEDED_REG " \\n[" NEEDED_REG "]/%1\n", + as_string(end_col - start_col + 1)); + prints(".if \\n[" NEEDED_REG "] \\{\\\n"); + for (i = start_col; i <= end_col; i++) + printfs(". nr %1 +\\n[" NEEDED_REG "]\n", + span_width_reg(i, i)); + int equal_flag = 0; + for (i = start_col; i <= end_col && !equal_flag; i++) + if (equal[i] || expand[i]) + equal_flag = 1; + if (equal_flag) { + for (i = 0; i < ncolumns; i++) + if (i < start_col || i > end_col) + printfs(". nr %1 +\\n[" NEEDED_REG "]\n", + span_width_reg(i, i)); + } + prints(".\\}\n"); +} + +void table::sum_columns(int start_col, int end_col, int do_expand) +{ + assert(end_col > start_col); + int i; + for (i = start_col; i <= end_col; i++) + if (expand[i]) + break; + if (i > end_col) { + if (do_expand) + return; + } + else { + if (!do_expand) + return; + } + printfs(".nr %1 \\n[%2]", + span_width_reg(start_col, end_col), + span_width_reg(start_col, start_col)); + for (i = start_col + 1; i <= end_col; i++) + printfs("+(%1*\\n[" SEPARATION_FACTOR_REG "])+\\n[%2]", + as_string(column_separation[i - 1]), + span_width_reg(i, i)); + prints('\n'); +} + +horizontal_span::horizontal_span(int sc, int ec, horizontal_span *p) +: next(p), start_col(sc), end_col(ec) +{ +} + +void table::build_span_list() +{ + span_list = 0; + table_entry *p = entry_list; + while (p) { + if (p->end_col != p->start_col) { + horizontal_span *q; + for (q = span_list; q; q = q->next) + if (q->start_col == p->start_col + && q->end_col == p->end_col) + break; + if (!q) + span_list = new horizontal_span(p->start_col, p->end_col, span_list); + } + p = p->next; + } + // Now sort span_list primarily by order of end_row, and secondarily + // by reverse order of start_row. This ensures that if we divide + // spans using the order in span_list, we will get reasonable results. + horizontal_span *unsorted = span_list; + span_list = 0; + while (unsorted) { + horizontal_span **pp; + for (pp = &span_list; *pp; pp = &(*pp)->next) + if (unsorted->end_col < (*pp)->end_col + || (unsorted->end_col == (*pp)->end_col + && (unsorted->start_col > (*pp)->start_col))) + break; + horizontal_span *tem = unsorted->next; + unsorted->next = *pp; + *pp = unsorted; + unsorted = tem; + } +} + +void table::compute_overall_width() +{ + prints(".\\\" compute overall width\n"); + if (!(flags & GAP_EXPAND)) { + if (left_separation) + printfs(".if n .ll -%1n\n", as_string(left_separation)); + if (right_separation) + printfs(".if n .ll -%1n\n", as_string(right_separation)); + } + // Compute the amount of horizontal space available for expansion, + // measuring every column _including_ those eligible for expansion. + // This is the minimum required to set the table without compression. + prints(".nr " EXPAND_REG " 0\n"); + prints(".nr " AVAILABLE_WIDTH_REG " \\n[.l]-\\n[.i]"); + for (int i = 0; i < ncolumns; i++) + printfs("-\\n[%1]", span_width_reg(i, i)); + if (total_separation) + printfs("-%1n", as_string(total_separation)); + prints("\n"); + // If the "expand" region option was given, a different warning will + // be issued later (if "nowarn" was not also specified). + if ((!(flags & NOWARN)) && (!(flags & EXPAND))) { + prints(".if \\n[" AVAILABLE_WIDTH_REG "]<0 \\{\\\n"); + // Protect characters in diagnostic message (especially :, [, ]) + // from being interpreted by eqn. + prints(". ig\n" + ".EQ\n" + "delim off\n" + ".EN\n" + ". .\n"); + entry_list->set_location(); + prints(". tmc \\n[.F]:\\n[.c]: warning:\n" + ". tm1 \" table wider than line length minus indentation" + "\n"); + prints(". ig\n" + ".EQ\n" + "delim on\n" + ".EN\n" + ". .\n"); + prints(". nr " AVAILABLE_WIDTH_REG " 0\n"); + prints(".\\}\n"); + } + // Now do a similar computation, this time omitting columns that + // _aren't_ undergoing expansion. The difference is the amount of + // space we have to distribute among the expanded columns. + bool do_expansion = false; + for (int i = 0; i < ncolumns; i++) + if (expand[i]) { + do_expansion = true; + break; + } + if (do_expansion) { + prints(".if \\n[" AVAILABLE_WIDTH_REG "] \\\n"); + prints(". nr " EXPAND_REG " \\n[.l]-\\n[.i]"); + for (int i = 0; i < ncolumns; i++) + if (!expand[i]) + printfs("-\\n[%1]", span_width_reg(i, i)); + if (total_separation) + printfs("-%1n", as_string(total_separation)); + prints("\n"); + int colcount = count_expand_columns(); + if (colcount > 1) + printfs(".nr " EXPAND_REG " \\n[" EXPAND_REG "]/%1\n", + as_string(colcount)); + for (int i = 0; i < ncolumns; i++) + if (expand[i]) + printfs(".nr %1 \\n[%1]>?\\n[" EXPAND_REG "]\n", + span_width_reg(i, i)); + } +} + +void table::compute_total_separation() +{ + if (flags & (ALLBOX | BOX | DOUBLEBOX)) + left_separation = right_separation = 1; + else { + for (int r = 0; r < nrows; r++) { + if (vline[r][0] > 0) + left_separation = 1; + if (vline[r][ncolumns] > 0) + right_separation = 1; + } + } + total_separation = left_separation + right_separation; + for (int c = 0; c < ncolumns - 1; c++) + total_separation += column_separation[c]; +} + +void table::compute_separation_factor() +{ + prints(".\\\" compute column separation factor\n"); + // Don't let the separation factor be negative. + prints(".nr " SEPARATION_FACTOR_REG " \\n[.l]-\\n[.i]"); + for (int i = 0; i < ncolumns; i++) + printfs("-\\n[%1]", span_width_reg(i, i)); + printfs("/%1\n", as_string(total_separation)); + // Store the remainder for use in compute_column_positions(). + if (flags & GAP_EXPAND) { + prints(".if n \\\n"); + prints(". nr " LEFTOVER_FACTOR_REG " \\n[.l]-\\n[.i]"); + for (int i = 0; i < ncolumns; i++) + printfs("-\\n[%1]", span_width_reg(i, i)); + printfs("%%%1\n", as_string(total_separation)); + } + prints(".ie \\n[" SEPARATION_FACTOR_REG "]<=0 \\{\\\n"); + if (!(flags & NOWARN)) { + // Protect characters in diagnostic message (especially :, [, ]) + // from being interpreted by eqn. + prints(".ig\n" + ".EQ\n" + "delim off\n" + ".EN\n" + "..\n"); + entry_list->set_location(); + prints(".tmc \\n[.F]:\\n[.c]: warning:\n" + ".tm1 \" table column separation reduced to zero\n" + ".nr " SEPARATION_FACTOR_REG " 0\n"); + } + prints(".\\}\n" + ".el .if \\n[" SEPARATION_FACTOR_REG "]<1n \\{\\\n"); + if (!(flags & NOWARN)) { + entry_list->set_location(); + prints(".tmc \\n[.F]:\\n[.c]: warning:\n" + ".tm1 \" table column separation reduced to fit line" + " length\n"); + prints(".ig\n" + ".EQ\n" + "delim on\n" + ".EN\n" + "..\n"); + } + prints(".\\}\n"); +} + +void table::compute_column_positions() +{ + prints(".\\\" compute column positions\n"); + printfs(".nr %1 0\n", column_divide_reg(0)); + printfs(".nr %1 %2n\n", column_start_reg(0), + as_string(left_separation)); + // In nroff mode, compensate for width of vertical rule. + if (left_separation) + printfs(".if n .nr %1 +1n\n", column_start_reg(0)); + int i; + for (i = 1;; i++) { + printfs(".nr %1 \\n[%2]+\\n[%3]\n", + column_end_reg(i-1), + column_start_reg(i-1), + span_width_reg(i-1, i-1)); + if (i >= ncolumns) + break; + printfs(".nr %1 \\n[%2]+(%3*\\n[" SEPARATION_FACTOR_REG "])\n", + column_start_reg(i), + column_end_reg(i-1), + as_string(column_separation[i-1])); + // If we have leftover expansion room in a table using the "expand" + // region option, put it prior to the last column so that the table + // looks as if expanded to the available line length. + if ((ncolumns > 2) && (flags & GAP_EXPAND) && (i == (ncolumns - 1))) + printfs(".if n .if \\n[" LEFTOVER_FACTOR_REG "] .nr %1 +(1n>?\\n[" + LEFTOVER_FACTOR_REG "])\n", + column_start_reg(i)); + printfs(".nr %1 \\n[%2]+\\n[%3]/2\n", + column_divide_reg(i), + column_end_reg(i-1), + column_start_reg(i)); + } + printfs(".nr %1 \\n[%2]+%3n\n", + column_divide_reg(ncolumns), + column_end_reg(i-1), + as_string(right_separation)); + printfs(".nr TW \\n[%1]\n", + column_divide_reg(ncolumns)); + if (flags & DOUBLEBOX) { + printfs(".nr %1 +" DOUBLE_LINE_SEP "\n", column_divide_reg(0)); + printfs(".nr %1 -" DOUBLE_LINE_SEP "\n", column_divide_reg(ncolumns)); + } +} + +void table::make_columns_equal() +{ + int first = -1; // index of first equal column + int i; + for (i = 0; i < ncolumns; i++) + if (equal[i]) { + if (first < 0) { + printfs(".nr %1 \\n[%1]", span_width_reg(i, i)); + first = i; + } + else + printfs(">?\\n[%1]", span_width_reg(i, i)); + } + if (first >= 0) { + prints('\n'); + for (i = first + 1; i < ncolumns; i++) + if (equal[i]) + printfs(".nr %1 \\n[%2]\n", + span_width_reg(i, i), + span_width_reg(first, first)); + } +} + +void table::compute_widths() +{ + prints(".\\\" compute column widths\n"); + build_span_list(); + int i; + horizontal_span *p; + // These values get refined later. + prints(".nr " SEPARATION_FACTOR_REG " 1n\n"); + for (i = 0; i < ncolumns; i++) { + init_span_reg(i, i); + if (!minimum_width[i].empty()) + printfs(".nr %1 (n;%2)\n", span_width_reg(i, i), minimum_width[i]); + } + for (p = span_list; p; p = p->next) + init_span_reg(p->start_col, p->end_col); + // Compute all field widths except for blocks. + table_entry *q; + for (q = entry_list; q; q = q->next) + if (!q->mod->zero_width) + q->do_width(); + // Compute all span widths, not handling blocks yet. + for (i = 0; i < ncolumns; i++) + compute_span_width(i, i); + for (p = span_list; p; p = p->next) + compute_span_width(p->start_col, p->end_col); + // Making columns equal normally increases the width of some columns. + make_columns_equal(); + // Note that divide_span keeps equal width columns equal. + // This function might increase the width of some columns, too. + for (p = span_list; p; p = p->next) + divide_span(p->start_col, p->end_col); + compute_total_separation(); + for (p = span_list; p; p = p->next) + sum_columns(p->start_col, p->end_col, 0); + // Now handle unexpanded blocks. + bool had_spanning_block = false; + bool had_equal_block = false; + for (q = entry_list; q; q = q->next) + if (q->divert(ncolumns, minimum_width, + (flags & EXPAND) ? column_separation : 0, 0)) { + if (q->end_col > q->start_col) + had_spanning_block = true; + for (i = q->start_col; i <= q->end_col && !had_equal_block; i++) + if (equal[i]) + had_equal_block = true; + } + // Adjust widths. + if (had_equal_block) + make_columns_equal(); + if (had_spanning_block) + for (p = span_list; p; p = p->next) + divide_span(p->start_col, p->end_col); + compute_overall_width(); + if ((flags & EXPAND) && total_separation != 0) { + compute_separation_factor(); + for (p = span_list; p; p = p->next) + sum_columns(p->start_col, p->end_col, 0); + } + else { + // Handle expanded blocks. + for (p = span_list; p; p = p->next) + sum_columns(p->start_col, p->end_col, 1); + for (q = entry_list; q; q = q->next) + if (q->divert(ncolumns, minimum_width, 0, 1)) { + if (q->end_col > q->start_col) + had_spanning_block = true; + } + // Adjust widths again. + if (had_spanning_block) + for (p = span_list; p; p = p->next) + divide_span(p->start_col, p->end_col); + } + compute_column_positions(); +} + +void table::print_single_hline(int r) +{ + prints(".vs " LINE_SEP ">?\\n[.V]u\n" + ".ls 1\n" + "\\v'" BODY_DEPTH "'" + "\\s[\\n[" LINESIZE_REG "]]"); + if (r > nrows - 1) + prints("\\D'l |\\n[TW]u 0'"); + else { + int start_col = 0; + for (;;) { + while (start_col < ncolumns + && entry[r][start_col] != 0 + && entry[r][start_col]->start_row != r) + start_col++; + int end_col; + for (end_col = start_col; + end_col < ncolumns + && (entry[r][end_col] == 0 + || entry[r][end_col]->start_row == r); + end_col++) + ; + if (end_col <= start_col) + break; + printfs("\\h'|\\n[%1]u", + column_divide_reg(start_col)); + if ((r > 0 && vline[r-1][start_col] == 2) + || (r < nrows && vline[r][start_col] == 2)) + prints("-" HALF_DOUBLE_LINE_SEP); + prints("'"); + printfs("\\D'l |\\n[%1]u", + column_divide_reg(end_col)); + if ((r > 0 && vline[r-1][end_col] == 2) + || (r < nrows && vline[r][end_col] == 2)) + prints("+" HALF_DOUBLE_LINE_SEP); + prints(" 0'"); + start_col = end_col; + } + } + prints("\\s0\n"); + prints(".ls\n" + ".vs\n"); +} + +void table::print_double_hline(int r) +{ + prints(".vs " LINE_SEP "+" DOUBLE_LINE_SEP + ">?\\n[.V]u\n" + ".ls 1\n" + "\\v'" BODY_DEPTH "'" + "\\s[\\n[" LINESIZE_REG "]]"); + if (r > nrows - 1) + prints("\\v'-" DOUBLE_LINE_SEP "'" + "\\D'l |\\n[TW]u 0'" + "\\v'" DOUBLE_LINE_SEP "'" + "\\h'|0'" + "\\D'l |\\n[TW]u 0'"); + else { + int start_col = 0; + for (;;) { + while (start_col < ncolumns + && entry[r][start_col] != 0 + && entry[r][start_col]->start_row != r) + start_col++; + int end_col; + for (end_col = start_col; + end_col < ncolumns + && (entry[r][end_col] == 0 + || entry[r][end_col]->start_row == r); + end_col++) + ; + if (end_col <= start_col) + break; + const char *left_adjust = 0; + if ((r > 0 && vline[r-1][start_col] == 2) + || (r < nrows && vline[r][start_col] == 2)) + left_adjust = "-" HALF_DOUBLE_LINE_SEP; + const char *right_adjust = 0; + if ((r > 0 && vline[r-1][end_col] == 2) + || (r < nrows && vline[r][end_col] == 2)) + right_adjust = "+" HALF_DOUBLE_LINE_SEP; + printfs("\\v'-" DOUBLE_LINE_SEP "'" + "\\h'|\\n[%1]u", + column_divide_reg(start_col)); + if (left_adjust) + prints(left_adjust); + prints("'"); + printfs("\\D'l |\\n[%1]u", + column_divide_reg(end_col)); + if (right_adjust) + prints(right_adjust); + prints(" 0'"); + printfs("\\v'" DOUBLE_LINE_SEP "'" + "\\h'|\\n[%1]u", + column_divide_reg(start_col)); + if (left_adjust) + prints(left_adjust); + prints("'"); + printfs("\\D'l |\\n[%1]u", + column_divide_reg(end_col)); + if (right_adjust) + prints(right_adjust); + prints(" 0'"); + start_col = end_col; + } + } + prints("\\s0\n" + ".ls\n" + ".vs\n"); +} + +void table::compute_vrule_top_adjust(int start_row, int col, string &result) +{ + if (row_is_all_lines[start_row] && start_row < nrows - 1) { + if (row_is_all_lines[start_row] == 2) + result = LINE_SEP ">?\\n[.V]u" "+" DOUBLE_LINE_SEP; + else + result = LINE_SEP ">?\\n[.V]u"; + start_row++; + } + else { + result = ""; + if (start_row == 0) + return; + for (stuff *p = stuff_list; p && p->row <= start_row; p = p->next) + if (p->row == start_row + && (p->is_single_line() || p->is_double_line())) + return; + } + int left = 0; + if (col > 0) { + table_entry *e = entry[start_row-1][col-1]; + if (e && e->start_row == e->end_row) { + if (e->to_double_line_entry() != 0) + left = 2; + else if (e->to_single_line_entry() != 0) + left = 1; + } + } + int right = 0; + if (col < ncolumns) { + table_entry *e = entry[start_row-1][col]; + if (e && e->start_row == e->end_row) { + if (e->to_double_line_entry() != 0) + right = 2; + else if (e->to_single_line_entry() != 0) + right = 1; + } + } + if (row_is_all_lines[start_row-1] == 0) { + if (left > 0 || right > 0) { + result += "-" BODY_DEPTH "-" BAR_HEIGHT; + if ((left == 2 && right != 2) || (right == 2 && left != 2)) + result += "-" HALF_DOUBLE_LINE_SEP; + else if (left == 2 && right == 2) + result += "+" HALF_DOUBLE_LINE_SEP; + } + } + else if (row_is_all_lines[start_row-1] == 2) { + if ((left == 2 && right != 2) || (right == 2 && left != 2)) + result += "-" DOUBLE_LINE_SEP; + else if (left == 1 || right == 1) + result += "-" HALF_DOUBLE_LINE_SEP; + } +} + +void table::compute_vrule_bot_adjust(int end_row, int col, string &result) +{ + if (row_is_all_lines[end_row] && end_row > 0) { + end_row--; + result = ""; + } + else { + stuff *p; + for (p = stuff_list; p && p->row < end_row + 1; p = p->next) + ; + if (p && p->row == end_row + 1 && p->is_double_line()) { + result = "-" DOUBLE_LINE_SEP; + return; + } + if ((p != 0 && p->row == end_row + 1) + || end_row == nrows - 1) { + result = ""; + return; + } + if (row_is_all_lines[end_row+1] == 1) + result = LINE_SEP; + else if (row_is_all_lines[end_row+1] == 2) + result = LINE_SEP "+" DOUBLE_LINE_SEP; + else + result = ""; + } + int left = 0; + if (col > 0) { + table_entry *e = entry[end_row+1][col-1]; + if (e && e->start_row == e->end_row) { + if (e->to_double_line_entry() != 0) + left = 2; + else if (e->to_single_line_entry() != 0) + left = 1; + } + } + int right = 0; + if (col < ncolumns) { + table_entry *e = entry[end_row+1][col]; + if (e && e->start_row == e->end_row) { + if (e->to_double_line_entry() != 0) + right = 2; + else if (e->to_single_line_entry() != 0) + right = 1; + } + } + if (row_is_all_lines[end_row+1] == 0) { + if (left > 0 || right > 0) { + result = "1v-" BODY_DEPTH "-" BAR_HEIGHT; + if ((left == 2 && right != 2) || (right == 2 && left != 2)) + result += "+" HALF_DOUBLE_LINE_SEP; + else if (left == 2 && right == 2) + result += "-" HALF_DOUBLE_LINE_SEP; + } + } + else if (row_is_all_lines[end_row+1] == 2) { + if (left == 2 && right == 2) + result += "-" DOUBLE_LINE_SEP; + else if (left != 2 && right != 2 && (left == 1 || right == 1)) + result += "-" HALF_DOUBLE_LINE_SEP; + } +} + +void table::add_vertical_rule(int start_row, int end_row, + int col, int is_double) +{ + vrule_list = new vertical_rule(start_row, end_row, col, is_double, + vrule_list); + compute_vrule_top_adjust(start_row, col, vrule_list->top_adjust); + compute_vrule_bot_adjust(end_row, col, vrule_list->bot_adjust); +} + +void table::build_vrule_list() +{ + int col; + if (flags & ALLBOX) { + for (col = 1; col < ncolumns; col++) { + int start_row = 0; + for (;;) { + while (start_row < nrows && vline_spanned(start_row, col)) + start_row++; + if (start_row >= nrows) + break; + int end_row = start_row; + while (end_row < nrows && !vline_spanned(end_row, col)) + end_row++; + end_row--; + add_vertical_rule(start_row, end_row, col, 0); + start_row = end_row + 1; + } + } + } + if (flags & (BOX | ALLBOX | DOUBLEBOX)) { + add_vertical_rule(0, nrows - 1, 0, 0); + add_vertical_rule(0, nrows - 1, ncolumns, 0); + } + for (int end_row = 0; end_row < nrows; end_row++) + for (col = 0; col < ncolumns+1; col++) + if (vline[end_row][col] > 0 + && !vline_spanned(end_row, col) + && (end_row == nrows - 1 + || vline[end_row+1][col] != vline[end_row][col] + || vline_spanned(end_row+1, col))) { + int start_row; + for (start_row = end_row - 1; + start_row >= 0 + && vline[start_row][col] == vline[end_row][col] + && !vline_spanned(start_row, col); + start_row--) + ; + start_row++; + add_vertical_rule(start_row, end_row, col, vline[end_row][col] > 1); + } + for (vertical_rule *p = vrule_list; p; p = p->next) + if (p->is_double) + for (int r = p->start_row; r <= p->end_row; r++) { + if (p->col > 0 && entry[r][p->col-1] != 0 + && entry[r][p->col-1]->end_col == p->col-1) { + int is_corner = r == p->start_row || r == p->end_row; + entry[r][p->col-1]->note_double_vrule_on_right(is_corner); + } + if (p->col < ncolumns && entry[r][p->col] != 0 + && entry[r][p->col]->start_col == p->col) { + int is_corner = r == p->start_row || r == p->end_row; + entry[r][p->col]->note_double_vrule_on_left(is_corner); + } + } +} + +void table::define_bottom_macro() +{ + prints(".\\\" define bottom macro\n"); + prints(".eo\n" + // protect # in macro name against eqn + ".ig\n" + ".EQ\n" + "delim off\n" + ".EN\n" + "..\n" + ".de T#\n" + ". if !\\n[" SUPPRESS_BOTTOM_REG "] \\{\\\n" + ". " REPEATED_VPT_MACRO " 0\n" + ". mk " SAVED_VERTICAL_POS_REG "\n"); + if (flags & (BOX | ALLBOX | DOUBLEBOX)) { + prints(". if \\n[T.]&\\n[" NEED_BOTTOM_RULE_REG "] \\{\\\n"); + print_single_hline(0); + prints(". \\}\n"); + } + prints(". ls 1\n"); + for (vertical_rule *p = vrule_list; p; p = p->next) + p->contribute_to_bottom_macro(this); + if (flags & DOUBLEBOX) + prints(". if \\n[T.] \\{\\\n" + ". vs " DOUBLE_LINE_SEP ">?\\n[.V]u\n" + "\\v'" BODY_DEPTH "'\\s[\\n[" LINESIZE_REG "]]" + "\\D'l \\n[TW]u 0'\\s0\n" + ". vs\n" + ". \\}\n" + ". if \\n[" LAST_PASSED_ROW_REG "]>=0 " + ".nr " TOP_REG " \\n[#T]-" DOUBLE_LINE_SEP "\n" + ". sp -1\n" + "\\v'" BODY_DEPTH "'\\s[\\n[" LINESIZE_REG "]]" + "\\D'l 0 |\\n[" TOP_REG "]u-1v'\\s0\n" + ". sp -1\n" + "\\v'" BODY_DEPTH "'\\h'|\\n[TW]u'\\s[\\n[" LINESIZE_REG "]]" + "\\D'l 0 |\\n[" TOP_REG "]u-1v'\\s0\n"); + prints(". ls\n"); + prints(". nr " LAST_PASSED_ROW_REG " \\n[" CURRENT_ROW_REG "]\n" + ". sp |\\n[" SAVED_VERTICAL_POS_REG "]u\n" + ". " REPEATED_VPT_MACRO " 1\n"); + if ((flags & NOKEEP) && (flags & (BOX | DOUBLEBOX | ALLBOX))) + prints(". if (\\n% > \\n[" STARTING_PAGE_REG "]) \\{\\\n" + ". tmc \\n[.F]:\\n[.c]: warning:\n" + ". tmc \" boxed, unkept table does not fit on page\n" + ". tm1 \" \\n[" STARTING_PAGE_REG "]\n" + ". \\}\n"); + prints(". \\}\n" + "..\n" + ".ig\n" + ".EQ\n" + "delim on\n" + ".EN\n" + "..\n" + ".ec\n"); +} + +// is the vertical line before column c in row r horizontally spanned? + +int table::vline_spanned(int r, int c) +{ + assert(r >= 0 && r < nrows && c >= 0 && c < ncolumns + 1); + return (c != 0 && c != ncolumns && entry[r][c] != 0 + && entry[r][c]->start_col != c + // horizontally spanning lines don't count + && entry[r][c]->to_double_line_entry() == 0 + && entry[r][c]->to_single_line_entry() == 0); +} + +int table::row_begins_section(int r) +{ + assert(r >= 0 && r < nrows); + for (int i = 0; i < ncolumns; i++) + if (entry[r][i] && entry[r][i]->start_row != r) + return 0; + return 1; +} + +int table::row_ends_section(int r) +{ + assert(r >= 0 && r < nrows); + for (int i = 0; i < ncolumns; i++) + if (entry[r][i] && entry[r][i]->end_row != r) + return 0; + return 1; +} + +void table::do_row(int r) +{ + printfs(".\\\" do row %1\n", i_to_a(r)); + if (!(flags & NOKEEP) && row_begins_section(r)) + prints(".if \\n[" USE_KEEPS_REG "] ." KEEP_MACRO_NAME "\n"); + bool had_line = false; + stuff *p; + for (p = stuff_list; p && p->row < r; p = p->next) + ; + for (stuff *p1 = p; p1 && p1->row == r; p1 = p1->next) + if (!p1->printed && (p1->is_single_line() || p1->is_double_line())) { + had_line = true; + break; + } + if (!had_line && !row_is_all_lines[r]) + printfs("." REPEATED_MARK_MACRO " %1\n", row_top_reg(r)); + had_line = false; + for (; p && p->row == r; p = p->next) + if (!p->printed) { + p->print(this); + if (!had_line && (p->is_single_line() || p->is_double_line())) { + printfs("." REPEATED_MARK_MACRO " %1\n", row_top_reg(r)); + had_line = true; + } + } + // change the row *after* printing the stuff list (which might contain .TH) + printfs("\\*[" TRANSPARENT_STRING_NAME "].nr " CURRENT_ROW_REG " %1\n", + as_string(r)); + if (!had_line && row_is_all_lines[r]) + printfs("." REPEATED_MARK_MACRO " %1\n", row_top_reg(r)); + // we might have had a .TH, for example, since we last tried + if (!(flags & NOKEEP) && row_begins_section(r)) + prints(".if \\n[" USE_KEEPS_REG "] ." KEEP_MACRO_NAME "\n"); + printfs(".mk %1\n", row_start_reg(r)); + prints(".mk " BOTTOM_REG "\n" + "." REPEATED_VPT_MACRO " 0\n"); + int c; + int row_is_blank = 1; + int first_start_row = r; + for (c = 0; c < ncolumns; c++) { + table_entry *e = entry[r][c]; + if (e) { + if (e->end_row == r) { + e->do_depth(); + if (e->start_row < first_start_row) + first_start_row = e->start_row; + row_is_blank = 0; + } + c = e->end_col; + } + } + if (row_is_blank) + prints(".nr " BOTTOM_REG " +1v\n"); + if (row_is_all_lines[r]) { + prints(".vs " LINE_SEP); + if (row_is_all_lines[r] == 2) + prints("+" DOUBLE_LINE_SEP); + prints(">?\\n[.V]u\n.ls 1\n"); + prints("\\&"); + prints("\\v'" BODY_DEPTH); + if (row_is_all_lines[r] == 2) + prints("-" HALF_DOUBLE_LINE_SEP); + prints("'"); + for (c = 0; c < ncolumns; c++) { + table_entry *e = entry[r][c]; + if (e) { + if (e->end_row == e->start_row) + e->to_simple_entry()->simple_print(1); + c = e->end_col; + } + } + prints("\n"); + prints(".ls\n" + ".vs\n"); + prints(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?\\n[.d]\n"); + printfs(".sp |\\n[%1]u\n", row_start_reg(r)); + } + for (int i = row_is_all_lines[r] ? r - 1 : r; + i >= first_start_row; + i--) { + simple_entry *first = 0; + for (c = 0; c < ncolumns; c++) { + table_entry *e = entry[r][c]; + if (e) { + if (e->end_row == r && e->start_row == i) { + simple_entry *simple = e->to_simple_entry(); + if (simple) { + if (!first) { + prints(".ta"); + first = simple; + } + simple->add_tab(); + } + } + c = e->end_col; + } + } + if (first) { + prints('\n'); + first->position_vertically(); + first->set_location(); + prints("\\&"); + first->simple_print(0); + for (c = first->end_col + 1; c < ncolumns; c++) { + table_entry *e = entry[r][c]; + if (e) { + if (e->end_row == r && e->start_row == i) { + simple_entry *simple = e->to_simple_entry(); + if (simple) { + if (e->end_row != e->start_row) { + prints('\n'); + simple->position_vertically(); + prints("\\&"); + } + simple->simple_print(0); + } + } + c = e->end_col; + } + } + prints('\n'); + prints(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?\\n[.d]\n"); + printfs(".sp |\\n[%1]u\n", row_start_reg(r)); + } + } + for (c = 0; c < ncolumns; c++) { + table_entry *e = entry[r][c]; + if (e) { + if (e->end_row == r && e->to_simple_entry() == 0) { + e->position_vertically(); + e->print(); + prints(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?\\n[.d]\n"); + printfs(".sp |\\n[%1]u\n", row_start_reg(r)); + } + c = e->end_col; + } + } + prints("." REPEATED_VPT_MACRO " 1\n" + ".sp |\\n[" BOTTOM_REG "]u\n" + "\\*[" TRANSPARENT_STRING_NAME "].nr " NEED_BOTTOM_RULE_REG " 1\n"); + if (r != nrows - 1 && (flags & ALLBOX)) { + print_single_hline(r + 1); + prints("\\*[" TRANSPARENT_STRING_NAME "].nr " NEED_BOTTOM_RULE_REG " 0\n"); + } + if (r != nrows - 1) { + if (p && p->row == r + 1 + && (p->is_single_line() || p->is_double_line())) { + p->print(this); + prints("\\*[" TRANSPARENT_STRING_NAME "].nr " NEED_BOTTOM_RULE_REG + " 0\n"); + } + int printed_one = 0; + for (vertical_rule *vr = vrule_list; vr; vr = vr->next) + if (vr->end_row == r) { + if (!printed_one) { + prints("." REPEATED_VPT_MACRO " 0\n"); + printed_one = 1; + } + vr->print(); + } + if (printed_one) + prints("." REPEATED_VPT_MACRO " 1\n"); + if (!(flags & NOKEEP) && row_ends_section(r)) + prints(".if \\n[" USE_KEEPS_REG "] ." RELEASE_MACRO_NAME "\n"); + } +} + +void table::do_top() +{ + prints(".\\\" do top\n"); + prints(".ss \\n[" SAVED_INTER_WORD_SPACE_SIZE "]\n"); + prints(".fc \002\003\n"); + if (flags & (BOX | DOUBLEBOX | ALLBOX)) + prints(".nr " IS_BOXED_REG " 1\n"); + else + prints(".nr " IS_BOXED_REG " 0\n"); + if (!(flags & NOKEEP) && (flags & (BOX | DOUBLEBOX | ALLBOX))) + prints("." TABLE_KEEP_MACRO_NAME "\n"); + if (flags & DOUBLEBOX) { + prints(".ls 1\n" + ".vs " LINE_SEP ">?\\n[.V]u\n" + "\\v'" BODY_DEPTH "'\\s[\\n[" LINESIZE_REG "]]\\D'l \\n[TW]u 0'\\s0\n" + ".vs\n" + "." REPEATED_MARK_MACRO " " TOP_REG "\n" + ".vs " DOUBLE_LINE_SEP ">?\\n[.V]u\n"); + printfs("\\v'" BODY_DEPTH "'" + "\\s[\\n[" LINESIZE_REG "]]" + "\\h'\\n[%1]u'" + "\\D'l |\\n[%2]u 0'" + "\\s0" + "\n", + column_divide_reg(0), + column_divide_reg(ncolumns)); + prints(".ls\n" + ".vs\n"); + } + else if (flags & (ALLBOX | BOX)) + print_single_hline(0); + // On terminal devices, a vertical rule on the first row of the table + // will stick out 1v above it if it the table is unboxed or lacks a + // horizontal rule on the first row. This is necessary for grotty's + // rule intersection detection. We must make room for it so that the + // vertical rule is not drawn above the top of the page. + else if ((flags & HAS_TOP_VLINE) && !(flags & HAS_TOP_HLINE)) + prints(".if n .sp\n"); + prints(".nr " STARTING_PAGE_REG " \\n%\n"); + //printfs(".mk %1\n", row_top_reg(0)); +} + +void table::do_bottom() +{ + prints(".\\\" do bottom\n"); + // print stuff after last row + for (stuff *p = stuff_list; p; p = p->next) + if (p->row > nrows - 1) + p->print(this); + if (!(flags & NOKEEP)) + prints(".if \\n[" USE_KEEPS_REG "] ." RELEASE_MACRO_NAME "\n"); + printfs(".mk %1\n", row_top_reg(nrows)); + prints(".nr " NEED_BOTTOM_RULE_REG " 1\n" + ".nr T. 1\n" + // protect # in macro name against eqn + ".ig\n" + ".EQ\n" + "delim off\n" + ".EN\n" + "..\n" + ".T#\n" + ".ig\n" + ".EQ\n" + "delim on\n" + ".EN\n" + "..\n"); + if (!(flags & NOKEEP) && (flags & (BOX | DOUBLEBOX | ALLBOX))) + prints("." TABLE_RELEASE_MACRO_NAME "\n"); + if (flags & DOUBLEBOX) + prints(".sp " DOUBLE_LINE_SEP "\n"); + // Horizontal box lines take up an entire row on nroff devices (maybe + // a half-row if we ever support [emulators of] devices like the + // Teletype Model 37 with half-line motions). + if (flags & (BOX | DOUBLEBOX | ALLBOX)) + prints(".if n .sp\n"); + // Space again for the doublebox option, until we can draw that more + // attractively; see Savannah #43637. + if (flags & DOUBLEBOX) + prints(".if n .sp\n"); + prints("." RESET_MACRO_NAME "\n" + ".nn \\n[" SAVED_NUMBERING_SUPPRESSION_COUNT "]\n" + ".ie \\n[" SAVED_NUMBERING_LINENO "] " + ".nm \\n[" SAVED_NUMBERING_LINENO "]\n" + ".el .nm\n" + ".fc\n" + ".cp \\n(" COMPATIBLE_REG "\n"); +} + +int table::get_nrows() +{ + return nrows; +} + +const char *last_filename = 0; + +void set_troff_location(const char *fn, int ln) +{ + if (!location_force_filename && last_filename != 0 + && strcmp(fn, last_filename) == 0) + printfs(".lf %1\n", as_string(ln)); + else { + string filename(fn); + filename += '\0'; + normalize_for_lf(filename); + printfs(".lf %1 %2\n", as_string(ln), filename.contents()); + last_filename = fn; + location_force_filename = 0; + } +} + +void printfs(const char *s, const string &arg1, const string &arg2, + const string &arg3, const string &arg4, const string &arg5) +{ + if (s) { + char c; + while ((c = *s++) != '\0') { + if (c == '%') { + switch (*s++) { + case '1': + prints(arg1); + break; + case '2': + prints(arg2); + break; + case '3': + prints(arg3); + break; + case '4': + prints(arg4); + break; + case '5': + prints(arg5); + break; + case '6': + case '7': + case '8': + case '9': + break; + case '%': + prints('%'); + break; + default: + assert(0 == "printfs format character not in [1-9%]"); + } + } + else + prints(c); + } + } +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/tbl/table.h b/src/preproc/tbl/table.h new file mode 100644 index 0000000..62346fa --- /dev/null +++ b/src/preproc/tbl/table.h @@ -0,0 +1,179 @@ +/* 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 <ctype.h> +#include <errno.h> + +#include "cset.h" +#include "cmap.h" +#include "stringclass.h" +#include "errarg.h" +#include "error.h" +#include "lf.h" + +// PREFIX and PREFIX_CHAR must be the same. +#define PREFIX "3" +#define PREFIX_CHAR '3' + +// LEADER and LEADER_CHAR must be the same. +#define LEADER "a" +#define LEADER_CHAR 'a' + +struct inc_number { + short inc; + short val; +}; + +struct entry_modifier { + inc_number point_size; + inc_number vertical_spacing; + string font; + string macro; + enum { CENTER, TOP, BOTTOM } vertical_alignment; + char zero_width; + char stagger; + + entry_modifier(); + ~entry_modifier(); +}; + +enum format_type { + FORMAT_LEFT, + FORMAT_CENTER, + FORMAT_RIGHT, + FORMAT_NUMERIC, + FORMAT_ALPHABETIC, + FORMAT_SPAN, + FORMAT_VSPAN, + FORMAT_HLINE, + FORMAT_DOUBLE_HLINE +}; + +struct entry_format : public entry_modifier { + format_type type; + + entry_format(format_type); + entry_format(); + void debug_print() const; +}; + +class table_entry; +struct horizontal_span; +struct stuff; +struct vertical_rule; + +class table { + int nrows; + int ncolumns; + int linesize; + char delim[2]; + char decimal_point_char; + vertical_rule *vrule_list; + stuff *stuff_list; + horizontal_span *span_list; + table_entry *entry_list; + table_entry **entry_list_tailp; + table_entry ***entry; + char **vline; + char *row_is_all_lines; + string *minimum_width; + int *column_separation; + char *equal; + int left_separation; // from a vertical rule or box border, in ens + int right_separation; // from a vertical rule or box border, in ens + int total_separation; + int allocated_rows; + void build_span_list(); + void compute_overall_width(); + void do_hspan(int r, int c); + void do_vspan(int r, int c); + void allocate(int r); + void compute_widths(); + void divide_span(int, int); + void sum_columns(int, int, int); + void compute_total_separation(); + void compute_separation_factor(); + void compute_column_positions(); + void do_row(int); + void init_output(); + void add_stuff(stuff *); + void do_top(); + void do_bottom(); + void do_vertical_rules(); + void build_vrule_list(); + void add_vertical_rule(int, int, int, int); + void define_bottom_macro(); + int vline_spanned(int r, int c); + int row_begins_section(int); + int row_ends_section(int); + void make_columns_equal(); + void compute_vrule_top_adjust(int, int, string &); + void compute_vrule_bot_adjust(int, int, string &); + void determine_row_type(); + int count_expand_columns(); +public: + unsigned flags; + enum { + CENTER = 0x00000001, + EXPAND = 0x00000002, + BOX = 0x00000004, + ALLBOX = 0x00000008, + DOUBLEBOX = 0x00000010, + NOKEEP = 0x00000020, + NOSPACES = 0x00000040, + NOWARN = 0x00000080, + // The next few properties help manage nroff mode output. + HAS_TOP_VLINE = 0x00000100, + HAS_TOP_HLINE = 0x00000200, + GAP_EXPAND = 0x00000400, + EXPERIMENTAL = 0x80000000 // undocumented + }; + char *expand; + table(int nc, unsigned flags, int linesize, char decimal_point_char); + ~table(); + + void add_text_line(int r, const string &, const char *, int); + void add_single_hline(int r); + void add_double_hline(int r); + void add_entry(int r, int c, const string &, const entry_format *, + const char *, int lineno); + void add_vlines(int r, const char *); + void check(); + void print(); + void set_minimum_width(int c, const string &w); + void set_column_separation(int c, int n); + void set_equal_column(int c); + void set_expand_column(int c); + void set_delim(char c1, char c2); + void print_single_hline(int r); + void print_double_hline(int r); + int get_nrows(); +}; + +void set_troff_location(const char *, int); + +extern int compatible_flag; + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/preproc/tbl/tbl.1.man b/src/preproc/tbl/tbl.1.man new file mode 100644 index 0000000..4c9a98a --- /dev/null +++ b/src/preproc/tbl/tbl.1.man @@ -0,0 +1,2018 @@ +'\" t +.TH @g@tbl @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +@g@tbl \- prepare tables for +.I groff +documents +. +. +.\" ==================================================================== +.\" 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_tbl_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@tbl +.RB [ \-C ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY @g@tbl +.B \-\-help +.YS +. +. +.SY @g@tbl +.B \-v +. +.SY @g@tbl +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU implementation of +.I tbl \" generic +is part of the +.MR groff @MAN1EXT@ +document formatting system. +. +.I @g@tbl +is a +.MR @g@troff @MAN1EXT@ +preprocessor that translates descriptions of tables embedded in +.MR roff @MAN7EXT@ +input files into the language understood by +.IR @g@troff . +. +It copies the contents of each +.I file +to the standard output stream, +except that lines between +.B .TS +and +.B .TE +are interpreted as table descriptions. +. +While GNU +.IR tbl 's \" GNU +input syntax is highly compatible with AT&T +.IR tbl , \" AT&T +the output GNU +.I tbl \" GNU +produces cannot be processed by AT&T +.IR troff ; \" AT&T +GNU +.I troff \" GNU +(or a +.I troff \" generic +implementing any GNU extensions employed) +must be used. +. +Normally, +.I @g@tbl +is not executed directly by the user, +but invoked by specifying the +.B \-t +option to +.MR groff @MAN1EXT@ . +. +If no +.I file +operands are given on the command line, +or if +.I file +is +.RB \[lq] \- \[rq], +.I @g@tbl +reads the standard input stream. +. +. +.\" ==================================================================== +.SS Overview +.\" ==================================================================== +. +.I @g@tbl +expects to find table descriptions between input lines that begin with +.B .TS +(table start) +and +.B .TE +(table end). +. +Each such +.I table region +encloses one or more table descriptions. +. +Within a table region, +table descriptions beyond the first must each be preceded +by an input line beginning with +.BR .T& . +. +This mechanism does not start a new table region; +all table descriptions are treated as part of their +.BR .TS / .TE +enclosure, +even if they are boxed or have column headings that repeat on subsequent +pages +(see below). +. +. +.P +(Experienced +.I roff +users should observe that +.I @g@tbl +is not a +.I roff +language interpreter: +the default control character must be used, +and no spaces or tabs are permitted between the control character and +the macro name. +. +These +.I @g@tbl +input tokens remain as-is in the output, +where they become ordinary macro calls. +. +Macro packages often define +.BR TS , +.BR T& , +and +.B TE +macros to handle issues of table placement on the page. +. +.I @g@tbl +produces +.I groff +code to define these macros as empty if their definitions do not exist +when the formatter encounters a table region.) +. +. +.P +Each table region may begin with +.I region options, +and must contain one or more +.I table definitions; +each table definition contains a +.I format specification +followed by one or more input lines (rows) of +.I entries. +. +These entries comprise the +.I table data. +. +. +. +.\" ==================================================================== +.SS "Region options" +.\" ==================================================================== +. +The line immediately following the +.B .TS +token may specify region options, +keywords that influence the interpretation or rendering of the region as +a whole or all table entries within it indiscriminately. +. +They must be separated by commas, +spaces, +or tabs. +. +Those that require a parenthesized argument permit spaces and tabs +between the option's name and the opening parenthesis. +. +Options accumulate and cannot be unset within a region once declared; +if an option that takes a parameter is repeated, +the last occurrence controls. +. +If present, +the set of region options must be terminated with a semicolon +.RB ( ; ). +. +. +.P +Any of the +.BR allbox , +.BR box , +.BR doublebox , +.BR frame , +and +.B doubleframe +region options makes a table \[lq]boxed\[rq] for the purpose of later +discussion. +. +. +.TP +.B allbox +Enclose each table entry in a box; +implies +.BR box . +. +. +.TP +.B box +Enclose the entire table region in a box. +. +As a GNU extension, +the alternative option name +.B frame +is also recognized. +. +. +.TP +.B center +Center the table region with respect to the current indentation and line +length; +the default is to left-align it. +. +As a GNU extension, +the alternative option name +.B centre +is also recognized. +. +. +.TP +.BI decimalpoint( c ) +Recognize character +.I c +as the decimal separator in columns using the +.B N +(numeric) classifier +(see subsection \[lq]Column classifiers\[rq] below). +. +This is a GNU extension. +. +. +.TP +.BI delim( xy ) +Recognize characters +.I x +.RI and\~ y +as start and end delimiters, +respectively, +for +.MR @g@eqn @MAN1EXT@ +input, +and ignore input between them. +. +.I x +.RI and\~ y +need not be distinct. +. +. +.TP +.B doublebox +Enclose the entire table region in a double box; +implies +.BR box . +. +As a GNU extension, +the alternative option name +.B \%doubleframe +is also recognized. +. +. +.TP +.B expand +Spread the table horizontally to fill the available space +(line length minus indentation) +by increasing column separation. +. +Ordinarily, +a table is made only as wide as necessary to accommodate the widths of +its entries and its column separations +(whether specified or default). +. +When +.B expand +applies to a table that exceeds the available horizontal space, +column separation is reduced as far as necessary +(even to zero). +. +.I @g@tbl +produces +.I groff +input that issues a diagnostic if such compression occurs. +. +The column modifier +.B x +(see below) +overrides this option. +. +. +.TP +.BI linesize( n ) +Draw lines or rules +(e.g., +from +.BR box ) +with a thickness of +.IR n \~points. +. +The default is the current type size when the region begins. +. +This option is ignored on terminal devices. +. +. +.TP +.B nokeep +Don't use +.I roff +diversions to manage page breaks. +. +Normally, +.I @g@tbl +employs them to avoid breaking a page within a table row. +. +This usage can sometimes interact badly with macro packages' own use of +diversions\[em]when footnotes, +for example, +are employed. +. +This is a GNU extension. +. +. +.TP +.B nospaces +Ignore leading and trailing spaces in table entries. +. +This is a GNU extension. +. +. +.TP +.B nowarn +Suppress diagnostic messages produced at document formatting time when +the line or page lengths are inadequate to contain a table row. +. +This is a GNU extension. +. +. +.\" TODO: How about "right"? (and "left" for symmetry) +.TP +.BI tab( c ) +Use the character +.I c +instead of a tab to separate entries in a row of table data. +. +. +.\" ==================================================================== +.SS "Table format specification" +.\" ==================================================================== +. +The table format specification is mandatory: +it determines the number of columns in the table and directs how the +entries within it are to be typeset. +. +The format specification is a series of column +.I descriptors. +. +Each descriptor encodes a +.I classifier +followed by zero or more +.I modifiers. +. +Classifiers are letters +(recognized case-insensitively) +or punctuation symbols; +modifiers consist of or begin with letters or numerals. +. +Spaces, +tabs, +newlines, +and commas separate descriptors. +. +Newlines and commas are special; +they apply the descriptors following them to a subsequent row of the +table. +. +(This enables column headings to be centered or emboldened while the +table entries for the data are not, +for instance.) +. +We term the resulting group of column descriptors a +.I row definition. +. +Within a row definition, +separation between column descriptors +(by spaces or tabs) +is often optional; +only some modifiers, +described below, +make separation necessary. +. +. +.P +Each column descriptor begins with a mandatory +.I classifier, +a character that selects from one of several arrangements. +. +Some determine the positioning of table entries within a rectangular +cell: +centered, +left-aligned, +numeric +(aligned to a configurable decimal separator), +and so on. +. +Others perform special operations like drawing lines or spanning entries +from adjacent cells in the table. +. +Except for +.RB \[lq] | \[rq], +any classifier can be followed by one or more +.I modifiers; +some of these accept an argument, +which in GNU +.I tbl \" GNU +can be parenthesized. +.\" AT&T tbl allowed parentheses only after 'w'. +.\" TODO: Accept parentheses after 'p' and 'v'. +. +Modifiers select fonts, +set the type size, +.\"define the column width, +.\"adjust inter-column spacing, \" slack text for window/orphan control +and perform other tasks described below. +. +. +.P +The format specification can occupy multiple input lines, +but must conclude with a dot +.RB \[lq] .\& \[rq] +followed by a newline. +. +Each row definition is applied in turn to one row of the table. +. +The last row definition is applied to rows of table data in excess of +the row definitions. +. +. +.P +For clarity in this document's examples, +we shall write classifiers in uppercase and modifiers in lowercase. +. +Thus, +.RB \[lq] CbCb,LR.\& \[rq] +defines two rows of two columns. +. +The first row's entries are centered and boldfaced; +the second and any further rows' first and second columns are left- and +right-aligned, +respectively. +. +.\" slack text for window/orphan control +.\"If more rows of entries are added to the table data, +.\"they reuse the row definition +.\".RB \[lq] LR \[rq]. +. +. +.P +The row definition with the most column descriptors determines the +number of columns in the table; +any row definition with fewer is implicitly extended on the right-hand +side with +.B L +classifiers as many times as necessary to make the table rectangular. +. +. +.\" ==================================================================== +.SS "Column classifiers" +.\" ==================================================================== +. +The +.BR L , +.BR R , +and +.B C +classifiers are the easiest to understand and use. +. +. +.TP +.BR A ,\~ a +Center longest entry in this column, +left-align remaining entries in the column with respect to the centered +entry, +then indent all entries by one en. +. +Such \[lq]alphabetic\[rq] entries +(hence the name of the classifier) +can be used in the same column as +.BR L -classified +entries, +as in +.RB \[lq] LL,AR.\& \[rq]. +. +The +.B A +entries are often termed \[lq]sub-columns\[rq] due to their indentation. +. +. +.TP +.BR C ,\~ c +Center entry within the column. +. +. +.TP +.BR L ,\~ l +Left-align entry within the column. +. +. +.TP +.BR N ,\~ n +Numerically align entry in the column. +. +.I @g@tbl +aligns columns of numbers vertically at the units place. +. +If multiple decimal separators are adjacent to a digit, +it uses the rightmost one for vertical alignment. +. +If there is no decimal separator, +the rightmost digit is used for vertical alignment; +otherwise, +.I @g@tbl +centers the entry within the column. +. +The +.I roff +dummy character +.B \[rs]& +in an entry marks the glyph preceding it +(if any) +as the units place; +if multiple instances occur in the data, +the leftmost is used for alignment. +. +. +.IP +If +.BR N -classified +entries share a column with +.B L +or +.BR R \~entries, +.I @g@tbl +centers the widest +.BR N \~entry +with respect to the widest +.B L +or +.BR R \~entry, +preserving the alignment of +.BR N \~entries +with respect to each other. +. +. +.IP +The appearance of +.I @g@eqn +equations +within +.BR N -classified +columns +can be troublesome due to the foregoing textual scan for a decimal +separator. +. +Use the +.B \%delim +region option to make +.I @g@tbl +ignore the data within +.I eqn +delimiters for that purpose. +. +. +.TP +.BR R ,\~ r +Right-align entry within the column. +. +. +.TP +.BR S ,\~ s +Span previous entry on the left into this column. +. +. +.TP +.B \[ha] +Span entry in the same column from the previous row into this row. +. +. +.TP +.BR _ ,\~ \- +Replace table entry with a horizontal rule. +. +An empty table entry is expected to correspond to this classifier; +if data are found there, +.I @g@tbl +issues a diagnostic message. +. +. +.TP +.B = +Replace table entry with a double horizontal rule. +. +An empty table entry is expected to correspond to this classifier; +if data are found there, +.I @g@tbl +issues a diagnostic message. +. +. +.TP +.B | +Place a vertical rule (line) on the corresponding row of the table +(if two of these are adjacent, +a double vertical rule). +. +This classifier does not contribute to the column count and no table +entries correspond to it. +. +A +.B | +to the left of the first column descriptor or to the right of the last +one produces a vertical rule at the edge of the table; +these are redundant +(and ignored) +in boxed tables. +. +. +.P +To change the table format within a +.I @g@tbl +region, +use the +.B .T& +token at the start of a line. +. +It is followed by a format specification and table data, +but +.I not +region options. +. +The quantity of columns in a new table format thus introduced cannot +increase relative to the previous table format; +in that case, +you must end the table region and start another. +. +If that will not serve because the region uses box options or the +columns align in an undesirable manner, +you must design the initial table format specification to include the +maximum quantity of columns required, +and use the +.B S +horizontal spanning classifier where necessary to achieve the desired +columnar alignment. +. +. +.P +Attempting to horizontally span in the first column or vertically span +on the first row is an error. +. +Non-rectangular span areas are also not supported. +. +. +.\" ==================================================================== +.SS "Column modifiers" +.\" ==================================================================== +. +Any number of modifiers can follow a column classifier. +. +Arguments to modifiers, +where accepted, +are case-sensitive. +. +If the same modifier is applied to a column specifier more than once, +or if conflicting modifiers are applied, +only the last occurrence has effect. +. +The +.RB modifier\~ x +is mutually exclusive with +.B e +.RB and\~ w , +but +.B e +is not mutually exclusive +.RB with\~ w ; +if these are used in combination, +.BR x \~unsets +both +.B e +.RB and\~ w , +while either +.B e +or +.B w +.RB overrides\~ x . +. +. +.br +.ne 4v \" Keep next two tagged paragraphs together. +.TP +.BR b ,\~ B +Typeset entry in boldface, +abbreviating +.BR f(B) . +. +. +.TP +.BR d ,\~ D +Align a vertically spanned table entry to the bottom +(\[lq]down\[rq]), +instead of the center, +of its range. +. +This is a GNU extension. +. +. +.TP +.BR e ,\~ E +Equalize the widths of columns with this modifier. +. +The column with the largest width controls. +. +This modifier sets the default line length used in a text block. +. +. +.TP +.BR f ,\~ F +Select the typeface for the table entry. +. +This modifier must be followed by a font or style name +(one or two characters not starting with a digit), +font mounting position +(a single digit), +or a name or mounting position of any length in parentheses. +. +The last form is a GNU extension. +. +(The parameter corresponds to that accepted by the +.I troff \" generic +.B ft +request.) +. +A one-character argument not in parentheses must be separated by one or +more spaces or tabs from what follows. +. +. +.TP +.BR i ,\~ I +Typeset entry in an oblique or italic face, +abbreviating +.BR f(I) . +. +. +.TP +.BR m ,\~ M +Call a +.I groff +macro before typesetting a text block +(see subsection \[lq]Text blocks\[rq] below). +. +This is a GNU extension. +. +This modifier must be followed by a macro name of one or two characters +or a name of any length in parentheses. +. +A one-character macro name not in parentheses must be separated by one +or more spaces or tabs from what follows. +. +The named macro must be defined before the table region containing this +column modifier is encountered. +. +The macro should contain only simple +.I groff +requests to change text formatting, +like adjustment or hyphenation. +. +The macro is called +.I after +the column modifiers +.BR b , +.BR f , +.BR i , +.BR p , +and +.B v +take effect; +it can thus override other column modifiers. +. +. +.TP +.BR p ,\~ P +Set the type size for the table entry. +. +This modifier must be followed by an +.RI integer\~ n +with an optional leading sign. +. +If unsigned, +the type size is set to +.IR n \~scaled +points. +. +Otherwise, +the type size is incremented or decremented per the sign by +.IR n \~scaled +points. +. +The use of a signed multi-digit number is a GNU extension. +. +(The parameter corresponds to that accepted by the +.I troff \" generic +.B ps +request.) +. +If a type size modifier is followed by a column separation modifier +(see below), +they must be separated by at least one space or tab. +.\" TODO: Allow parentheses so scaling units and fractional values can +.\" be used? +. +. +.TP +.BR t ,\~ T +Align a vertically spanned table entry to the top, +instead of the center, +of its range. +. +. +.TP +.BR u ,\~ U +Move the column up one half-line, +\[lq]staggering\[rq] the rows. +. +This is a GNU extension. +. +. +.TP +.BR v ,\~ V +Set the vertical spacing to be used in a text block. +. +This modifier must be followed by an +.RI integer\~ n +with an optional leading sign. +. +If unsigned, +the vertical spacing is set to +.IR n\~ points. +. +Otherwise, +the vertical spacing is incremented or decremented per the sign by +.IR n\~ points. +. +The use of a signed multi-digit number is a GNU extension. +. +(This parameter corresponds to that accepted by the +.I troff \" generic +.B vs +request.) +. +If a vertical spacing modifier is followed by a column separation +modifier +(see below), +they must be separated by at least one space or tab. +.\" TODO: Allow parentheses so scaling units and fractional values can +.\" be used? +. +. +.TP +.BR w ,\~ W +Set the column's minimum width. +. +This modifier must be followed by a number, +which is either a unitless integer, +or a +.I roff +horizontal measurement in parentheses. +. +Parentheses are required if the width is to be followed immediately by +an explicit column separation +(alternatively, +follow the width with one or more spaces or tabs). +. +If no unit is specified, +ens are assumed. +. +This modifier sets the default line length used in a text block. +. +. +.TP +.BR x ,\~ X +Expand the column. +. +After computing the column widths, +distribute any remaining line length evenly over all columns bearing +this modifier. +. +Applying the +.BR x \~modifier +to more than one column is a GNU extension. +.\" 'x' wasn't documented at all in Lesk 1979. +. +This modifier sets the default line length used in a text block. +. +. +.TP +.BR z ,\~ Z +Ignore the table entries corresponding to this column for width +calculation purposes; +that is, +compute the column's width using only the information in its descriptor. +. +. +.TP +.I n +A numeric suffix on a column descriptor sets the separation distance +(in ens) +from the succeeding column; +the default separation is +.BR 3n . +. +This separation is +proportionally multiplied if the +.B expand +region option is in effect; +in the case of tables wider than the output line length, +this separation might be zero. +. +A negative separation cannot be specified. +. +A separation amount after the last column in a row is nonsensical and +provokes a diagnostic from +.IR @g@tbl . +. +. +.\" ==================================================================== +.SS "Table data" +.\" ==================================================================== +. +The table data come after the format specification. +. +Each input line corresponds to a table row, +except that a backslash at the end of a line of table data continues an +entry on the next input line. +. +(Text blocks, +discussed below, +also spread table entries across multiple input lines.) +. +Table entries within a row are separated in the input by a tab character +by default; +see the +.B tab +region option above. +. +Excess entries in a row of table data +(those that have no corresponding column descriptor, +not even an implicit one arising from rectangularization of the table) +are discarded with a diagnostic message. +. +.I roff +control lines are accepted between rows of table data and within text +blocks. +. +If you wish to visibly mark an empty table entry in the document source, +populate it with the +.B \[rs]& +.I roff +dummy character. +. +The table data are interrupted by a line consisting of the +.B .T& +input token, +and conclude with the line +.BR .TE . +. +. +.P +Ordinarily, +a table entry is typeset rigidly. +. +It is not filled, +broken, +hyphenated, +adjusted, +or populated with additional inter-sentence space. +. +.I @g@tbl +instructs the formatter to measure each table entry as it occurs in the +input, +updating the width required by its corresponding column. +. +If the +.B z +modifier applies to the column, +this measurement is ignored; +if +.B w +applies and its argument is larger than this width, +that argument is used instead. +. +In contrast to conventional +.I roff +input +(within a paragraph, +say), +changes to text formatting, +such as font selection or vertical spacing, +do not persist between entries. +. +. +.P +Several forms of table entry are interpreted specially. +. +. +.IP \[bu] 2n +If a table row contains only an underscore or equals sign +.RB ( _ +or +.BR = ), +a single or double horizontal rule (line), +respectively, +is drawn across the table at that point. +. +. +.IP \[bu] 2n +A table entry containing only +.B _ +or +.B = +on an otherwise populated row is replaced by a single or double +horizontal rule, +respectively, +joining its +neighbors. +. +. +.IP \[bu] 2n +Prefixing a lone underscore or equals sign with a backslash also has +meaning. +. +If a table entry consists only of +.B \[rs]_ +or +.B \[rs]= +on an otherwise populated row, +it is replaced by a single or double horizontal rule, +respectively, +that does +.I not +(quite) join its neighbors. +. +. +.IP \[bu] +A table entry consisting of +.BI \[rs]R x\c +, +where +.IR x \~is +any +.I roff +ordinary or special character, +is replaced by enough repetitions of the glyph corresponding +.RI to\~ x +to fill the column, +albeit without joining its neighbors. +.\" TODO: Bad things happen if there's garbage in the entry after 'x', +.\" which can be a *roff special character escape sequence, so +.\" validation is not trivial. +. +. +.IP \[bu] +On any row but the first, +a table entry of +.B \[rs]\[ha] +causes the entry above it to span down into the current one. +. +. +.P +On occasion, +these special tokens may be required as literal table data. +. +To use either +.B _ +or +.B = +literally and alone in an entry, +prefix or suffix it with the +.I roff +dummy character +.BR \[rs]& . +. +To express +.BR \[rs]_ , +.BR \[rs]= , +or +.BR \[rs]R , +use a +.I roff +escape sequence to interpolate the backslash +.RB ( \[rs]e +or +.BR \[rs][rs] ). +. +A reliable way to emplace the +.B \[rs]\[ha] +glyph sequence within a table entry is to use a pair of +.I groff +special character escape sequences +.RB ( \[rs][rs]\[rs][ha] ). +. +. +.P +Rows of table entries can be interleaved with +.I groff +control lines; +these do not count as table data. +. +On such lines the default control character +.RB ( .\& ) +must be used +(and not changed); +the no-break control character is not recognized. +. +To start the first table entry in a row with a dot, +precede it with the +.I roff +dummy character +.BR \[rs]& . +. +. +.\" ==================================================================== +.SS "Text blocks" +.\" ==================================================================== +. +An ordinary table entry's contents can make a column, +and therefore the table, +excessively wide; +the table then exceeds the line length of the page, +and becomes ugly or is exposed to truncation by the output device. +. +When a table entry requires more conventional typesetting, +breaking across more than one output line +(and thereby increasing the height of its row), +it can be placed within a +.I text block. +. +. +.P +.I @g@tbl +interprets a table entry beginning with +.RB \[lq] T{ \[rq] +at the end of an input line not as table data, +but as a token starting a text block. +. +Similarly, +.RB \[lq] T} \[rq] +at the start of an input line ends a text block; +it must also end the table entry. +. +Text block tokens can share an input line with other table data +(preceding +.B T{ +and following +.BR T} ). +. +Input lines between these tokens are formatted in a diversion by +.IR troff . \" generic +. +Text blocks cannot be nested. +. +Multiple text blocks can occur in a table row. +. +. +.P +Text blocks are formatted as was the text prior to the table, +modified by applicable column descriptors. +. +Specifically, +the classifiers +.BR A , +.BR C , +.BR L , +.BR N , +.BR R , +and +.B S +determine a text block's +.I alignment +within its cell, +but not its +.I adjustment. +. +Add +.B na +or +.B ad +requests to the beginning of a text block to alter its adjustment +distinctly from other text in the document. +. +As with other table entries, +when a text block ends, +any alterations to formatting parameters are discarded. +. +They do not affect subsequent table entries, +not even other text blocks. +. +. +.P +.ne 2v +If +.B w +or +.B x +modifiers are not specified for +.I all +columns of a text block's span, +the default length of the text block +(more precisely, +the line length used to process the text block diversion) +is computed as +.IR L \[tmu] C /( N +1), +.\" ...and rounded to the horizontal motion quantum of the output device +where +.I L +is the current line length, +.I C +the number of columns spanned by the text block, +and +.I N +the number of columns in the table. +. +If necessary, +you can also control a text block's width by including an +.B ll +(line length) +request in it prior to any text to be formatted. +. +Because a diversion is used to format the text block, +its height and width are subsequently available in the registers +.B dn +and +.BR dl , +respectively. +. +. +.\" ==================================================================== +.SS \f[I]roff\f[] interface +.\" ==================================================================== +. +The register +.B TW +stores the width of the table region in basic units; +it can't be used within the region itself, +but is defined before the +.B .TE +token is output so that a +.I groff +macro named +.B TE +can make use of it. +. +.B T.\& +is a Boolean-valued register indicating whether the bottom of the table +is being processed. +. +The +.B #T +register marks the top of the table. +. +Avoid using these names for any other purpose. +. +. +.P +.I @g@tbl +also defines a macro +.B T# +to produce the bottom and side lines of a boxed table. +. +While +.I @g@tbl +itself arranges for the output to include a call of this macro at the +end of such a table, +it can also be used by macro packages to create boxes for multi-page +tables by calling it from a page footer macro that is itself called by +a trap planted near the bottom of the page. +. +See section \[lq]Limitations\[rq] below for more on multi-page tables. +. +. +.P +GNU +.I tbl \" GNU +.\" AT&T tbl used all kinds of registers; many began with "3". +internally employs register, +string, +macro, +and diversion names beginning with the +.RB numeral\~ 3 . +. +A document to be preprocessed with GNU +.I tbl \" GNU +should not use any such identifiers. +.\" XXX: Why are they not named starting with "gtbl*" or something? GNU +.\" tbl turns AT&T troff compatibility mode off anyway. +. +. +.\" ==================================================================== +.SS "Interaction with \f[I]@g@eqn\f[]" +.\" ==================================================================== +. +.I @g@tbl +should always be called before +.MR @g@eqn @MAN1EXT@ . +. +(\c +.MR groff @MAN1EXT@ +automatically arranges preprocessors in the correct order.) +. +Don't call the +.B EQ +and +.B EN +macros within tables; +instead, +set up delimiters in your +.I eqn \" generic +input and use the +.B \%delim +region option so that +.I @g@tbl +will recognize them. +. +. +.br +.ne 5v \" Keep enough space for heading, intro sentence, and first item. +.\" ==================================================================== +.SS "GNU \f[I]tbl\f[] enhancements" +.\" ==================================================================== +. +In addition to extensions noted above, +GNU +.I tbl \" GNU +removes constraints endured by users of AT&T +.IR tbl .\" AT&T +. +. +.IP \[bu] 2n +Region options can be specified in any lettercase. +. +. +.IP \[bu] +There is no limit on the number of columns in a table, +regardless of their classification, +nor any limit on the number of text blocks. +. +. +.IP \[bu] +All table rows are considered when deciding column widths, +not just those occurring in the first 200 input lines of a region. +. +Similarly, +table continuation +.RB ( .T& ) +tokens are recognized outside a region's first 200 input lines. +. +. +.IP \[bu] +Numeric and alphabetic entries may appear in the same column. +. +. +.IP \[bu] +Numeric and alphabetic entries may span horizontally. +. +. +.\" ==================================================================== +.SS "Using GNU \f[I]tbl\f[] within macros" +.\" ==================================================================== +. +You can embed a table region inside a macro definition. +. +However, +since +.I @g@tbl +writes its own macro definitions at the beginning of each table region, +it is necessary to call end macros instead of ending macro definitions +with +.RB \[lq] ..\& \[rq]. +.\" XXX: Why don't we fix that by ending all of tbl's own macro +.\" definitions with a call to a macro in its own reserved name space? +. +Additionally, +the escape character must be disabled. \" XXX: Why? +. +. +.P +Not all +.I @g@tbl +features can be exercised from such macros because +.I @g@tbl +is a +.I roff +preprocessor: +it sees the input earlier than +.I @g@troff +does. +. +For example, +vertically aligning decimal separators fails if the numbers containing +them occur as macro or string parameters; +the alignment is performed by +.I @g@tbl +itself, +which sees only +.BR \[rs]$1 , +.BR \[rs]$2 , +and so on, +and therefore can't recognize a decimal separator that only appears +later when +.I @g@troff +interpolates a macro or string definition. +. +. +.\" XXX: The following is a general caveat about preprocessors; move it. +.P +Using +.I @g@tbl +macros within conditional input +(that is, +contingent upon an +.BR if , +.BR ie , +.BR el , +or +.B while +request) +can result in misleading line numbers in subsequent diagnostics. +. +.I @g@tbl +unconditionally injects its output into the source document, +but the conditional branch containing it may not be taken, +and if it is not, +the +.B lf +requests that +.I @g@tbl +injects to restore the source line number cannot take effect. +. +Consider copying the input line counter register +.B c.\& +and restoring its value at a convenient location after applicable +arithmetic. +. +. +.br +.ne 5v +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-C +Enable AT&T compatibility mode: +recognize +.B .TS +and +.B .TE +even when followed by a character other than space or newline. +. +Furthermore, +interpret the uninterpreted leader escape sequence +.BR \[rs]a . +. +. +.\" ==================================================================== +.SH Limitations +.\" ==================================================================== +. +Multi-page tables, +if boxed and/or if you want their column headings repeated after page +breaks, +require support at the time the document is formatted. +. +A convention for such support has arisen in macro packages such as +.IR ms , +.IR mm , +and +.IR me . +. +To use it, +follow the +.B .TS +token with a space and then +.RB \[lq] H \[rq]; +this will be interpreted by the formatter +as a +.B TS +macro call with an +.B H +argument. +. +Then, +within the table data, +call the +.B TH +macro; +this informs the macro package where the headings end. +. +If your table has no such heading rows, +or you do not desire their repetition, +call +.B TH +immediately after the table format specification. +. +If a multi-page table is boxed or has repeating column headings, +do not enclose it with keep/release macros, +or divert it in any other way. +. +Further, +the +.B bp +request will not cause a page break in a +.RB \[lq] "TS H" \[rq] +table. +. +Define a macro to wrap +.BR bp : +invoke it normally if there is no current diversion. +. +Otherwise, +pass the macro call to the enclosing diversion using the transparent +line escape sequence +.BR \[rs]!\& ; +this will \[lq]bubble up\[rq] the page break to the output device. +. +See section \[lq]Examples\[rq] below for a demonstration. +. +. +.P +Double horizontal rules are not supported by +.MR grotty @MAN1EXT@ ; +single rules are used instead. +. +.I \%grotty +also ignores half-line motions, +so the +.B u +column modifier has no effect. +. +On terminal devices +.RI (\[lq] nroff\~ mode\[rq]), +horizontal rules and box borders occupy a full vee of space; +this amount is doubled for +.B doublebox +tables. +. +Tables using these features thus require more vertical space in +.I nroff +mode than in +.I troff +mode: +write +.B ne +requests accordingly. +. +Vertical rules between columns are drawn in the space between columns in +.I nroff +mode; +using double vertical rules and/or reducing the column separation below +the default can make them ugly or overstrike them with table data. +. +. +.P +A text block within a table must be able to fit on one page. +. +. +.P +Using +.B \[rs]a +to put leaders in table entries does not work +in GNU +.IR tbl , \" GNU +except in compatibility mode. +. +This is correct behavior: +.B \[rs]a +is an +.I uninterpreted +leader. +. +You can still use the +.I roff +leader character (Control+A) or define a string to use +.B \[rs]a +as it was designed: +to be interpreted only in copy mode. +. +. +.RS +.P +.EX +\&.ds a \[rs]a +\&.TS +\&box center tab(;); +\&Lw(2i)0 L. +\&Population\[rs]*a;6,327,119 +\&.TE +.EE +.RE +. +. +.\" We use a real leader to avoid defining a string in a man page. +.P +.TS +box center tab(;); +Lw(2i)0 L. +Population;6,327,119 +.TE +. +. +.P +A leading and/or trailing +.B | +in a format specification, +such as +.RB \[lq] |LCR|.\& \[rq], +produces an en space between the vertical rules and the content of the +adjacent columns. +. +If no such space is desired +(so that the rule abuts the content), +you can introduce \[lq]dummy\[rq] columns with zero separation and empty +corresponding table entries before and/or after. +. +. +.RS +.P +.EX +\&.TS +\¢er tab(#); +\&R0|L C R0|L. +_ +\&#levulose#glucose#dextrose# +_ +\&.TE +.EE +.RE +. +. +.P +These dummy columns have zero width and are therefore invisible; +unfortunately they usually don't work as intended on terminal devices. +. +. +.if t \{\ +.TS +center tab(#); +R0|L C R0|L. +_ +#levulose#glucose#dextrose# +_ +.TE +.\} +. +. +.\" ==================================================================== +.SH Examples +.\" ==================================================================== +. +It can be easier to acquire the language of +.I tbl \" generic +through examples than formal description, +especially at first. +. +. +.\" Note: This example is nearly at the column limit (78n) for nroff +.\" output. Recast with care. +.RS +.P +.EX +\&.TS +box center tab(#); +Cb Cb +L L. +Ability#Application +Strength#crushes a tomato +Dexterity#dodges a thrown tomato +Constitution#eats a month-old tomato without becoming ill +Intelligence#knows that a tomato is a fruit +Wisdom#chooses \[rs]f[I]not\[rs]f[] to put tomato in a fruit salad +Charisma#sells obligate carnivores tomato-based fruit salads +\&.TE +.EE +.RE +. +. +.P +.TS +box center tab(#); +Cb Cb +L L. +Ability#Application +Strength#crushes a tomato +Dexterity#dodges a thrown tomato +Constitution#eats a month-old tomato without becoming ill +Intelligence#knows that a tomato is a fruit +Wisdom#chooses \f[I]not\f[] to put tomato in a fruit salad +Charisma#sells obligate carnivores tomato-based fruit salads +.TE +. +. +.P +The +.B A +and +.B N +column classifiers can be easier to grasp in visual rendering than in +description. +. +. +.RS +.P +.EX +\&.TS +center tab(;); +CbS,LN,AN. +Daily energy intake (in MJ) +Macronutrients +\&.\[rs]" assume 3 significant figures of precision +Carbohydrates;4.5 +Fats;2.25 +Protein;3 +\&.T& +LN,AN. +Mineral +Pu\-239;14.6 +_ +\&.T& +LN. +Total;\[rs][ti]24.4 +\&.TE +.EE +.RE +. +. +.RS +.P +.TS +center tab(;); +CbS,LN,AN. +Daily energy intake (in MJ) +.\" assume 3 significant figures of precision +Macronutrients +Carbohydrates;4.5 +Fats;2.25 +Protein;3 +.T& +LN,AN. +Mineral +Pu-239;14.6 +_ +.T& +LN. +Total;\[ti]24.4 +.TE +.RE +. +. +.br +.ne 12v +.P +Next, +we'll lightly adapt a compact presentation of spanning, +vertical alignment, +and zero-width column modifiers from the +.I mandoc +reference for its +.I tbl \" generic +interpreter. +. +It rewards close study. +. +. +.RS +.P +.EX +\&.TS +box center tab(:); +Lz S | Rt +Ld| Cb| \[ha] +\[ha] | Rz S. +left:r +l:center: +:right +\&.TE +.EE +.RE +. +. +.RS +.P +.TS +box center tab(:); +Lz S | Rt +Ld| Cb| ^ +^ | Rz S. +left:r +l:center: +:right +.TE +.RE +. +. +.P +.ne 2v +Row staggering is not visually achievable on terminal devices, +but a table using it can remain comprehensible nonetheless. +. +. +.RS +.P +.EX +\&.TS +center tab(|); +Cf(BI) Cf(BI) Cf(B), C C Cu. +n|n\[rs]f[B]\[rs][tmu]\[rs]f[]n|difference +1|1 +2|4|3 +3|9|5 +4|16|7 +5|25|9 +6|36|11 +\&.TE +.EE +.RE +. +. +.RS +.P +.TS +center tab(|); +Cf(BI) Cf(BI) Cf(B), C C Cu. +n|n\f[B]\[tmu]\f[]n|difference +1|1 +2|4|3 +3|9|5 +4|16|7 +5|25|9 +6|36|11 +.TE +.RE +. +. +.P +Some +.I @g@tbl +features cannot be illustrated in the limited environment of a portable +man page. +. +. +.\" TODO: Find a better example than this. +.\".P +.\"As noted above, +.\"we can embed a table region in a +.\".I groff +.\"macro definition. +.\". +.\".IR @g@tbl , +.\"however, +.\"cannot know what will result from any macro argument interpolations, +.\"so we might confine such interpolations to one column of the table and +.\"apply the +.\".B x +.\"modifier to it. +.\". +.\". +.\".RS +.\".P +.\".EX +.\"\&.de END +.\"\&.. +.\"\&.eo +.\"\&.de MYTABLE END +.\"\&.TS +.\"\&allbox tab(;); +.\"\&C Lx. +.\"\&This is table \[rs]$1.;\[rs]$2 +.\"\&.TE +.\"\&.END +.\"\&.ec +.\"\&.MYTABLE 1 alpha +.\"\&.MYTABLE 2 beta +.\"\&.MYTABLE 3 "gamma delta" +.\".EE +.\".RE +.\" +.\" +.P +We can define a macro outside of a +.I tbl \" generic +region that we can call from within it to cause a page break inside a +multi-page boxed table. +. +You can choose a different name; +be sure to change both occurrences of \[lq]BP\[rq]. +. +. +.RS +.P +.ne 4v +.EX +\&.de BP +\&.\& ie \[aq]\[rs]\[rs]n(.z\[aq]\[aq] \&.bp \[rs]\[rs]$1 +\&.\& el \[rs]!.BP \[rs]\[rs]$1 +\&.. +.EE +.RE +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +\[lq]Tbl\[em]A Program to Format Tables\[rq], +by M.\& E.\& Lesk, +1976 +(revised 16 January 1979), +AT&T Bell Laboratories Computing Science Technical Report No.\& 49. +. +. +.P +The spanning example above was taken from +.UR https://man.openbsd.org/tbl.7 +.IR mandoc 's +man page for its +.I tbl \" mandoc +implementation +.UE . +. +. +.P +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_tbl_1_man_C] +.do rr *groff_tbl_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/preproc/tbl/tbl.am b/src/preproc/tbl/tbl.am new file mode 100644 index 0000000..d4b0edb --- /dev/null +++ b/src/preproc/tbl/tbl.am @@ -0,0 +1,54 @@ +# 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 += tbl +tbl_LDADD = libgroff.a $(LIBM) lib/libgnu.a +tbl_SOURCES = \ + src/preproc/tbl/main.cpp \ + src/preproc/tbl/table.cpp \ + src/preproc/tbl/table.h +PREFIXMAN1 += src/preproc/tbl/tbl.1 +EXTRA_DIST += src/preproc/tbl/tbl.1.man + +tbl_TESTS = \ + src/preproc/tbl/tests/boxes-and-vertical-rules.sh \ + src/preproc/tbl/tests/check-horizontal-line-length.sh \ + src/preproc/tbl/tests/check-line-intersections.sh \ + src/preproc/tbl/tests/check-vertical-line-length.sh \ + src/preproc/tbl/tests/cooperate-with-nm-request.sh \ + src/preproc/tbl/tests/count-continued-input-lines.sh \ + src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh \ + src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh \ + src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh \ + src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh \ + src/preproc/tbl/tests/expand-region-option-works.sh \ + src/preproc/tbl/tests/format-time-diagnostics-work.sh \ + src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh \ + src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh \ + src/preproc/tbl/tests/save-and-restore-line-numbering.sh \ + src/preproc/tbl/tests/save-and-restore-tab-stops.sh \ + src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh \ + src/preproc/tbl/tests/x-column-modifier-works.sh +TESTS += $(tbl_TESTS) +EXTRA_DIST += $(tbl_TESTS) + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/preproc/tbl/tests/boxes-and-vertical-rules.sh b/src/preproc/tbl/tests/boxes-and-vertical-rules.sh new file mode 100755 index 0000000..0471188 --- /dev/null +++ b/src/preproc/tbl/tests/boxes-and-vertical-rules.sh @@ -0,0 +1,174 @@ +#!/bin/sh +# +# Copyright (C) 2023 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Test behavior of unexpanded tables using boxes and/or vertical rules. + +# Case 1: "naked" table + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +L L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table without vertical rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '2p' \ + | grep -qx 'abcdef abcdef abcdef abcdef abcdef' || wail + +# Case 2: left-hand vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +| L L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with left-hand rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '3p' \ + | grep -qx '| abcdef abcdef abcdef abcdef abcdef' || wail + +# Case 3: right-hand vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +L L L L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with right-hand rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '3p' \ + | grep -qx 'abcdef abcdef abcdef abcdef abcdef |' || wail + +# Case 4: vertical rule on both ends + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +| L L L L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with both rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '3p' \ + | grep -qx '| abcdef abcdef abcdef abcdef abcdef |' || wail + +# Case 5: vertical rule on both ends and interior rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +| L L L | L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with both edge and interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '3p' \ + | grep -qx '| abcdef abcdef abcdef | abcdef abcdef |' || wail + +# Case 6: boxed table + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +box tab(@); +L L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking boxed table without interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '3p' \ + | grep -qx '| abcdef abcdef abcdef abcdef abcdef |' || wail + +# Case 7: boxed table with interior vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +box tab(@); +L L L | L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking boxed table with interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +# 3 spaces between table entries +echo "$output" | sed -n '3p' \ + | grep -qx '| abcdef abcdef abcdef | abcdef abcdef |' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/check-horizontal-line-length.sh b/src/preproc/tbl/tests/check-horizontal-line-length.sh new file mode 100755 index 0000000..3d5e2a2 --- /dev/null +++ b/src/preproc/tbl/tests/check-horizontal-line-length.sh @@ -0,0 +1,78 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" +fail= + +wail () { + echo "...FAILED" >&2 + echo "$output" + fail=yes +} + +# GNU tbl draws horizontal lines 1n wider than they need to be on nroff +# devices to enable them to cross a vertical line on the right-hand +# side. + +input='.ll 10n +.TS +L. +_ +1234567890 +.TE +.pl \n(nlu +' + +echo "checking length of plain horizontal rule" >&2 +output=$(printf "%s" "$input" | "$groff" -Tascii -t) +echo "$output" | grep -Eqx -- '-{11}' || wail + +input='.ll 12n +.TS +| L |. +_ +1234567890 +_ +.TE +.pl \n(nlu +' + +echo "checking intersection of vertical and horizontal rules" >&2 +output=$(printf "%s" "$input" | "$groff" -Tascii -t) +echo "$output" | sed -n '1p' | grep -Eqx '\+-{12}\+' || wail +echo "$output" | sed -n '3p' | grep -Eqx '\+-{12}\+' || wail + +input='.ll 12n +.TS +box; +L. +1234567890 +.TE +.pl \n(nlu +' + +echo "checking width of boxed table" >&2 +output=$(printf "%s" "$input" | "$groff" -Tascii -t) +echo "$output" | sed -n '1p' | grep -Eqx '\+-{12}\+' || wail +echo "$output" | sed -n '3p' | grep -Eqx '\+-{12}\+' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/check-line-intersections.sh b/src/preproc/tbl/tests/check-line-intersections.sh new file mode 100755 index 0000000..2012beb --- /dev/null +++ b/src/preproc/tbl/tests/check-line-intersections.sh @@ -0,0 +1,52 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" +fail= + +wail () { + echo "...FAILED" >&2 + echo "$output" + fail=yes +} + +input='.TS +allbox tab(@); +L L L. +a@b@c +d@e@f +g@h@i +.TE +' + +output=$(printf "%s" "$input" | "$groff" -Tascii -t) +echo "$output" + +for l in 1 3 5 7 +do + echo "checking intersections on line $l" + echo "$output" | sed -n ${l}p | grep -Fqx '+---+---+---+' || wail +done + +# TODO: Check `-Tutf8` output for correct crossing glyph identities. + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/check-vertical-line-length.sh b/src/preproc/tbl/tests/check-vertical-line-length.sh new file mode 100755 index 0000000..1aafd09 --- /dev/null +++ b/src/preproc/tbl/tests/check-vertical-line-length.sh @@ -0,0 +1,49 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" +fail= + +wail () { + echo "...FAILED" >&2 + echo "$output" + fail=yes +} + +# GNU tbl draws vertical lines 1v taller than they need to be on nroff +# devices to enable them to cross a potential horizontal line in the +# table. + +input='.ll 12n +.TS +| L |. +_ +1234567890 +.TE +.pl \n(nlu +' + +echo "checking length of plain vertical rule" >&2 +output=$(printf "%s" "$input" | "$groff" -Tascii -t) +echo "$output" | sed -n '2p' | grep -Fqx -- '| 1234567890 |' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/cooperate-with-nm-request.sh b/src/preproc/tbl/tests/cooperate-with-nm-request.sh new file mode 100755 index 0000000..cfbd750 --- /dev/null +++ b/src/preproc/tbl/tests/cooperate-with-nm-request.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# +# Copyright (C) 2021 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +set -e + +# Regression-test Savannah #59812. +# +# A nonzero value of \n[ln] should not cause spurious numbering of table +# rows. + +DOC='\ +.nf +foo +.nm 1 +bar +.nm +baz +.TS +l. +qux +.TE +' + +OUTPUT=$(printf "%s" "$DOC" | "$groff" -Tascii -t) + +echo "$OUTPUT" | grep -Fqx qux + +# vim:set ai noet sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/count-continued-input-lines.sh b/src/preproc/tbl/tests/count-continued-input-lines.sh new file mode 100755 index 0000000..4682788 --- /dev/null +++ b/src/preproc/tbl/tests/count-continued-input-lines.sh @@ -0,0 +1,43 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +set -e + +# Regression-test Savannah #62191. +# +# Line continuation in a row of table data should not make the input +# line counter inaccurate. + +input='.this-is-line-1 +.TS +L. +foo\ +bar\ +baz\ +qux +.TE +.this-is-line-9' + +output=$(printf "%s\n" "$input" | "$groff" -Tascii -t -ww -z 2>&1) +echo "$output" | grep -q '^troff.*:9:.*this-is-line-9' + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh b/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh new file mode 100755 index 0000000..c4698f1 --- /dev/null +++ b/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh @@ -0,0 +1,225 @@ +#!/bin/sh +# +# Copyright (C) 2022-2023 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" +grotty="${abs_top_builddir:-.}/grotty" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Regression-test Savannah #63449. +# +# In nroff mode, a table at the top of the page (i.e., one starting at +# the first possible output line, with no vertical margin) that has +# vertical rules should not overdraw the page top and provoke a warning +# from grotty about "character(s) above [the] first line [being] +# discarded". + +# Case 1: No horizontal rules; vertical rule at leading column. +input='.TS +| L. +foo +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (1)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that a lone vertical rule starts the first output line" +echo "$output" | sed -n '1p' | grep -Fqx '|' || wail + +# Case 2: No horizontal rules; vertical rule between columns. +input='.TS +tab(@); +L | L. +foo@bar +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (2)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that a lone vertical rule ends the first output line" +echo "$output" | sed -n '1p' | grep -Eqx ' +\|' || wail + +# Case 3: No horizontal rules; vertical rule at trailing column. +input='.TS +L |. +foo +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (3)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that a lone vertical rule ends the first output line" +echo "$output" | sed -n '1p' | grep -Eqx ' +\|' || wail + +# Case 4: Vertical rule with horizontal rule in row description. +input='.TS +_ +L |. +foo +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (4)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that intersection is placed on the first output line" +echo "$output" | sed -n '1p' | grep -q '+' || wail + +# Case 5: Vertical rule with horizontal rule as first table datum. +input='.TS +L |. +_ +foo +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (5)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that intersection is placed on the first output line" +echo "$output" | sed -n '1p' | grep -q '+' || wail + +# Case 6: Horizontal rule as non-first row description with vertical +# rule. +input='.TS +L,_,L |. +foo +bar +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (6)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that table data begin on first output line" +echo "$output" | sed -n '1p' | grep -q 'foo' || wail + +# Also ensure that no collateral damage arises in related cases. + +# Case 7: Horizontal rule as first table datum with no vertical rule. +input='.TS +L. +_ +foo +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (7)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that horizontal rule is placed on the first output line" +echo "$output" | sed -n '1p' | grep -q '^---' || wail + +# Case 8: Horizontal rule as last table datum with no vertical rule. +input='.TS +L. +foo +_ +.TE +.ec @ +.pl @n(nlu' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (8)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that horizontal rule is placed on the last output line" +echo "$output" | sed -n '$p' | grep -q '^---' || wail + +# Case 9: Horizontal rule in row description with no vertical rule. +input='.TS +_ +L. +foo +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (9)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that intersection is placed on the first output line" +echo "$output" | sed -n '1p' | grep -q '^---' || wail + +# Case 10: Horizontal rule as non-first row description with no vertical +# rule. +input='.TS +L,_,L. +foo +bar +.TE' + +tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou) +output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null) +error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null) +echo "$output" + +echo "checking that no diagnostic messages are produced by grotty (10)" +echo "$error" | grep -q 'grotty:' && wail + +echo "checking that table data begin on first output line" +echo "$output" | sed -n '1p' | grep -q 'foo' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh b/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh new file mode 100755 index 0000000..4f63eed --- /dev/null +++ b/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh @@ -0,0 +1,62 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo "...FAILED" >&2 + fail=yes +} + +# Regression-test Savannah #49390. + +input='foo +.TS +box; +L. +bar +.TE +baz +.pl \n(nlu +' + +echo "checking for post-table text non-overlap of (single) box border" +output=$(printf "%s" "$input" | "$groff" -t -Tascii) +echo "$output" | grep -q baz || wail + +input='foo +.TS +doublebox; +L. +bar +.TE +baz +.pl \n(nlu +' + +echo "checking for post-table text non-overlap of double box border" +output=$(printf "%s" "$input" | "$groff" -t -Tascii) +echo "$output" | grep -q baz || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh b/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh new file mode 100755 index 0000000..1d672ec --- /dev/null +++ b/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Copyright (C) 2021 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/>. +# + +tbl="${abs_top_builddir:-.}/tbl" + +# Regression-test Savannah #61417. +# +# Don't segfault because we tried to span down from an invalid span that +# tbl neglected to replace with an empty table entry. + +test -f core && exit 77 # skip + +input=$(cat <<EOF +.TS +l. +\^ +\^ +.TE +EOF +) +output=$(printf "%s" "$input" | "$tbl") +! test -f core + +# vim:set ai noet sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh b/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh new file mode 100755 index 0000000..30bd9f6 --- /dev/null +++ b/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh @@ -0,0 +1,75 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo "...FAILED" >&2 + fail=yes +} + +# Regression-test Savannah #62366. +# +# Do not SEGV when a text block begins with a repeating glyph token, and +# do not malformat the output if it ends with one. + +test -f core && exit 77 # skip + +input='.TS +L. +T{ +\Ra +T} +.TE +.TS +L. +T{ +foo +\Ra +T} +.TE +.TS +L. +T{ +foo +\Ra +bar +T} +.TE' + +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii -P-cbou) + +echo "checking that tbl doesn't segfault" >&2 +test -f core && wail + +echo "checking text block starting with repeating glyph" >&2 +echo "$output" | sed -n 1p | grep -qx 'a' || wail + +echo "checking text block ending with repeating glyph" >&2 +echo "$output" | sed -n 2p | grep -qx 'foo a' || wail + +echo "checking text block containing repeating glyph" >&2 +echo "$output" | sed -n 3p | grep -qx 'foo a bar' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/expand-region-option-works.sh b/src/preproc/tbl/tests/expand-region-option-works.sh new file mode 100755 index 0000000..97da39d --- /dev/null +++ b/src/preproc/tbl/tests/expand-region-option-works.sh @@ -0,0 +1,173 @@ +#!/bin/sh +# +# Copyright (C) 2023 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Ensure that the "expand" region option expands to the line length. + +# Case 1: "naked" table + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +expand tab(@); +L L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table without vertical rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '2p' \ + | grep -Eqx '(abcdef {8,9}){4}abcdef' || wail + +# Case 2: left-hand vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +expand tab(@); +| L L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with left-hand rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx '\| abcdef {8}abcdef {7}abcdef {8}abcdef {9}abcdef' \ + || wail + +# Case 3: right-hand vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +expand tab(@); +L L L L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with right-hand rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx 'abcdef {8}abcdef {7}abcdef {8}abcdef {9}abcdef \|' \ + || wail + +# Case 4: vertical rule on both ends + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +expand tab(@); +| L L L L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with both rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx '\| abcdef {7}abcdef {7}abcdef {8}abcdef {8}abcdef \|' \ + || wail + +# Case 5: vertical rule on both ends and interior rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +expand tab(@); +| L L L | L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with both edge and interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx \ + '\| abcdef {7}abcdef {7}abcdef {4}\| {3}abcdef {8}abcdef \|' || wail + +# Case 6: boxed table + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +box expand tab(@); +L L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking boxed table without interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx '\| abcdef {7}abcdef {7}abcdef {8}abcdef {8}abcdef \|' \ + || wail + +# Case 7: boxed table with interior vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +box expand tab(@); +L L L | L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking boxed table with interior rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx \ + '\| abcdef {7}abcdef {7}abcdef {4}\| {3}abcdef {8}abcdef \|' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/format-time-diagnostics-work.sh b/src/preproc/tbl/tests/format-time-diagnostics-work.sh new file mode 100755 index 0000000..9d422bd --- /dev/null +++ b/src/preproc/tbl/tests/format-time-diagnostics-work.sh @@ -0,0 +1,268 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +set -e + +# Ensure we get diagnostics when we expect to, and not when we don't. +# +# Do NOT pattern-match the text of the diagnostic messages; those should +# be left flexible. (Some day they might even be localized.) + +# As of this writing, there are 5 distinct format-time diagnostic +# messages that tbl writes roff code to generate, one of which can be +# produced two different ways. + +# Diagnostic #1: a row overruns the page bottom +input='.pl 2v +.TS +; +L. +T{ +.nf +1 +2 +3 +T} +.TE +' + +echo "checking for diagnostic when row with text box overruns page" \ + "bottom" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +echo "checking 'nowarn' suppression of diagnostic when row with text" \ + "box overruns page bottom" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +echo "checking 'nokeep' suppression of diagnostic when row with text" \ + "box overruns page bottom" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +# The other way to get "diagnostic #1" is to have a row that is too +# tall _without_ involving a text block, for instance by having a font +# or vertical spacing that is too high. +input='.pl 2v +.vs 3v +.TS +; +L. +1 +.TE +' + +echo "checking for diagnostic when row with large vertical spacing" \ + "overruns page bottom" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +echo "checking 'nowarn' suppression of diagnostic when row with large" \ + "vertical spacing overruns page bottom" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +echo "checking 'nokeep' suppression of diagnostic when row with large" \ + "vertical spacing overruns page bottom" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +# Diagnostic #2: a boxed table won't fit on a page + +input='.pl 2v +.vs 3v +.TS +box; +L. +1 +.TE +' + +echo "checking for diagnostic when boxed table won't fit on page" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +# The above is an error, so the "nowarn" region option won't shut it up. +# +# However, "nokeep" does--but arguably shouldn't. See +# <https://savannah.gnu.org/bugs/?61878>. If that gets fixed, we should +# test that we still get a diagnostic even with the option given. + +# Diagnostic #3: unexpanded columns overrun the line length +# +# Case 1: no 'x' column modifiers used + +input='.pl 2v +.ll 10n +.TS +; +L. +12345678901 +.TE +' + +echo "checking for diagnostic when unexpanded columns overrun line" \ + "length (1)" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +echo "checking 'nowarn' suppression of diagnostic when unexpanded" \ + "columns overrun line length (1)" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +# Avoiding keeps won't get you out of this one. +echo "checking 'nokeep' NON-suppression of diagnostic when unexpanded" \ + "columns overrun line length (1)" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +# Case 2: 'x' column modifier used +# +# This worked as a "get out of jail (warning) free" card in groff 1.22.4 +# and earlier; i.e., it incorrectly suppressed the warning. See +# Savannah #61854. + +input='.pl 2v +.ll 10n +.TS +; +Lx. +12345678901 +.TE +' + +echo "checking for diagnostic when unexpanded columns overrun line" \ + "length (2)" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +echo "checking 'nowarn' suppression of diagnostic when unexpanded" \ + "columns overrun line length (2)" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +# Avoiding keeps won't get you out of this one. +echo "checking 'nokeep' NON-suppression of diagnostic when unexpanded" \ + "columns overrun line length (2)" +input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +# Diagnostic #4: expanded table gets all column separation squashed out + +input='.pl 3v +.ll 10n +.TS +tab(;) expand; +L L. +abcde;fghij +.TE +' + +echo "checking for diagnostic when region-expanded table has column" \ + "separation eliminated" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +echo "checking 'nowarn' suppression of diagnostic when" \ + "region-expanded table has column separation eliminated" +input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nowarn;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +# Avoiding keeps won't get you out of this one. +echo "checking 'nokeep' NON-suppression of diagnostic when" \ + "region-expanded table has column separation eliminated" +input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nokeep;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +# Diagnostic #5: expanded table gets column separation reduced + +input='.pl 3v +.ll 10n +.TS +tab(;) expand; +L L. +abcd;efgh +.TE +' + +echo "checking for diagnostic when region-expanded table has column" \ + "separation reduced" +output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +echo "checking 'nowarn' suppression of diagnostic when" \ + "region-expanded table has column separation reduced" +input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nowarn;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 0 + +# Avoiding keeps won't get you out of this one. +echo "checking 'nokeep' NON-suppression of diagnostic when" \ + "region-expanded table has column separation reduced" +input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nokeep;/') +output=$(printf "%s" "$input_nowarn" \ + | "$groff" -Tascii -t 2>&1 >/dev/null) +nlines=$(echo "$output" | grep . | wc -l) +test $nlines -eq 1 + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh b/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh new file mode 100755 index 0000000..e368b31 --- /dev/null +++ b/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# +# Copyright (C) 2021 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +set -e + +# Regression-test Savannah #59971. +# +# Hyphenation needs to be restored between (and after) text blocks just +# as adjustment is. + +EXAMPLE='.nr LL 78n +.hw a-bc-def-ghij-klmno-pqrstu-vwxyz +.LP +Here is a table with hyphenation disabled in its text block. +. +.TS +l lx. +foo T{ +.nh +abcdefghijklmnopqrstuvwxyz +abcdefghijklmnopqrstuvwxyz +abcdefghijklmnopqrstuvwxyz +T} +.TE +. +Let us see if hyphenation is enabled again as it should be. +abcdefghijklmnopqrstuvwxyz' + +OUTPUT=$(printf "%s\n" "$EXAMPLE" | "$groff" -Tascii -P-cbou -t -ms) + +echo "$OUTPUT" + +echo "testing whether hyphenation disabled in table text block" >&2 +! echo "$OUTPUT" | grep '^foo' | grep -- '-$' + +echo "testing whether hyphenation enabled after table" >&2 +echo "$OUTPUT" | grep -qx 'Let us see.*lmno-' + +# vim:set ai noet sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh b/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh new file mode 100755 index 0000000..e9a06d8 --- /dev/null +++ b/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh @@ -0,0 +1,79 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Regression-test Savannah #61909. +# +# Inter-sentence space should not be applied to the content of ordinary +# table entries. They are set "rigidly" (tbl(1)), also without filling, +# adjustment, hyphenation or breaking. If you want those things, use a +# text block. + +input='.ss 12 120 +Before one. +Before two. +.TS +L. +.\" two spaces +Foo. Bar. +.\" four spaces +Baz. Qux. +.\" two spaces +T{ +Ack. Nak. +T} +.TE +After one. +After two. +' + +output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t) +echo "$output" + +echo "checking that inter-sentence space is altered too early" +echo "$output" \ + | grep -Fqx 'Before one. Before two.' || wail # 11 spaces + +echo "checking that inter-sentence space is not applied to ordinary" \ + "table entries (1)" +echo "$output" | grep -Fqx 'Foo. Bar.' || wail # 2 spaces + +echo "checking that inter-sentence space is not applied to ordinary" \ + "table entries (2)" +echo "$output" | grep -Fqx 'Baz. Qux.' || wail # 4 spaces + +echo "checking that inter-sentence space is applied to text blocks" +echo "$output" | grep -Fqx 'Ack. Nak.' || wail # 11 spaces + +echo "checking that inter-sentence space is restored after table" +echo "$output" \ + | grep -Fqx 'After one. After two.' || wail # 11 spaces + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/save-and-restore-line-numbering.sh b/src/preproc/tbl/tests/save-and-restore-line-numbering.sh new file mode 100755 index 0000000..592b43a --- /dev/null +++ b/src/preproc/tbl/tests/save-and-restore-line-numbering.sh @@ -0,0 +1,85 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Regression-test Savannah #60140. +# +# Line numbering needs to be suspended within a table and restored +# afterward. Historical implementations handled line numbering in +# tables badly when text blocks were used. + +input='.nm 1 +Here is a line of output. +Sic transit adispicing meatballs. +We pad it out with more content to ensure that the line breaks. +.TS +L. +This is my table. +There are many like it but this one is mine. +T{ +Ut enim ad minima veniam, +quis nostrum exercitationem ullam corporis suscipitlaboriosam, +nisi ut aliquid ex ea commodi consequatur? +T} +.TE +What is the line number now?' + +output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t) +echo "$output" + +echo "testing that line numbering is suppressed in table" >&2 +echo "$output" | grep -Fqx 'This is my table.' || wail + +echo "testing that line numbering is restored after table" >&2 +echo "$output" | grep -Eq '3 +What is the line number now\?' || wail + +input='.nf +.nm 1 +test of line numbering suppression +five +four +.nn 3 +three +.TS +L. +I am a table. +I have two rows. +.TE +two +one +numbering returns here' + +output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t) +echo "$output" + +echo "testing that suppressed numbering is restored correctly" >&2 +echo "$output" | grep -Eq '4 +numbering returns here' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/save-and-restore-tab-stops.sh b/src/preproc/tbl/tests/save-and-restore-tab-stops.sh new file mode 100755 index 0000000..b98922a --- /dev/null +++ b/src/preproc/tbl/tests/save-and-restore-tab-stops.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# +# Copyright (C) 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +# Regression-test Savannah #42978. +# +# When tbl changes the tab stops, it needs to restore them. +# +# Based on an example by Bjarni Igni Gislason. + +EXAMPLE='.TH tbl\-tabs\-test 1 2020-10-20 "groff test suite" +.SH Name +tbl\-tabs\-test \- see if tbl messes up the tab stops +.SH Description +Do not use tabs in man pages outside of +.BR .TS / .TE +regions. +.PP +But if you do.\|.\|. +.PP +.TS +l l l. +table entries long enough to change the tab stops +.TE +.PP +.EX +#!/bin/sh +case $# +1) + if foo + then + bar + else + if baz + then + qux + fi + fi +;; +esac +.EE' + +OUTPUT=$(printf "%s\n" "$EXAMPLE" | "$groff" -Tascii -P-cbou -t -man) +FAIL= + +if ! echo "$OUTPUT" | grep -Eq '^ {12}if foo$' +then + FAIL=yes + echo "first tab stop is wrong" >&2 +fi + +if ! echo "$OUTPUT" | grep -Eq '^ {17}bar$' +then + FAIL=yes + echo "second tab stop is wrong" >&2 +fi + +if ! echo "$OUTPUT" | grep -Eq '^ {22}qux$' +then + FAIL=yes + echo "third tab stop is wrong" >&2 +fi + +test -z "$FAIL" + +# vim:set ai noet sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh b/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh new file mode 100755 index 0000000..621a752 --- /dev/null +++ b/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh @@ -0,0 +1,56 @@ +#!/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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Regression-test Savannah #61878. +# +# A boxed, unkept table that overruns the page bottom will produce ugly +# output; it looks especially bizarre in nroff mode. +# +# We set the page length to 2v to force a problem (any boxed table in +# nroff mode needs 3 vees minimum), and put a page break at the start to +# catch an incorrectly initialized starting page number for the table. + +input='.pl 2v +.bp +.TS +box nokeep; +L. +Z +.TE' + +output=$(printf "%s" "$input" | "$groff" -t -Tascii 2>/dev/null) +error=$(printf "%s" "$input" | "$groff" -t -Tascii 2>&1 >/dev/null) +echo "$output" + +echo "checking that a diagnostic message is produced" +echo "$error" | grep -q 'warning: boxed.*page 2$' || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/preproc/tbl/tests/x-column-modifier-works.sh b/src/preproc/tbl/tests/x-column-modifier-works.sh new file mode 100755 index 0000000..da9b890 --- /dev/null +++ b/src/preproc/tbl/tests/x-column-modifier-works.sh @@ -0,0 +1,172 @@ +#!/bin/sh +# +# Copyright (C) 2023 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/>. +# + +groff="${abs_top_builddir:-.}/test-groff" + +fail= + +wail () { + echo ...FAILED >&2 + fail=YES +} + +# Ensure that the "x" column modifier causes table expansion to the line +# length. + +# Case 1: "naked" table + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +Lx L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table without vertical rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '2p' \ + | grep -Eqx 'abcdef {25}(abcdef {3}){3}abcdef' || wail + +# Case 2: left-hand vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +| Lx L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with left-hand rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx '\| abcdef {23}(abcdef {3}){3}abcdef' || wail + +# Case 3: right-hand vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +Lx L L L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with right-hand rule" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx 'abcdef {23}(abcdef {3}){3}abcdef \|' || wail + +# Case 4: vertical rule on both ends + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +| Lx L L L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with both rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx '\| abcdef {21}(abcdef {3}){3}abcdef \|' || wail + +# Case 5: vertical rule on both ends and interior rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +tab(@); +| Lx L L | L L |. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking unboxed table with both edge and interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx \ + '\| abcdef {21}abcdef {3}abcdef \| abcdef {3}abcdef \|' \ + || wail + +# Case 6: boxed table + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +box tab(@); +Lx L L L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking boxed table without interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx '\| abcdef {21}(abcdef {3}){3}abcdef \|' || wail + +# Case 7: boxed table with interior vertical rule + +input='.ll 64n +.nf +1234567890123456789012345678901234567890123456789012345678901234 +.fi +.TS +box tab(@); +Lx L L | L L. +abcdef@abcdef@abcdef@abcdef@abcdef +.TE +.pl \n(nlu' + +echo "checking boxed table with interior rules" >&2 +output=$(printf "%s\n" "$input" | "$groff" -t -Tascii) +echo "$output" +echo "$output" | sed -n '3p' \ + | grep -Eqx \ + '\| abcdef {21}abcdef {3}abcdef \| abcdef {3}abcdef \|' \ + || wail + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: |