// -*- 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #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(""); else if (char_table[c].spacing_type) printf(""); else printf(""); if (c == '<') printf("<"); else if (c == '>') printf(">"); else if (c == '&') printf("&"); else putchar(c); if (isdigit(c)) printf(""); else if (char_table[c].spacing_type) printf(""); else printf(""); } } 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("%s", entity); else printf("unknown eqn/troff special char %s", 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); }