/* 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:
