// -*- C++ -*- /* Copyright (C) 1989, 1990, 1991, 1992 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 2, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "eqn.h" #include "eqn.tab.h" #include "stringclass.h" #include "ptable.h" 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) a_delete 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, }; static struct { const char *name; const char *def; } def_table[] = { "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\"}", "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}", "times", "type \"binary\" \\(mu", "ldots", "type \"inner\" { . . . }", "inf", "\\(if", "partial", "\\(pd", "nothing", "\"\"", "half", "{1 smallover 2}", "hat_def", "roman \"^\"", "hat", "accent { hat_def }", "dot_def", "back 15 \"\\v'-52M'.\\v'52M'\"", "dot", "accent { dot_def }", "dotdot_def", "back 25 \"\\v'-52M'..\\v'52M'\"", "dotdot", "accent { dotdot_def }", "tilde_def", "\"~\"", "tilde", "accent { tilde_def }", "utilde_def", "\"\\v'75M'~\\v'-75M'\"", "utilde", "uaccent { utilde_def }", "vec_def", "up 52 size -5 \\(->", "vec", "accent { vec_def }", "dyad_def", "up 52 size -5 {\\(<- back 60 \\(->}", "dyad", "accent { dyad_def }", "==", "type \"relation\" \\(==", "!=", "type \"relation\" \\(!=", "+-", "type \"binary\" \\(+-", "->", "type \"relation\" \\(->", "<-", "type \"relation\" \\(<-", "<<", "{ < back 20 < }", ">>", "{ > back 20 > }", "...", "type \"inner\" vcenter { . . . }", "prime", "'", "approx", "type \"relation\" \"\\(~=\"", "grad", "\\(gr", "del", "\\(gr", "cdot", "type \"binary\" vcenter .", "dollar", "$", }; void init_table(const char *device) { int i; for (i = 0; i < sizeof(token_table)/sizeof(token_table[0]); i++) { definition *def = new definition; def->is_macro = 0; def->tok = token_table[i].token; macro_table.define(token_table[i].name, def); } for (i = 0; i < sizeof(def_table)/sizeof(def_table[0]); i++) { definition *def = new definition; def->is_macro = 1; def->contents = strsave(def_table[i].def); def->is_simple = 1; macro_table.define(def_table[i].name, def); } definition *def = new definition; 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() { a_delete filename; fclose(fp); } int file_input::read_line() { for (;;) { line.clear(); lineno++; for (;;) { int c = getc(fp); if (c == EOF) break; else if (illegal_input_char(c)) lex_error("illegal 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() { a_delete 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() { a_delete 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 respresenting $1. Must be illegal input character. #define ARG1 14 argument_macro_input::argument_macro_input(const char *body, int ac, char **av, input *x) : input(x), argc(ac), ap(0) { 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++) a_delete argv[i]; a_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; int lineno; int got_location = get_location(&filename, &lineno); int start = get_char(); while (start == ' ' || start == '\t' || start == '\n') start = get_char(); token_buffer.clear(); if (start == EOF) { if (got_location) error_with_file_and_line(filename, lineno, "end of input while defining macro"); else error("end of input while defining macro"); return; } for (;;) { int c = get_char(); if (c == EOF) { if (got_location) error_with_file_and_line(filename, lineno, "end of input while defining macro"); else error("end of input while defining macro"); add_context(start + token_buffer); return; } if (c == start) break; token_buffer += char(c); } add_context(start + token_buffer + start); } 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; macro_table.define(name, def); } else if (def->is_macro) { a_delete 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"); 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); } } 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 = end_delim = '\0'; } 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(); }