// -*- C++ -*-
/* Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.uucp)

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 1, 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 LICENSE.  If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "config.h"
#include "troff.h"
#include "symbol.h"
#include "dictionary.h"
#include "hvunits.h"
#include "env.h"
#include "request.h"
#include "node.h"
#include "reg.h"
#include "token.h"
#include "div.h"
#include "charinfo.h"
#include "font.h"

#define MACROPATH_ENVVAR "GROFF_TMAC_PATH"

#define DEFAULT_INPUT_STACK_LIMIT 1000

#ifndef DEFAULT_WARNING_MASK
// warnings that are enabled by default
#define DEFAULT_WARNING_MASK (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE)
#endif

// initial size of buffer for reading names; expanded as necessary
#define ABUF_SIZE 16

#ifdef COLUMN
void init_column_requests();
#endif /* COLUMN */

static node *read_draw_node();
void handle_first_page_transition();
void copy_file();
#ifdef COLUMN
void vjustify();
#endif /* COLUMN */
void transparent();
void transparent_file();

const char *program_name = 0;
token tok;
const char *device = DEVICE;
int break_flag = 0;
static int backtrace_flag = 0;
char *pipe_command = 0;
charinfo *charset_table[256];

static int warning_mask = DEFAULT_WARNING_MASK;
static int inhibit_errors = 0;

static void enable_warning(const char *);
static void disable_warning(const char *);

static int escape_char = '\\';
static symbol end_macro_name;
static int compatible_flag = 0;
static void process_input_stack();
int ascii_output_flag = 0;
int suppress_output_flag = 0;

int tcommand_flag = 0;

static int get_copy(node**, int = 0);
static symbol read_escape_name();
static void interpolate_string(symbol);
static void interpolate_macro(symbol);
static void interpolate_number_format(symbol);

static void interpolate_arg(symbol);
static request_or_macro *lookup_request(symbol);
static int get_delim_number(units *, int);
static int get_delim_number(units *, int, units);
static int get_line_arg(units *res, int si, charinfo **cp);
static int read_size(int *);
static symbol get_delim_name();
static void init_registers();

struct input_iterator;
input_iterator *make_temp_iterator(const char *);
const char *input_char_description(int);

const int ESCAPE_RIGHT_PARENTHESIS = 014;
const int ESCAPE_QUESTION = 015;
const int BEGIN_TRAP = 016;
const int END_TRAP = 017;
const int PAGE_EJECTOR = 020;
const int ESCAPE_NEWLINE = 021;
const int ESCAPE_AMPERSAND = 022;
const int ESCAPE_UNDERSCORE = 023;
const int ESCAPE_BAR = 024;
const int ESCAPE_CIRCUMFLEX = 025;
const int ESCAPE_LEFT_BRACE = 026;
const int ESCAPE_RIGHT_BRACE = 027;
const int ESCAPE_LEFT_QUOTE = 030;
const int ESCAPE_RIGHT_QUOTE = 031;
const int ESCAPE_HYPHEN = 032;
const int ESCAPE_BANG = 033;
const int ESCAPE_c = 034;
const int ESCAPE_e = 035;
const int ESCAPE_PERCENT = 036;
const int ESCAPE_SPACE = 037;

const int TITLE_REQUEST = 0200;
const int COPY_FILE_REQUEST = 0201;
const int TRANSPARENT_FILE_REQUEST = 0202;
#ifdef COLUMN
const int VJUSTIFY_REQUEST = 0203;
#endif /* COLUMN */
const int ESCAPE_E = 0204;

void set_escape_char()
{
  if (!has_arg())
    escape_char = '\\';
  else if (!tok.ch())
    error("bad escape character");
  else
    escape_char = tok.ch();
  skip_line();
}

void escape_off()
{
  escape_char = 0;
  skip_line();
}

class input_iterator {
public:
  input_iterator();
  virtual ~input_iterator();
  int get(node **);
  friend class input_stack;
protected:
  const unsigned char *ptr;
  const unsigned char *eptr;
  input_iterator *next;
private:
  virtual int fill(node **);
  virtual int peek();
  virtual int has_args() { return 0; }
  virtual int nargs() { return 0; }
  virtual input_iterator *get_arg(int) { return NULL; }
  virtual int get_location(int, const char **,  int *)
    { return 0; }
  virtual void backtrace() {}
  virtual int set_location(const char *, int)
    { return 0; }
  virtual int next_file(FILE *, const char *) { return 0; }
  virtual void shift(int) {}
  virtual int is_boundary();
  virtual int internal_level() { return 0; }
};

input_iterator::input_iterator()
: ptr(0), eptr(0)
{
}

input_iterator::~input_iterator()
{
}

int input_iterator::fill(node **)
{
  return EOF;
}

int input_iterator::peek()
{
  return EOF;
}

int input_iterator::is_boundary()
{
  return 0;
}

inline int input_iterator::get(node **p)
{
  return ptr < eptr ? *ptr++ : fill(p);
}


class input_boundary : public input_iterator {
public:
  int is_boundary() { return 1; }
};

class file_iterator : public input_iterator {
  FILE *fp;
  int lineno;
  const char *filename;
  int newline_flag;
  enum { BUF_SIZE = 512 };
  unsigned char buf[BUF_SIZE];
public:
  file_iterator(FILE *, const char *);
  ~file_iterator();
  int fill(node **);
  int peek();
  int get_location(int, const char **, int *);
  void backtrace();
  int set_location(const char *, int);
  int next_file(FILE *, const char *);
};

file_iterator::file_iterator(FILE *f, const char *fn)
: fp(f), filename(fn), lineno(1), newline_flag(0)
{
}

file_iterator::~file_iterator()
{
  if (fp != stdin)
    fclose(fp);
  else
    clearerr(stdin);
}

int file_iterator::next_file(FILE *f, const char *s)
{
  if (fp != stdin)
    fclose(fp);
  else
    clearerr(stdin);
  filename = s;
  fp = f;
  lineno = 1;
  newline_flag = 0;
  ptr = 0;
  eptr = 0;
  return 1;
}

int file_iterator::fill(node **)
{
  if (newline_flag)
    lineno++;
  newline_flag = 0;
  unsigned char *p = buf;
  ptr = p;
  unsigned char *e = p + BUF_SIZE;
  while (p < e) {
    int c = getc(fp);
    if (c == EOF)
      break;
    if (illegal_input_char(c))
      warning(WARN_INPUT, "illegal input character code %1", int(c));
    else {
      *p++ = c;
      if (c == '\n') {
	newline_flag = 1;
	break;
      }
    }
  }
  if (p > buf) {
    eptr = p;
    return *ptr++;
  }
  else {
    eptr = p;
    return EOF;
  }
}

int file_iterator::peek()
{
  int c = getc(fp);
  while (illegal_input_char(c)) {
    warning(WARN_INPUT, "illegal input character code %1", int(c));
    c = getc(fp);
  }
  if (c != EOF)
    ungetc(c, fp);
  return c;
}

int file_iterator::get_location(int /*allow_macro*/,
				const char **filenamep, int *linenop)
{
  *linenop = lineno;
  if (filename != 0 && strcmp(filename, "-") == 0)
    *filenamep = "<standard input>";
  else
    *filenamep = filename;
  return 1;
}

void file_iterator::backtrace()
{
  errprint("%1:%2: backtrace: file `%1'\n", filename, lineno);
}

int file_iterator::set_location(const char *f, int ln)
{
  if (f)
    filename = f;
  lineno = ln;
  return 1;
}

input_iterator nil_iterator;  

class input_stack {
public:
  static int get(node **);
  static int peek();
  static void push(input_iterator *);
  static input_iterator *get_arg(int);
  static int nargs();
  static int get_location(int, const char **, int *);
  static int set_location(const char *, int);
  static void backtrace();
  static void backtrace_all();
  static void next_file(FILE *, const char *);
  static void shift(int n);
  static void add_boundary();
  static void remove_boundary();
  static int get_level();
  static void clear();

  static int limit;
private:
  static input_iterator *top;
  static int level;

  static int finish_get(node **);
  static int finish_peek();
};

input_iterator *input_stack::top = &nil_iterator;
int input_stack::level = 0;
int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;

inline int input_stack::get_level()
{
  return level + top->internal_level();
}

inline int input_stack::get(node **np)
{
  return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
}

int input_stack::finish_get(node **np)
{
  for (;;) {
    int c = top->fill(np);
    if (c != EOF || top->is_boundary())
      return c;
    if (top == &nil_iterator)
      break;
    input_iterator *tem = top;
    top = top->next;
    level--;
    delete tem;
    if (top->ptr < top->eptr)
      return *top->ptr++;
  }
  assert(level == 0);
  return EOF;
}

inline int input_stack::peek()
{
  return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
}

int input_stack::finish_peek()
{
  for (;;) {
    int c = top->peek();
    if (c != EOF || top->is_boundary())
      return c;
    if (top == &nil_iterator)
      break;
    input_iterator *tem = top;
    top = top->next;
    level--;
    delete tem;
    if (top->ptr < top->eptr)
      return *top->ptr;
  }
  assert(level == 0);
  return EOF;
}

void input_stack::add_boundary()
{
  push(new input_boundary);
}

void input_stack::remove_boundary()
{
  assert(top->is_boundary());
  input_iterator *temp = top->next;
  delete top;
  top = temp;
  level--;
}

void input_stack::push(input_iterator *in)
{
  if (in == 0)
    return;
  if (++level > limit && limit > 0)
    fatal("input stack limit exceeded");
  in->next = top;
  top = in;
}

input_iterator *input_stack::get_arg(int i)
{
  input_iterator *p;
  for (p = top; p != NULL; p = p->next)
    if (p->has_args())
      return p->get_arg(i);
  return 0;
}

void input_stack::shift(int n)
{
  for (input_iterator *p = top; p; p = p->next)
    if (p->has_args()) {
      p->shift(n);
      return;
    }
}

int input_stack::nargs()
{
  for (input_iterator *p =top; p != 0; p = p->next)
    if (p->has_args())
      return p->nargs();
  return 0;
}

int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
{
  for (input_iterator *p = top; p; p = p->next)
    if (p->get_location(allow_macro, filenamep, linenop))
      return 1;
  return 0;
}

void input_stack::backtrace()
{
  char *f;
  int n;
  // only backtrace down to (not including) the topmost file
  for (input_iterator *p = top;
       p && !p->get_location(0, &f, &n);
       p = p->next)
    p->backtrace();
}

void input_stack::backtrace_all()
{
  for (input_iterator *p = top; p; p = p->next)
    p->backtrace();
}

int input_stack::set_location(const char *filename, int lineno)
{
  for (input_iterator *p = top; p; p = p->next)
    if (p->set_location(filename, lineno))
      return 1;
  return 0;
}

void input_stack::next_file(FILE *fp, const char *s)
{
  for (input_iterator *p = top; p; p = p->next)
    if (p->next_file(fp, s))
      return;
  for (input_iterator **pp = &top; *pp; pp = &(*pp)->next)
    ;
  *pp = new file_iterator(fp, s);
}

void input_stack::clear()
{
  while (!top->is_boundary() && top != &nil_iterator) {
    input_iterator *tem = top;
    top = top->next;
    level--;
    delete tem;
  }
}

void backtrace_request()
{
  input_stack::backtrace_all();
  fflush(stderr);
  skip_line();
}

void next_file()
{
  symbol nm = get_long_name(1);
  while (!tok.newline() && !tok.eof())
    tok.next();
  if (!nm.is_null()) {
    errno = 0;
    FILE *fp = fopen(nm.contents(), "r");
    if (!fp)
      error("can't open `%1': %2", nm.contents(), strerror(errno));
    else
      input_stack::next_file(fp, nm.contents());
  }
  tok.next();
}

void shift()
{
  int n = 1;
  if (!has_arg() || get_integer(&n))
    input_stack::shift(n);
  skip_line();
}

static int get_char_for_escape_name()
{
  int c = get_copy(NULL);
  switch (c) {
  case EOF:
    error("end of input in escape name");
    return '\0';
  default:
    if (!illegal_input_char(c))
      break;
    // fall through
  case ' ':
  case '\n':
  case '\t':
  case '\001':
  case '\b':
    error("%1 is not allowed in an escape name", input_char_description(c));
    return '\0';
  }
  return c;
}

static symbol read_two_char_escape_name()
{
  char buf[3];
  buf[0] = get_char_for_escape_name();
  if (buf[0] != '\0') {
    buf[1] = get_char_for_escape_name();
    if (buf[1] == '\0')
      buf[0] = 0;
    else
      buf[2] = 0;
  }
  return symbol(buf);
}

static symbol read_long_escape_name()
{
  int start_level = input_stack::get_level();
  char abuf[ABUF_SIZE];
  char *buf = abuf;
  int buf_size = ABUF_SIZE;
  int i = 0;
  for (;;) {
    int c = get_char_for_escape_name();
    if (c == 0) {
      if (buf != abuf)
	delete buf;
      return NULL_SYMBOL;
    }
    if (i + 2 > buf_size) {
      if (buf == abuf) {
	buf = new char [ABUF_SIZE*2];
	memcpy(buf, abuf, buf_size);
	buf_size = ABUF_SIZE*2;
      }
      else {
	char *old_buf = buf;
	buf = new char[buf_size*2];
	memcpy(buf, old_buf, buf_size);
	buf_size *= 2;
	delete old_buf;
      }
    }
    if (c == ']' && input_stack::get_level() == start_level)
      break;
    buf[i++] = c;
  }
  buf[i] = 0;
  if (buf == abuf) {
    if (i == 0) {
      error("empty escape name");
      return NULL_SYMBOL;
    }
    return symbol(abuf);
  }
  else {
    symbol s(buf);
    delete buf;
    return s;
  }
}

static symbol read_escape_name()
{
  int c = get_char_for_escape_name();
  if (c == 0)
    return NULL_SYMBOL;
  if (c == '(')
    return read_two_char_escape_name();
  if (c == '[' && !compatible_flag)
    return read_long_escape_name();
  char buf[2];
  buf[0] = c;
  buf[1] = '\0';
  return symbol(buf);
}

static symbol read_increment_and_escape_name(int *incp)
{
  int c = get_char_for_escape_name();
  switch (c) {
  case 0:
    *incp = 0;
    return NULL_SYMBOL;
  case '(':
    *incp = 0;
    return read_two_char_escape_name();
  case '+':
    *incp = 1;
    return read_escape_name();
  case '-':
    *incp = -1;
    return read_escape_name();
  case '[':
    if (!compatible_flag) {
      *incp = 0;
      return read_long_escape_name();
    }
    break;
  }
  *incp = 0;
  char buf[2];
  buf[0] = c;
  buf[1] = '\0';
  return symbol(buf);
}

static int get_copy(node **nd, int defining)
{
  for (;;) {
    int c = input_stack::get(nd);
    if (c == ESCAPE_NEWLINE) {
      if (defining)
	return c;
      do {
	c = input_stack::get(nd);
      } while (c == ESCAPE_NEWLINE);
    }
    if (c != escape_char || escape_char <= 0)
      return c;
    c = input_stack::peek();
    switch(c) {
    case 0:
      return escape_char;
    case '"':
      (void)input_stack::get(NULL);
      while ((c = input_stack::get(NULL)) != '\n' && c != EOF)
	;
      return c;
    case '$':
      {
	(void)input_stack::get(NULL);
	symbol s = read_escape_name();
	if (!s.is_null())
	  interpolate_arg(s);
	break;
      }
    case '*':
      {
	(void)input_stack::get(NULL);
	symbol s = read_escape_name();
	if (!s.is_null())
	  interpolate_string(s);
	break;
      }
    case 'a':
      (void)input_stack::get(NULL);
      return '\001';
    case 'e':
      (void)input_stack::get(NULL);
      return ESCAPE_e;
    case 'E':
      (void)input_stack::get(NULL);
      return ESCAPE_E;
    case 'n':
      {
	(void)input_stack::get(NULL);
	int inc;
	symbol s = read_increment_and_escape_name(&inc);
	if (!s.is_null())
	  interpolate_number_reg(s, inc);
	break;
      }
    case 'g':
      {
        (void)input_stack::get(NULL);
        symbol s = read_escape_name();
	if (!s.is_null())
	  interpolate_number_format(s);
        break;
      }
    case 't':
      (void)input_stack::get(NULL);
      return '\t';
    case '\n':
      (void)input_stack::get(NULL);
      if (defining)
	return ESCAPE_NEWLINE;
      break;
    case ' ':
      (void)input_stack::get(NULL);
      return ESCAPE_SPACE;
    case '|':
      (void)input_stack::get(NULL);
      return ESCAPE_BAR;
    case '^': 
      (void)input_stack::get(NULL);
      return ESCAPE_CIRCUMFLEX;
    case '{':
      (void)input_stack::get(NULL);
      return ESCAPE_LEFT_BRACE;
    case '}':
      (void)input_stack::get(NULL);
      return ESCAPE_RIGHT_BRACE;
    case '`':
      (void)input_stack::get(NULL);
      return ESCAPE_LEFT_QUOTE;
    case '\'':
      (void)input_stack::get(NULL);
      return ESCAPE_RIGHT_QUOTE;
    case '-':
      (void)input_stack::get(NULL);
      return ESCAPE_HYPHEN;
    case '_':
      (void)input_stack::get(NULL);
      return ESCAPE_UNDERSCORE;
    case 'c':
      (void)input_stack::get(NULL);
      return ESCAPE_c;
    case '!':
      (void)input_stack::get(NULL);
      return ESCAPE_BANG;
    case '?':
      (void)input_stack::get(NULL);
      return ESCAPE_QUESTION;
    case '&':
      (void)input_stack::get(NULL);
      return ESCAPE_AMPERSAND;
    case ')':
      (void)input_stack::get(NULL);
      return ESCAPE_RIGHT_PARENTHESIS;
    case '.':
      (void)input_stack::get(NULL);
      return c;			
    case '%':
      (void)input_stack::get(NULL);
      return ESCAPE_PERCENT;
    default:
      if (c == escape_char) {
	(void)input_stack::get(NULL);
	return c;
      }
      else
	return escape_char;
    }
  }
}

class non_interpreted_char_node : public node {
  unsigned char c;
public:
  non_interpreted_char_node(unsigned char);
  node *copy();
  int interpret(macro *);
  int same(node *);
  const char *type();
};

int non_interpreted_char_node::same(node *nd)
{
  return c == ((non_interpreted_char_node *)nd)->c;
}

const char *non_interpreted_char_node::type()
{
  return "non_interpreted_char_node";
}

non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
{
  assert(n != 0);
}

node *non_interpreted_char_node::copy()
{
  return new non_interpreted_char_node(c);
}

int non_interpreted_char_node::interpret(macro *mac)
{
  mac->append(c);
  return 1;
}

static void do_width();
static node *do_non_interpreted();
static node *do_special();
static void do_register();

static node *do_overstrike()
{
  token start;
  overstrike_node *on = new overstrike_node;
  start.next();
  tok.next();
  while (tok != start) {
    if (tok.newline() || tok.eof()) {
      warning(WARN_DELIM, "missing closing delimiter");
      break;
    }
    if (tok.changes_env())
      tok.process();
    else {
      charinfo *ci = tok.get_char(1);
      if (ci) {
	node *n = curenv->make_char_node(ci);
	if (n)
	  on->overstrike(n);
      }
    }
    tok.next();
  }
  return on;
}

static node *do_bracket()
{
  token start;
  bracket_node *bn = new bracket_node;
  start.next();
  tok.next();
  while (tok != start) {
    if (tok.eof()) {
      warning(WARN_DELIM, "missing closing delimiter");
      break;
    }
    if (tok.newline()) {
      warning(WARN_DELIM, "missing closing delimiter");
      input_stack::push(make_temp_iterator("\n"));
      break;
    }
    if (tok.changes_env())
      tok.process();
    else {
      charinfo *ci = tok.get_char(1);
      if (ci) {
	node *n = curenv->make_char_node(ci);
	if (n)
	  bn->bracket(n);
      }
    }
    tok.next();
  }
  return bn;
}

static int do_name_test()
{
  token start;
  start.next();
  int start_level = input_stack::get_level();
  int bad_char = 0;
  int some_char = 0;
  for (;;) {
    tok.next();
    if (tok.newline() || tok.eof()) {
      warning(WARN_DELIM, "missing closing delimiter");
      break;
    }
    if (tok == start
	&& (compatible_flag || input_stack::get_level() == start_level))
      break;
    if (!tok.ch())
      bad_char = 1;
    some_char = 1;
  }
  return some_char && !bad_char;
}

#if 0
static node *do_zero_width()
{
  token start;
  start.next();
  int start_level = input_stack::get_level();
  environment env(curenv);
  environment *oldenv = curenv;
  curenv = &env;
  for (;;) {
    tok.next();
    if (tok.newline() || tok.eof()) {
      error("missing closing delimiter");
      break;
    }
    if (tok == start
	&& (compatible_flag || input_stack::get_level() == start_level))
      break;
    tok.process();
  }
  curenv = oldenv;
  node *rev = env.extract_output_line();
  node *n = 0;
  while (rev) {
    node *tem = rev;
    rev = rev->next;
    tem->next = n;
    n = tem;
  }
  return new zero_width_node(n);
}  

#else

// It's undesirable for \Z to change environments, because then
// \n(.w won't work as expected.

static node *do_zero_width()
{
  node *rev = new dummy_node;
  token start;
  start.next();
  int start_level = input_stack::get_level();
  for (;;) {
    tok.next();
    if (tok.newline() || tok.eof()) {
      warning(WARN_DELIM, "missing closing delimiter");
      break;
    }
    if (tok == start
	&& (compatible_flag || input_stack::get_level() == start_level))
      break;
    if (!tok.add_to_node_list(&rev))
      error("illegal token in argument to \\Z");
  }
  node *n = 0;
  while (rev) {
    node *tem = rev;
    rev = rev->next;
    tem->next = n;
    n = tem;
  }
  return new zero_width_node(n);
}

#endif

token_node *node::get_token_node()
{
  return 0;
}

class token_node : public node {
public:
  token tk;
  token_node(const token &t);
  node *copy();
  token_node *get_token_node();
  int same(node *);
  const char *type();
};

token_node::token_node(const token &t) : tk(t)
{
}

node *token_node::copy()
{
  return new token_node(tk);
}

token_node *token_node::get_token_node()
{
  return this;
}

int token_node::same(node *nd)
{
  return tk == ((token_node *)nd)->tk;
}

const char *token_node::type()
{
  return "token_node";
}

token::token() : nd(0), type(TOKEN_EMPTY)
{
}

token::~token()
{
  delete nd;
}

token::token(const token &t)
: nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
{
  nd = t.nd ? t.nd->copy() : 0;
}

void token::operator=(const token &t)
{
  delete nd;
  nm = t.nm;
  nd = t.nd ? t.nd->copy() : 0;
  c = t.c;
  val = t.val;
  dim = t.dim;
  type = t.type;
}

void token::skip()
{
  while (space())
    next();
}

int has_arg()
{
  while (tok.space())
    tok.next();
  return !tok.newline();
}

void token::make_space()
{
  type = TOKEN_SPACE;
}

void token::make_newline()
{
  type = TOKEN_NEWLINE;
}

void token::next()
{
  if (nd) {
    delete nd;
    nd = 0;
  }
  units x;
  for (;;) {
    node *n;
    int cc = input_stack::get(&n);
    if (cc != escape_char || escape_char == 0) {
    handle_normal_char:
      switch(cc) {
      case EOF:
	type = TOKEN_EOF;
	return;
      case TRANSPARENT_FILE_REQUEST:
      case TITLE_REQUEST:
      case COPY_FILE_REQUEST:
#ifdef COLUMN
      case VJUSTIFY_REQUEST:
#endif /* COLUMN */
	type = TOKEN_REQUEST;
	c = cc;
	return;
      case BEGIN_TRAP:
	type = TOKEN_BEGIN_TRAP;
	return;
      case END_TRAP:
	type = TOKEN_END_TRAP;
	return;
      case PAGE_EJECTOR:
	type = TOKEN_PAGE_EJECTOR;
	return;
      case ESCAPE_PERCENT:
      ESCAPE_PERCENT:
	type = TOKEN_HYPHEN_INDICATOR;
	return;
      case ESCAPE_SPACE:
      ESCAPE_SPACE:
	type = TOKEN_NODE;
	nd = new space_char_hmotion_node(curenv->get_space_width());
	return;
      case ESCAPE_e:
      ESCAPE_e:
	type = TOKEN_ESCAPE;
	return;
      case ESCAPE_E:
	goto handle_escape_char;
      case ESCAPE_BAR:
      ESCAPE_BAR:
	type = TOKEN_NODE;
	nd = new hmotion_node(curenv->get_narrow_space_width());
	return;
      case ESCAPE_CIRCUMFLEX:
      ESCAPE_CIRCUMFLEX:
	type = TOKEN_NODE;
	nd = new hmotion_node(curenv->get_half_narrow_space_width());
	return;
      case ESCAPE_NEWLINE:
	break;
      case ESCAPE_LEFT_BRACE:
      ESCAPE_LEFT_BRACE:
	type = TOKEN_LEFT_BRACE;
	return;
      case ESCAPE_RIGHT_BRACE:
      ESCAPE_RIGHT_BRACE:
	type = TOKEN_RIGHT_BRACE;
	return;
      case ESCAPE_LEFT_QUOTE:
      ESCAPE_LEFT_QUOTE:
	type = TOKEN_SPECIAL;
	nm = symbol("ga");
	return;
      case ESCAPE_RIGHT_QUOTE:
      ESCAPE_RIGHT_QUOTE:
	type = TOKEN_SPECIAL;
	nm = symbol("aa");
	return;
      case ESCAPE_HYPHEN:
      ESCAPE_HYPHEN:
	type = TOKEN_SPECIAL;
	nm = symbol("-");
	return;
      case ESCAPE_UNDERSCORE:
      ESCAPE_UNDERSCORE:
	type = TOKEN_SPECIAL;
	nm = symbol("ul");
	return;
      case ESCAPE_c:
      ESCAPE_c:
	type = TOKEN_INTERRUPT;
	return;
      case ESCAPE_BANG:
      ESCAPE_BANG:
	type = TOKEN_TRANSPARENT;
	return;
      case ESCAPE_QUESTION:
      ESCAPE_QUESTION:
	nd = do_non_interpreted();
	if (nd) {
	  type = TOKEN_NODE;
	  return;
	}
	break;
      case ESCAPE_AMPERSAND:
      ESCAPE_AMPERSAND:
	type = TOKEN_DUMMY;
	return;
      case ESCAPE_RIGHT_PARENTHESIS:
      ESCAPE_RIGHT_PARENTHESIS:
	type = TOKEN_NODE;
	nd = new transparent_dummy_node;
	return;
      case '\b':
	type = TOKEN_BACKSPACE;
	return;
      case ' ':
	type = TOKEN_SPACE;
	return;
      case '\t':
	type = TOKEN_TAB;
	return;
      case '\n':
	type = TOKEN_NEWLINE;
	return;
      case '\001':
	type = TOKEN_LEADER;
	return;
      case 0:
	assert(n != 0);
	token_node *tn = n->get_token_node();
	if (tn)
	  *this = tn->tk;
	else {
	  nd = n;
	  type = TOKEN_NODE;
	}
	return;
      default:
	type = TOKEN_CHAR;
	c = cc;
	return;
      }
    }
    else {
    handle_escape_char:
      cc = input_stack::get(NULL);
      switch(cc) {
      case '(':
	nm = read_two_char_escape_name();
	type = TOKEN_SPECIAL;
	return;
      case EOF:
	type = TOKEN_EOF;
	error("end of input after escape character");
	return;
      case '`':
	goto ESCAPE_LEFT_QUOTE;
      case '\'':
	goto ESCAPE_RIGHT_QUOTE;
      case '-':
	goto ESCAPE_HYPHEN;
      case '_':
	goto ESCAPE_UNDERSCORE;
      case '%':
	goto ESCAPE_PERCENT;
      case ' ':
	goto ESCAPE_SPACE;
      case '0':
	nd = new hmotion_node(curenv->get_digit_width());
	type = TOKEN_NODE;
	return;
      case '|':
	goto ESCAPE_BAR;
      case '^':
	goto ESCAPE_CIRCUMFLEX;
      case '/':
	type = TOKEN_ITALIC_CORRECTION;
	return;
      case ',':
	type = TOKEN_NODE;
	nd = new left_italic_corrected_node;
	return;
      case '&':
	goto ESCAPE_AMPERSAND;
      case ')':
	goto ESCAPE_RIGHT_PARENTHESIS;
      case '!':
	goto ESCAPE_BANG;
      case '?':
	goto ESCAPE_QUESTION;
      case '~':
	nd = new unbreakable_space_node(curenv->get_space_width());
	type = TOKEN_NODE;
	return;
      case '"':
	while ((cc = input_stack::get(NULL)) != '\n' && cc != EOF)
	  ;
	if (cc == '\n')
	  type = TOKEN_NEWLINE;
	else
	  type = TOKEN_EOF;
	return;
      case '$':
	{
	  symbol nm = read_escape_name();
	  if (!nm.is_null())
	    interpolate_arg(nm);
	  break;
	}
      case '*':
	{
	  symbol nm = read_escape_name();
	  if (!nm.is_null())
	    interpolate_string(nm);
	  break;
	}
      case 'a':
	nd = new non_interpreted_char_node('\001');
	type = TOKEN_NODE;
	return;
      case 'A':
	c = '0' + do_name_test();
	type = TOKEN_CHAR;
	return;
      case 'b':
	nd = do_bracket();
	type = TOKEN_NODE;
	return;
      case 'c':
	goto ESCAPE_c;
      case 'C':
	nm = get_delim_name();
	if (nm.is_null())
	  break;
	type = TOKEN_SPECIAL;
	return;
      case 'd':
	type = TOKEN_NODE;
	nd = new vmotion_node(curenv->get_size()/2);
	return;
      case 'D':
	nd = read_draw_node();
	if (!nd)
	  break;
	type = TOKEN_NODE;
	return;
      case 'e':
	goto ESCAPE_e;
      case 'E':
	goto handle_escape_char;
      case 'f':
	{
	  symbol s = read_escape_name();
	  if (s.is_null())
	    break;
	  for (const char *p = s.contents(); *p != '\0'; p++)
	    if (!csdigit(*p)) {
	      type = TOKEN_FONT_NAME;
	      nm = s;
	      return;
	    }
	  type = TOKEN_FONT_POSITION;
	  val = atoi(s.contents());
	  return;
	}
      case 'g':
	{
	  symbol s = read_escape_name();
	  if (!s.is_null())
	    interpolate_number_format(s);
	  break;
	}
      case 'h':
	if (!get_delim_number(&x, 'm'))
	  break;
	type = TOKEN_NODE;
	nd = new hmotion_node(x);
	return;
      case 'H':
	if (!get_delim_number(&x, 'z', curenv->get_requested_point_size()))
	  break;
	type = TOKEN_CHAR_HEIGHT;
	val = x;
	return;
      case 'k':
	nm = read_escape_name();
	if (nm.is_null())
	  break;
	type = TOKEN_MARK_INPUT;
	return;
      case 'l':
      case 'L':
	{
	  charinfo *s = 0;
	  if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
	    break;
	  if (s == 0)
	    s = get_charinfo(cc == 'l' ? "ru" : "br");
	  type = TOKEN_NODE;
	  node *n = curenv->make_char_node(s);
	  if (cc == 'l')
	    nd = new hline_node(x, n);
	  else
	    nd = new vline_node(x, n);
	  return;
	}
      case 'n':
	{
	  int inc;
	  symbol nm = read_increment_and_escape_name(&inc);
	  if (!nm.is_null())
	    interpolate_number_reg(nm, inc);
	  break;
	}
      case 'N':
	if (!get_delim_number(&val, 0))
	  break;
	type = TOKEN_NUMBERED_CHAR;
	return;
      case 'o':
	nd = do_overstrike();
	type = TOKEN_NODE;
	return;
      case 'p':
	type = TOKEN_SPREAD;
	return;
      case 'r':
	type = TOKEN_NODE;
	nd = new vmotion_node(-curenv->get_size());
	return;
      case 'R':
	do_register();
	break;
      case 's':
	if (!read_size(&x))
	  break;
	type = TOKEN_SIZE;
	val = x;
	return;
      case 'S':
	if (!get_delim_number(&x, 0))
	  break;
	type = TOKEN_CHAR_SLANT;
	val = x;
	return;
      case 't':
	type = TOKEN_NODE;
	nd = new non_interpreted_char_node('\t');
	return;
      case 'u':
	type = TOKEN_NODE;
	nd = new vmotion_node(-curenv->get_size()/2);
	return;
      case 'v':
	if (!get_delim_number(&x, 'v'))
	  break;
	type = TOKEN_NODE;
	nd = new vmotion_node(x);
	return;
      case 'w':
	do_width();
	break;
      case 'x':
	if (!get_delim_number(&x, 'v'))
	  break;
	type = TOKEN_NODE;
	nd = new extra_size_node(x);
	return;
      case 'X':
	nd = do_special();
	if (!nd)
	  break;
	type = TOKEN_NODE;
	return;
      case 'Y':
	{
	  symbol s = read_escape_name();
	  if (s.is_null())
	    break;
	  request_or_macro *p = lookup_request(s);
	  macro *m = p->to_macro();
	  if (!m) {
	    error("can transparently throughput a request");
	    break;
	  }
	  nd = new special_node(*m);
	  type = TOKEN_NODE;
	  return;
	}
      case 'z':
	{
	  next();
	  charinfo *ci = get_char(1);
	  if (ci == 0)
	    break;
	  node *gn = curenv->make_char_node(ci);
	  if (gn == 0)
	    break;
	  nd = new zero_width_node(gn);
	  type = TOKEN_NODE;
	  return;
	}
      case 'Z':
	nd = do_zero_width();
	if (nd == 0)
	  break;
	type = TOKEN_NODE;
	return;
      case '{':
	goto ESCAPE_LEFT_BRACE;
      case '}':
	goto ESCAPE_RIGHT_BRACE;
      case '\n':
	break;
      case '[':
	if (!compatible_flag) {
	  nm = read_long_escape_name();
	  if (nm.is_null())
	    break;
	  type = TOKEN_SPECIAL;
	  return;
	}
	goto handle_normal_char;
      default:
	if (cc != escape_char && cc != '.')
	  warning(WARN_ESCAPE, "escape character ignored before %1",
		  input_char_description(cc));
	goto handle_normal_char;
      }
    }
  }
}

int token::operator==(const token &t)
{
  if (type != t.type)
    return 0;
  switch(type) {
  case TOKEN_CHAR:
    return c == t.c;
  case TOKEN_SPECIAL:
    return nm == t.nm;
  case TOKEN_NUMBERED_CHAR:
  case TOKEN_CHAR_HEIGHT:
  case TOKEN_CHAR_SLANT:
  case TOKEN_FONT_POSITION:
  case TOKEN_SIZE:
    return val == t.val;
  default:
    return 1;
  }
}

int token::operator!=(const token &t)
{
  return !(*this == t);
}

// is token a suitable delimiter (like ')?

int token::delimiter(int err)
{
  switch(type) {
  case TOKEN_CHAR:
    switch(c) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    case '+':
    case '-':
    case '/':
    case '*':
    case '%':
    case '<':
    case '>':
    case '=':
    case '&':
    case ':':
    case '(':
    case ')':
    case '.':
      if (err)
	error("cannot use character `%1' as a starting delimiter", char(c));
      return 0;
    default:
      return 1;
    }
  case TOKEN_NODE:
  case TOKEN_SPACE:
  case TOKEN_TAB:
  case TOKEN_NEWLINE:
    if (err)
      error("cannot use %1 as a starting delimiter", description());
    return 0;
  default:
    return 1;
  }
}

const char *token::description()
{
  static char buf[4];
  switch (type) {
  case TOKEN_BACKSPACE:
    return "a backspace character";
  case TOKEN_CHAR:
    buf[0] = '`';
    buf[1] = c;
    buf[2] = '\'';
    buf[3] = '\0';
    return buf;
  case TOKEN_CHAR_HEIGHT:
    return "`\\H'";
  case TOKEN_CHAR_SLANT:
    return "`\\S'";
  case TOKEN_DUMMY:
    return "`\\&'";
  case TOKEN_ESCAPE:
    return "`\\e'";
  case TOKEN_FONT_NAME:
  case TOKEN_FONT_POSITION:
    return "`\\f'";
  case TOKEN_HYPHEN_INDICATOR:
    return "`\\%'";
  case TOKEN_INTERRUPT:
    return "`\\c'";
  case TOKEN_ITALIC_CORRECTION:
    return "`\\/'";
  case TOKEN_LEADER:
    return "a leader character";
  case TOKEN_LEFT_BRACE:
    return "`\\{'";
  case TOKEN_MARK_INPUT:
    return "`\\k'";
  case TOKEN_NEWLINE:
    return "newline";
  case TOKEN_NODE:
    return "a node";
  case TOKEN_NUMBERED_CHAR:
    return "`\\N'";
  case TOKEN_RIGHT_BRACE:
    return "`\\}'";
  case TOKEN_SIZE:
    return "`\\s'";
  case TOKEN_SPACE:
    return "a space";
  case TOKEN_SPECIAL:
    return "a special character";
  case TOKEN_SPREAD:
    return "`\\p'";
  case TOKEN_TAB:
    return "a tab character";
  case TOKEN_TRANSPARENT:
    return "`\\!'";
  case TOKEN_EOF:
    return "end of input";
  default:
    break;
  }
  return "a magic token";
}

void skip_line()
{
  while (!tok.newline())
    if (tok.eof())
      return;
    else
      tok.next();
  tok.next();
}

void compatible()
{
  int n;
  if (!has_arg())
    compatible_flag = 1;
  else if (get_integer(&n))
    compatible_flag = n != 0;
  skip_line();
}


static void empty_name_warning(int required)
{
  if (tok.newline() || tok.eof()) {
    if (required)
      warning(WARN_MISSING, "missing name");
  }
  else if (tok.right_brace() || tok.tab()) {
    const char *start = tok.description();
    do {
      tok.next();
    } while (tok.space() || tok.right_brace() || tok.tab());
    if (!tok.newline() && !tok.eof())
      error("%1 is not allowed before an argument", start);
    else if (required)
      warning(WARN_MISSING, "missing name");
  }
  else if (required)
    error("name expected (got %1)", tok.description());
  else
    error("name expected (got %1): treated as missing", tok.description());
}

static void non_empty_name_warning()
{
  if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
      && !tok.right_brace()
      // We don't want to give a warning for .el\{
      && !tok.left_brace())
    error("%1 is not allowed in a name", tok.description());
}

symbol get_name(int required)
{
  if (compatible_flag) {
    char buf[3];
    tok.skip();
    if ((buf[0] = tok.ch()) != 0) {
      tok.next();
      if ((buf[1] = tok.ch()) != 0) {
	buf[2] = 0;
	tok.make_space();
      }
      else
	non_empty_name_warning();
      return symbol(buf);
    }
    else {
      empty_name_warning(required);
      return NULL_SYMBOL;
    }
  }
  else 
    return get_long_name(required);
}

symbol get_long_name(int required)
{
  while (tok.space())
    tok.next();
  char abuf[ABUF_SIZE];
  char *buf = abuf;
  int buf_size = ABUF_SIZE;
  int i = 0;
  for (;;) {
    if (i + 1 > buf_size) {
      if (buf == abuf) {
	buf = new char [ABUF_SIZE*2];
	memcpy(buf, abuf, buf_size);
	buf_size = ABUF_SIZE*2;
      }
      else {
	char *old_buf = buf;
	buf = new char[buf_size*2];
	memcpy(buf, old_buf, buf_size);
	buf_size *= 2;
	delete old_buf;
      }
    }
    if ((buf[i] = tok.ch()) == 0)
      break;
    i++;
    tok.next();
  }
  if (i == 0) {
    empty_name_warning(required);
    return NULL_SYMBOL;
  }
  non_empty_name_warning();
  if (buf == abuf)
    return symbol(buf);
  else {
    symbol s(buf);
    delete buf;
    return s;
  }
}

extern int exit_flag;

NO_RETURN void exit_groff()
{
  exit_flag = 1;
  if (!end_macro_name.is_null()) {
    spring_trap(end_macro_name);
    tok.next();
    process_input_stack();
  }
  curenv->do_break();
  tok.next();
  process_input_stack();
  end_diversions();
  exit_flag = 2;
  topdiv->set_ejecting();
  push_page_ejector();
  topdiv->space(topdiv->get_page_length(), 1);
  tok.next();
  process_input_stack();
  // This will only be if a trap-invoked macro starts a diversion,
  // or if vertical position traps have been disabled.
  cleanup_and_exit(0);
}

// This implements .ex.  The input stack must be cleared before calling
// exit_groff().

NO_RETURN void exit_request()
{
  input_stack::clear();
  exit_groff();
}

void end_macro()
{
  end_macro_name = get_name();
  skip_line();
}

inline int possibly_handle_first_page_transition()
{
  if (!topdiv->first_page_begun && curdiv == topdiv && !curenv->is_dummy()) {
    handle_first_page_transition();
    return 1;
  }
  else
    return 0;
}

static int transparent_translate(int cc)
{
  if (!illegal_input_char(cc)) {
    charinfo *ci = charset_table[cc];
    if (ci->get_special_translation() == charinfo::TRANSLATE_SPACE)
      return ' ';
    if (ci->get_special_translation() == charinfo::TRANSLATE_DUMMY)
      return ESCAPE_AMPERSAND;
#if 0
    // This is just too ugly.
    ci = ci->get_translation();
    if (ci) {
      int c = ci->get_ascii_code();
      if (c != '\0')
	return c;
    }
#endif
  }
  return cc;
}

class int_stack {
  struct int_stack_element {
    int n;
    int_stack_element *next;
  } *top;
public:
  int_stack();
  ~int_stack();
  void push(int);
  int is_empty();
  int pop();
};

int_stack::int_stack()
{
  top = 0;
}

int_stack::~int_stack()
{
  while (top != 0) {
    int_stack_element *temp = top;
    top = top->next;
    delete temp;
  }
  
}

int int_stack::is_empty()
{
  return top == 0;
}

void int_stack::push(int n)
{
  int_stack_element *p = new int_stack_element;
  p->next = top;
  p->n = n;
  top = p;
}


int int_stack::pop()
{
  assert(top != 0);
  int_stack_element *p = top;
  top = top->next;
  int n = p->n;
  delete p;
  return n;
}

int node::reread(int *)
{
  return 0;
}

int diverted_space_node::reread(int *bolp)
{
  if (curenv->get_fill())
    blank_line();
  else
    curdiv->space(n);
  *bolp = 1;
  return 1;
}

int diverted_copy_file_node::reread(int *bolp)
{
  curdiv->copy_file(filename.contents());
  *bolp = 1;
  return 1;
}

static void process_input_stack()
{
  int_stack trap_bol_stack;
  int bol = 1;
  for (;;) {
    int suppress_next = 0;
    switch (tok.type) {
    case token::TOKEN_CHAR:
      {
	unsigned char ch = tok.c;
	if (bol && 
	    (ch == curenv->control_char
	     || ch == curenv->no_break_control_char)) {
	  break_flag = ch == curenv->control_char;
	  // skip tabs as well as spaces here
	  do {
	    tok.next();
	  } while (tok.white_space());
	  symbol nm = get_name();
	  if (nm.is_null())
	    skip_line();
	  else
	    interpolate_macro(nm);
	  suppress_next = 1;
	}
	else {
	  if (possibly_handle_first_page_transition())
	    ;
	  else {
	    for (;;) {
	      curenv->add_char(charset_table[ch]);
	      tok.next();
	      if (tok.type != token::TOKEN_CHAR)
		break;
	      ch = tok.c;
	    }
	    suppress_next = 1;
	    bol = 0;
	  }
	}
	break;
      }
    case token::TOKEN_TRANSPARENT:
      {
	if (bol) {
	  if (possibly_handle_first_page_transition())
	    ;
	  else {
	    int cc;
	    do {
	      node *n;
	      cc = get_copy(&n);
	      if (cc != EOF)
		if (cc != '\0')
		  curdiv->transparent_output(transparent_translate(cc));
		else
		  curdiv->transparent_output(n);
	    } while (cc != '\n' && cc != EOF);
	    if (cc == EOF)
	      curdiv->transparent_output('\n');
	  }
	}
	break;
      }
    case token::TOKEN_NEWLINE:
      {
	if (bol && !curenv->get_prev_line_interrupted())
	  blank_line();
	else {
	  curenv->newline();
	  bol = 1;
	}
	break;
      }
    case token::TOKEN_REQUEST:
      {
	int request_code = tok.c;
	tok.next();
	switch (request_code) {
	case TITLE_REQUEST:
	  title();
	  break;
	case COPY_FILE_REQUEST:
	  copy_file();
	  break;
	case TRANSPARENT_FILE_REQUEST:
	  transparent_file();
	  break;
#ifdef COLUMN
	case VJUSTIFY_REQUEST:
	  vjustify();
	  break;
#endif /* COLUMN */
	default:
	  assert(0);
	  break;
	}
	suppress_next = 1;
	break;
      }
    case token::TOKEN_SPACE:
      {
	if (possibly_handle_first_page_transition())
	  ;
	else if (bol && !curenv->get_prev_line_interrupted()) {
	  curenv->do_break();
	  int nspaces = 0;
	  do {
	    nspaces += tok.nspaces();
	    tok.next();
	  } while (tok.space());
	  if (tok.newline())
	    blank_line();
	  else {
	    curenv->add_node(new hmotion_node(curenv->get_space_width()*nspaces));
	    suppress_next = 1;
	    bol = 0;
	  }
	}
	else {
	  curenv->space();
	  bol = 0;
	}
	break;
      }
    case token::TOKEN_EOF:
      return;
    case token::TOKEN_NODE:
      {
	if (possibly_handle_first_page_transition())
	  ;
	else if (tok.nd->reread(&bol)) {
	  delete tok.nd;
	  tok.nd = 0;
	}
	else {
	  curenv->add_node(tok.nd);
	  tok.nd = 0;
	  bol = 0;
	}
	break;
      }
    case token::TOKEN_PAGE_EJECTOR:
      {
	continue_page_eject();
	// I think we just want to preserve bol.
	// bol = 1;
	break;
      }
    case token::TOKEN_BEGIN_TRAP:
      {
	trap_bol_stack.push(bol);
	bol = 1;
	break;
      }
    case token::TOKEN_END_TRAP:
      {
	if (trap_bol_stack.is_empty())
	  error("spurious end trap token detected!");
	else
	  bol = trap_bol_stack.pop();

	/* I'm not totally happy about this.  But I can't think of any other
	  way to do it.  Doing an output_pending_lines() whenever a
	  TOKEN_END_TRAP is detected doesn't work: for example,

          .wh -1i x
          .de x
          'bp
	  ..
	  .wh -.5i y
	  .de y
	  .tl ''-%-''
	  ..
	  .br
	  .ll .5i
	  .sp |\n(.pu-1i-.5v
	  a\%very\%very\%long\%word

          will print all but the first lines from the word immediately
          after the footer, rather than on the next page. */

	if (trap_bol_stack.is_empty())
	  curenv->output_pending_lines();
	break;
      }
    default:
      {
	bol = 0;
	tok.process();
	break;
      }
    }
    if (!suppress_next)
      tok.next();
    trap_sprung_flag = 0;
  }
}

#ifdef WIDOW_CONTROL

void flush_pending_lines()
{
  while (!tok.newline() && !tok.eof())
    tok.next();
  curenv->output_pending_lines();
  tok.next();
}

#endif /* WIDOW_CONTROL */

request_or_macro::request_or_macro()
{
}

macro *request_or_macro::to_macro()
{
  return 0;
}

request::request(REQUEST_FUNCP pp) : p(pp)
{
}

void request::invoke(symbol)
{
  (*p)();
}

struct char_block {
  enum { SIZE = 128 };
  unsigned char s[SIZE];
  char_block *next;
  char_block();
};

char_block::char_block()
: next(0)
{
}

class char_list {
public:
  char_list();
  ~char_list();
  void append(unsigned char);
  int length();
private:
  unsigned char *ptr;
  int len;
  char_block *head;
  char_block *tail;
  friend class macro_header;
  friend class string_iterator;
};

char_list::char_list()
: head(0), tail(0), ptr(0), len(0)
{
}

char_list::~char_list()
{
  while (head != 0) {
    char_block *tem = head;
    head = head->next;
    delete tem;
  }
}

int char_list::length()
{
  return len;
}

void char_list::append(unsigned char c)
{
  if (tail == 0) {
    head = tail = new char_block;
    ptr = tail->s;
  }
  else {
    if (ptr >= tail->s + char_block::SIZE) {
      tail->next = new char_block;
      tail = tail->next;
      ptr = tail->s;
    }
  }
  *ptr++ = c;
  len++;
}

class node_list {
  node *head;
  node *tail;
public:
  node_list();
  ~node_list();
  void append(node *);
  int length();
  node *extract();

  friend class macro_header;
  friend class string_iterator;
};

void node_list::append(node *n)
{
  if (head == 0) {
    n->next = 0;
    head = tail = n;
  }
  else {
    n->next = 0;
    tail = tail->next = n;
  }
}

int node_list::length()
{
  int total = 0;
  for (node *n = head; n != 0; n = n->next)
    ++total;
  return total;
}

node_list::node_list()
{
  head = tail = 0;
}

node *node_list::extract()
{
  node *temp = head;
  head = tail = 0;
  return temp;
}


node_list::~node_list()
{
  delete_node_list(head);
}

struct macro_header {
  int count;
  char_list cl;
  node_list nl;
  macro_header() { count = 1; }
  macro_header *copy(int);
};


macro::~macro() 
{ 
  if (p != 0 && --(p->count) <= 0) 
    delete p;
}

macro::macro()
{
  if (!input_stack::get_location(1, &filename, &lineno)) {
    filename = 0;
    lineno = 0;
  }
  length = 0;
  p = 0;
}

macro::macro(const macro &m) 
: filename(m.filename), lineno(m.lineno), p(m.p), length(m.length)
{ 
  if (p != 0)
    p->count++;
}

macro &macro::operator=(const macro &m)
{
  // don't assign object
  if (m.p != 0)
    m.p->count++;
  if (p != 0 && --(p->count) <= 0) 
    delete p; 
  p = m.p; 
  filename = m.filename;
  lineno = m.lineno;
  length = m.length;
  return *this;
}

void macro::append(unsigned char c)
{
  assert(c != 0);
  if (p == 0)
    p = new macro_header;
  if (p->cl.length() != length) {
    macro_header *tem = p->copy(length);
    if (--(p->count) <= 0)
      delete p;
    p = tem;
  }
  p->cl.append(c);
  ++length;
}

void macro::append(node *n)
{
  assert(n != 0);
  if (p == 0)
    p = new macro_header;
  if (p->cl.length() != length) {
    macro_header *tem = p->copy(length);
    if (--(p->count) <= 0)
      delete p;
    p = tem;
  }
  p->cl.append(0);
  p->nl.append(n);
  ++length;
}

void macro::print_size()
{
  errprint("%1", length);
}

// make a copy of the first n bytes

macro_header *macro_header::copy(int n)
{
  macro_header *p = new macro_header;
  char_block *bp = cl.head;
  unsigned char *ptr = bp->s;
  node *nd = nl.head;
  while (--n >= 0) {
    if (ptr >= bp->s + char_block::SIZE) {
      bp = bp->next;
      ptr = bp->s;
    }
    int c = *ptr++;
    p->cl.append(c);
    if (c == 0) {
      p->nl.append(nd->copy());
      nd = nd->next;
    }
  }
  return p;
}

void print_macros()
{
  object_dictionary_iterator iter(request_dictionary);
  request_or_macro *rm;
  symbol s;
  while (iter.get(&s, (object **)&rm)) {
    assert(!s.is_null());
    macro *m = rm->to_macro();
    if (m) {
      errprint("%1\t", s.contents());
      m->print_size();
      errprint("\n");
    }
  }
  fflush(stderr);
  skip_line();
}

class string_iterator : public input_iterator {
  macro mac;
  const char *how_invoked;
  int newline_flag;
  int lineno;
  char_block *bp;
  int count;			// of characters remaining
  node *nd;
protected:
  symbol nm;
  string_iterator();
public:
  string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
  int fill(node **);
  int peek();
  int get_location(int, const char **, int *);
  void backtrace();
};

string_iterator::string_iterator(const macro &m, const char *p, symbol s) 
: lineno(1), mac(m), newline_flag(0), how_invoked(p), nm(s)
{
  count = mac.length;
  if (count != 0) {
    bp = mac.p->cl.head;
    nd = mac.p->nl.head;
    ptr = eptr = bp->s;
  }
  else {
    bp = 0;
    nd = 0;
    ptr = eptr = 0;
  }
}

string_iterator::string_iterator()
{
  bp = 0;
  nd = 0;
  ptr = eptr = 0;
  newline_flag = 0;
  how_invoked = 0;
}

int string_iterator::fill(node **np)
{
  if (newline_flag)
    lineno++;
  newline_flag = 0;
  if (count <= 0)
    return EOF;
  const unsigned char *p = eptr;
  if (p >= bp->s + char_block::SIZE) {
    bp = bp->next;
    p = bp->s;
  }
  if (*p == '\0') {
    if (np)
      *np = nd->copy();
    nd = nd->next;
    eptr = ptr = p + 1;
    count--;
    return 0;
  }
  const unsigned char *e = bp->s + char_block::SIZE;
  if (e - p > count)
    e = p + count;
  ptr = p;
  while (p < e) {
    unsigned char c = *p;
    if (c == '\n' || c == ESCAPE_NEWLINE) {
      newline_flag = 1;
      p++;
      break;
    }
    if (c == '\0')
      break;
    p++;
  }
  eptr = p;
  count -= p - ptr;
  return *ptr++;
}

int string_iterator::peek()
{
  if (count <= 0)
    return EOF;
  const unsigned char *p = eptr;
  if (count <= 0)
    return EOF;
  if (p >= bp->s + char_block::SIZE) {
    p = bp->next->s;
  }
  return *p;
}

int string_iterator::get_location(int allow_macro,
				  const char **filep, int *linep)
{
  if (!allow_macro)
    return 0;
  if (mac.filename == 0)
    return 0;
  *filep = mac.filename;
  *linep = mac.lineno + lineno - 1;
  return 1;
}

void string_iterator::backtrace()
{
  if (mac.filename) {
    errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
    if (how_invoked) {
      if (!nm.is_null())
	errprint(": %1 `%2'\n", how_invoked, nm.contents());
      else
	errprint(": %1\n", how_invoked);
    }
    else
      errprint("\n");
  }
}

class temp_iterator : public input_iterator {
  unsigned char *base;
  temp_iterator(const char *, int len);
public:
  ~temp_iterator();
  friend input_iterator *make_temp_iterator(const char *);
};

#ifdef __GNUG__
inline
#endif
temp_iterator::temp_iterator(const char *s, int len)
{
  base = new unsigned char[len];
  memcpy(base, s, len);
  ptr = base;
  eptr = base + len;
}

temp_iterator::~temp_iterator()
{
  delete base;
}

class small_temp_iterator : public input_iterator {
private:
  small_temp_iterator(const char *, int);
  ~small_temp_iterator();
  enum { BLOCK = 16 };
  static small_temp_iterator *free_list;
  void *operator new(size_t);
  void operator delete(void *);
  enum { SIZE = 12 };
  unsigned char buf[SIZE];
  friend input_iterator *make_temp_iterator(const char *);
};

small_temp_iterator *small_temp_iterator::free_list = 0;

void *small_temp_iterator::operator new(size_t n)
{
  assert(n == sizeof(small_temp_iterator));
  if (!free_list) {
    free_list = (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
    for (int i = 0; i < BLOCK - 1; i++)
      free_list[i].next = free_list + i + 1;
    free_list[BLOCK-1].next = 0;
  }
  small_temp_iterator *p = free_list;
  free_list = (small_temp_iterator *)(free_list->next);
  p->next = 0;
  return p;
}

#ifdef __GNUG__
inline
#endif
void small_temp_iterator::operator delete(void *p)
{
  if (p) {
    ((small_temp_iterator *)p)->next = free_list;
    free_list = (small_temp_iterator *)p;
  }
}

small_temp_iterator::~small_temp_iterator()
{
}


#ifdef __GNUG__
inline
#endif
small_temp_iterator::small_temp_iterator(const char *s, int len)
{
  for (int i = 0; i < len; i++)
    buf[i] = s[i];
  ptr = buf;
  eptr = buf + len;
}

input_iterator *make_temp_iterator(const char *s)
{
  if (s == 0)
    return new small_temp_iterator(s, 0);
  else {
    int n = strlen(s);
    if (n <= small_temp_iterator::SIZE)
      return new small_temp_iterator(s, n);
    else
      return new temp_iterator(s, n);
  }
}

// this is used when macros are interpolated using the .macro_name notation

struct arg_list {
  macro mac;
  arg_list *next;
  arg_list(const macro &);
  ~arg_list();
};

arg_list::arg_list(const macro &m) : mac(m), next(0)
{
}

arg_list::~arg_list()
{
}

class macro_iterator : public string_iterator {
  arg_list *args;
  int argc;
public:
  macro_iterator(symbol, macro &);
  macro_iterator();
  ~macro_iterator();
  int has_args() { return 1; }
  input_iterator *get_arg(int i);
  int nargs() { return argc; }
  void add_arg(const macro &m);
  void shift(int n);
};

input_iterator *macro_iterator::get_arg(int i)
{ 
  if (i == 0)
    return make_temp_iterator(nm.contents());
  if (i > 0 && i <= argc) {
    arg_list *p = args;
    for (int j = 1; j < i; j++) {
      assert(p != 0);
      p = p->next;
    }
    return new string_iterator(p->mac);
  }
  else
    return 0;
}

void macro_iterator::add_arg(const macro &m)
{
  for (arg_list **p = &args; *p; p = &((*p)->next))
    ;
  *p = new arg_list(m);
  ++argc;
}

void macro_iterator::shift(int n)
{
  while (n > 0 && argc > 0) {
    arg_list *tem = args;
    args = args->next;
    delete tem;
    --argc;
    --n;
  }
}

// This gets used by eg .if '\?xxx\?''.

int operator==(const macro &m1, const macro &m2)
{
  if (m1.length != m2.length)
    return 0;
  string_iterator iter1(m1);
  string_iterator iter2(m2);
  int n = m1.length;
  while (--n >= 0) {
    node *nd1 = 0;
    int c1 = iter1.get(&nd1);
    assert(c1 != EOF);
    node *nd2 = 0;
    int c2 = iter2.get(&nd2);
    assert(c2 != EOF);
    if (c1 != c2) {
      if (c1 == 0)
	delete nd1;
      else if (c2 == 0)
	delete nd2;
      return 0;
    }
    if (c1 == 0) {
      assert(nd1 != 0);
      assert(nd2 != 0);
      int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
      delete nd1;
      delete nd2;
      if (!are_same)
	return 0;
    }
  }
  return 1;
}

static void interpolate_macro(symbol nm)
{
  request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
  if (p == 0) {
    int warned = 0;
    const char *s = nm.contents();
    if (strlen(s) > 2) {
      char buf[3];
      buf[0] = s[0];
      buf[1] = s[1];
      buf[2] = '\0';
      if (request_dictionary.lookup(symbol(buf)) != 0)
	warned = warning(WARN_SPACE,
			 "space required between `%1' and argument", buf);
    }
    if (!warned) {
      warning(WARN_MAC, "`%1' not defined", nm.contents());
      p = new macro;
      request_dictionary.define(nm, p);
    }
  }
  if (p)
    p->invoke(nm);
  else {
    skip_line();
    return;
  }
}

static void decode_args(macro_iterator *mi)
{
  if (!tok.newline() && !tok.eof()) {
    node *n;
    int c = get_copy(&n);
    for (;;)  {
      while (c == ' ')
	c = get_copy(&n);
      if (c == '\n' || c == EOF)
	break;
      macro arg;
      int quote_input_level = 0;
      if (c == '\"') {
	quote_input_level = input_stack::get_level();
	c = get_copy(&n);
      }
      while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
	if (quote_input_level > 0 && c == '\"'
	    && (compatible_flag
		|| input_stack::get_level() == quote_input_level)) {
	  c = get_copy(&n);
	  if (c == '"') {
	    arg.append(c);
	    c = get_copy(&n);
	  }
	  else
	    break;
	}
	else {
	  if (c == 0)
	    arg.append(n);
	  else
	    arg.append(c);
	  c = get_copy(&n);
	}
      }
      mi->add_arg(arg);
    }
  }
}

void macro::invoke(symbol nm)
{
  macro_iterator *mi = new macro_iterator(nm, *this);
  decode_args(mi);
  input_stack::push(mi);
  tok.next();
}

macro *macro::to_macro()
{
  return this;
}

macro_iterator::macro_iterator(symbol s, macro &m)
: string_iterator(m, "macro", s), args(0), argc(0)
{
}

macro_iterator::macro_iterator() : args(0), argc(0)
{
}

macro_iterator::~macro_iterator()
{
  while (args != 0) {
    arg_list *tem = args;
    args = args->next;
    delete tem;
  }
}

void read_request()
{
  macro_iterator *mi = new macro_iterator;
  int had_prompt = 0;
  if (!tok.newline() && !tok.eof()) {
    int c = get_copy(NULL);
    while (c == ' ')
      c = get_copy(NULL);
    while (c != EOF && c != '\n' && c != ' ') {
      if (!illegal_input_char(c)) {
	fputc(c, stderr);
	had_prompt = 1;
      }
      c = get_copy(NULL);
    }
    if (c == ' ') {
      tok.make_space();
      decode_args(mi);
    }
  }
  fputc(had_prompt ? ':' : '\007', stderr);
  fflush(stderr);
  input_stack::push(mi);
  macro mac;
  int nl = 0;
  int c;
  while ((c = getchar()) != EOF) {
    if (illegal_input_char(c))
      warning(WARN_INPUT, "illegal input character code %1", int(c));
    else {
      if (c == '\n') {
	if (nl)
	  break;
	else
	  nl = 1;
      }
      else
	nl = 0;
      mac.append(c);
    }
  }
  input_stack::push(new string_iterator(mac));
  tok.next();
}


void do_define_string(int append)
{
  symbol nm;
  node *n;
  int c;
  nm = get_name(1);
  if (nm.is_null()) {
    skip_line();
    return;
  }
  if (tok.newline())
    c = '\n';
  else if (!tok.space()) {
    error("bad string definition");
    skip_line();
    return;
  }
  else
    c = get_copy(&n);
  while (c == ' ')
    c = get_copy(&n);
  if (c == '"')
    c = get_copy(&n);
  macro mac;
  request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
  macro *mm = rm ? rm->to_macro() : 0;
  if (append && mm)
    mac = *mm;
  while (c != '\n' && c != EOF) {
    if (c == 0)
      mac.append(n);
    else
      mac.append((unsigned char)c);
    c = get_copy(&n);
  }
  if (!mm) {
    mm = new macro;
    request_dictionary.define(nm, mm);
  }
  *mm = mac;
  tok.next();
}

void define_string()
{
  do_define_string(0);
}

void append_string()
{
  do_define_string(1);
}

void define_character()
{
  node *n;
  int c;
  tok.skip();
  charinfo *ci = tok.get_char(1);
  if (ci == 0) {
    skip_line();
    return;
  }
  tok.next();
  if (tok.newline())
    c = '\n';
  else if (!tok.space()) {
    error("bad character definition");
    skip_line();
    return;
  }
  else
    c = get_copy(&n);
  while (c == ' ' || c == '\t')
    c = get_copy(&n);
  if (c == '"')
    c = get_copy(&n);
  macro *m = new macro;
  while (c != '\n' && c != EOF) {
    if (c == 0)
      m->append(n);
    else
      m->append((unsigned char)c);
    c = get_copy(&n);
  }
  m = ci->set_macro(m);
  if (m)
    delete m;
  tok.next();
}


static void interpolate_string(symbol nm)
{
  request_or_macro *p = lookup_request(nm);
  macro *m = p->to_macro();
  if (!m)
    error("you can only invoke a string using \\*");
  else {
    string_iterator *si = new string_iterator(*m, "string", nm);
    input_stack::push(si);
  }
}

/* This class is used for the implementation of \$@.  It is used for
each of the closing double quotes.  It artificially increases the
input level by 2, so that the closing double quote will appear to have
the same input level as the opening quote. */

class end_quote_iterator : public input_iterator {
  unsigned char buf[1];
public:
  end_quote_iterator();
  ~end_quote_iterator() { }
  int internal_level() { return 2; }
};

end_quote_iterator::end_quote_iterator()
{
  buf[0] = '"';
  ptr = buf;
  eptr = buf + 1;
}

static void interpolate_arg(symbol nm)
{
  const char *s = nm.contents();
  if (!s || *s == '\0')
    error("missing argument name");
  else if (s[1] == 0 && csdigit(s[0]))
    input_stack::push(input_stack::get_arg(s[0] - '0'));
  else if (s[0] == '*' && s[1] == '\0') {
    for (int i = input_stack::nargs(); i > 0; i--) {
      input_stack::push(input_stack::get_arg(i));
      if (i != 1)
	input_stack::push(make_temp_iterator(" "));
    }
  }
  else if (s[0] == '@' && s[1] == '\0') {
    for (int i = input_stack::nargs(); i > 0; i--) {
      input_stack::push(new end_quote_iterator);
      input_stack::push(input_stack::get_arg(i));
      input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
    }
  }
  else {
    for (const char *p = s; *p && csdigit(*p); p++)
      ;
    if (*p)
      error("bad argument name `%1'", s);
    else
      input_stack::push(input_stack::get_arg(atoi(s)));
  }
}

/* We need to unget the current token; we fake this by wrapping it up
in a token_node, and wrapping that up in a string_iterator. */

void handle_first_page_transition()
{
  macro mac;
  mac.append(new token_node(tok));
  input_stack::push(new string_iterator(mac));
  topdiv->begin_page();
}

void push_page_ejector()
{
  static char buf[2] = { PAGE_EJECTOR, '\0' };
  input_stack::push(make_temp_iterator(buf));
}

void handle_initial_request(unsigned char code)
{
  char buf[2];
  buf[0] = code;
  buf[1] = '\0';
  macro mac;
  mac.append(new token_node(tok));
  input_stack::push(new string_iterator(mac));
  input_stack::push(make_temp_iterator(buf));
  topdiv->begin_page();
  tok.next();
}

void handle_initial_title()
{
  handle_initial_request(TITLE_REQUEST);
}

int trap_sprung_flag = 0;
int postpone_traps_flag = 0;
symbol postponed_trap;

void spring_trap(symbol nm)
{
  assert(!nm.is_null());
  trap_sprung_flag = 1;
  if (postpone_traps_flag) {
    postponed_trap = nm;
    return;
  }
  static char buf[2] = { BEGIN_TRAP, 0 };
  static char buf2[2] = { END_TRAP, '\0' };
  input_stack::push(make_temp_iterator(buf2));
  request_or_macro *p = lookup_request(nm);
  macro *m = p->to_macro();
  if (m)
    input_stack::push(new string_iterator(*m, "trap-invoked macro", nm));
  else
    error("you can't invoke a request with a trap");
  input_stack::push(make_temp_iterator(buf));
}

void postpone_traps()
{
  postpone_traps_flag = 1;
}

int unpostpone_traps()
{
  postpone_traps_flag = 0;
  if (!postponed_trap.is_null()) {
    spring_trap(postponed_trap);
    postponed_trap = NULL_SYMBOL;
    return 1;
  }
  else
    return 0;
}

// this should be local to define_macro, but cfront 1.2 doesn't support that
static symbol dot_symbol(".");

enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };

void do_define_macro(define_mode mode)
{
  symbol nm;
  if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
    nm = get_name(1);
    if (nm.is_null()) {
      skip_line();
      return;
    }
  }
  symbol term = get_name();	// the request that terminates the definition
  if (term.is_null())
    term = dot_symbol;
  while (!tok.newline() && !tok.eof())
    tok.next();
  const char *start_filename;
  int start_lineno;
  int have_start_location = input_stack::get_location(0, &start_filename,
						      &start_lineno);
  node *n;
  // doing this here makes the line numbers come out right
  int c = get_copy(&n, 1);
  macro mac;
  macro *mm = 0;
  if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
    request_or_macro *rm =
      (request_or_macro *)request_dictionary.lookup(nm);
    if (rm)
      mm = rm->to_macro();
    if (mm && mode == DEFINE_APPEND)
      mac = *mm;
  }
  int bol = 1;
  for (;;) {
    while (c == ESCAPE_NEWLINE) {
      if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
	mac.append(c);
      c = get_copy(&n, 1);
    }
    if (bol && c == '.') {
      const char *s = term.contents();
      int d;
      // see if it matches term
      for (int i = 0; s[i] != 0; i++) {
	d = get_copy(&n);
	if ((unsigned char)s[i] != d)
	  break;
      }
      if (s[i] == 0
	  && ((i == 2 && compatible_flag)
	      || (d = get_copy(&n)) == ' ' 
	      || d == '\n')) {	// we found it
		if (d == '\n')
		  tok.make_newline();
		else
		  tok.make_space();
		if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
		  if (!mm) {
		    mm = new macro;
		    request_dictionary.define(nm, mm);
		  }
		  *mm = mac;
		}
		if (term != dot_symbol)
		  interpolate_macro(term);
		else
		  skip_line();
		return;
	      }
      if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
	mac.append(c);
	for (int j = 0; j < i; j++)
	  mac.append(s[j]);
      }
      c = d;
    }
    if (c == EOF) {
      if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
	if (have_start_location)
	  error_with_file_and_line(start_filename, start_lineno,
				   "end of file while defining macro `%1'",
				   nm.contents());
	else
	  error("end of file while defining macro `%1'", nm.contents());
      }
      else {
	if (have_start_location)
	  error_with_file_and_line(start_filename, start_lineno,
				   "end of file while ignoring input lines");
	else
	  error("end of file while ignoring input lines");
      }
      tok.next();
      return;
    }
    if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
      if (c == 0)
	mac.append(n);
      else
	mac.append(c);
    }
    bol = (c == '\n');
    c = get_copy(&n, 1);
  }
}

void define_macro()
{
  do_define_macro(DEFINE_NORMAL);
}

void append_macro()
{
  do_define_macro(DEFINE_APPEND);
}

void ignore()
{
  do_define_macro(DEFINE_IGNORE);
}

void remove_macro()
{
  for (;;) {
    symbol s = get_name();
    if (s.is_null())
      break;
    request_dictionary.remove(s);
  }
  skip_line();
}

void rename_macro()
{
  symbol s1 = get_name(1);
  if (!s1.is_null()) {
    symbol s2 = get_name(1);
    if (!s2.is_null())
      request_dictionary.rename(s1, s2);
  }
  skip_line();
}

void alias_macro()
{
  symbol s1 = get_name(1);
  if (!s1.is_null()) {
    symbol s2 = get_name(1);
    if (!s2.is_null()) {
      if (!request_dictionary.alias(s1, s2))
	warning(WARN_MAC, "`%1' not defined", s2.contents());
    }
  }
  skip_line();
}

void chop_macro()
{
  symbol s = get_name(1);
  if (!s.is_null()) {
    request_or_macro *p = lookup_request(s);
    macro *m = p->to_macro();
    if (!m)
      error("cannot chop request");
    else if (m->length == 0)
      error("cannot chop empty macro");
    else
      m->length -= 1;
  }
  skip_line();
}

void asciify_macro()
{
  symbol s = get_name(1);
  if (!s.is_null()) {
    request_or_macro *p = lookup_request(s);
    macro *m = p->to_macro();
    if (!m)
      error("cannot asciify request");
    else {
      macro am;
      string_iterator iter(*m);
      for (;;) {
	node *nd;
	int c = iter.get(&nd);
	if (c == EOF)
	  break;
	if (c != 0)
	  am.append(c);
	else
	  nd->asciify(&am);
      }
      *m = am;
    }
  }
  skip_line();
}

void interpolate_number_reg(symbol nm, int inc)
{
  reg *r = lookup_number_reg(nm);
  if (inc < 0)
    r->decrement();
  else if (inc > 0)
    r->increment();
  input_stack::push(make_temp_iterator(r->get_string()));
}

static void interpolate_number_format(symbol nm)
{
  reg *r = (reg *)number_reg_dictionary.lookup(nm);
  if (r)
    input_stack::push(make_temp_iterator(r->get_format()));
}

static int get_delim_number(units *n, int si, int prev_value)
{
  token start;
  start.next();
  if (start.delimiter(1)) {
    tok.next();
    if (get_number(n, si, prev_value)) {
      if (start != tok)
	warning(WARN_DELIM, "closing delimiter does not match");
      return 1;
    }
  }
  return 0;
}

static int get_delim_number(units *n, int si)
{
  token start;
  start.next();
  if (start.delimiter(1)) {
    tok.next();
    if (get_number(n, si)) {
      if (start != tok)
	warning(WARN_DELIM, "closing delimiter does not match");
      return 1;
    }
  }
  return 0;
}

static int get_line_arg(units *n, int si, charinfo **cp)
{
  token start;
  start.next();
  if (start.delimiter(1)) {
    tok.next();
    if (get_number(n, si)) {
      if (tok.dummy())
	tok.next();
      if (start != tok) {
	*cp = tok.get_char(1);
	tok.next();
      }
      if (start != tok)
	warning(WARN_DELIM, "closing delimiter does not match");
      return 1;
    }
  }
  return 0;
}

static int read_size(int *x)
{
  tok.next();
  int c = tok.ch();
  int inc = 0;
  if (c == '-') {
    inc = -1;
    tok.next();
    c = tok.ch();
  }
  else if (c == '+') {
    inc = 1;
    tok.next();
    c = tok.ch();
  }
  int val;
  int bad = 0;
  if (c == '(') {
    tok.next();
    c = tok.ch();
    if (!inc) {
      // allow an increment either before or after the left parenthesis
      if (c == '-') {
	inc = -1;
	tok.next();
	c = tok.ch();
      }
      else if (c == '+') {
	inc = 1;
	tok.next();
	c = tok.ch();
      }
    }
    if (!csdigit(c))
      bad = 1;
    else {
      val = c - '0';
      tok.next();
      c = tok.ch();
      if (!csdigit(c))
	bad = 1;
      else {
	val = val*10 + (c - '0');
	val *= sizescale;
      }
    }
  }
  else if (csdigit(c)) {
    val = c - '0';
    if (!inc && c != '0' && c < '4') {
      tok.next();
      c = tok.ch();
      if (!csdigit(c))
	bad = 1;
      else
	val = val*10 + (c - '0');
    }
    val *= sizescale;
  }
  else if (!tok.delimiter(1))
    return 0;
  else {
    token start(tok);
    tok.next();
    if (!(inc 
	  ? get_number(&val, 'z')
	  : get_number(&val, 'z', curenv->get_requested_point_size())))
      return 0;
    if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
      if (start.ch() == '[')
	error("missing `]'");
      else
	error("missing closing delimiter");
      return 0;
    }
  }
  if (!bad) {
    switch (inc) {
    case 0:
      *x = val;
      break;
    case 1:
      *x = curenv->get_requested_point_size() + val;
      break;
    case -1:
      *x = curenv->get_requested_point_size() - val;
      break;
    default:
      assert(0);
    }
    return 1;
  }
  else {
    error("bad digit in point size");
    return 0;
  }
}

static symbol get_delim_name()
{
  token start;
  start.next();
  if (start.eof()) {
    error("end of input at start of delimited name");
    return NULL_SYMBOL;
  }
  if (start.newline()) {
    error("can't delimit name with a newline");
    return NULL_SYMBOL;
  }
  int start_level = input_stack::get_level();
  char abuf[ABUF_SIZE];
  char *buf = abuf;
  int buf_size = ABUF_SIZE;
  int i = 0;
  for (;;) {
    if (i + 1 > buf_size) {
      if (buf == abuf) {
	buf = new char [ABUF_SIZE*2];
	memcpy(buf, abuf, buf_size);
	buf_size = ABUF_SIZE*2;
      }
      else {
	char *old_buf = buf;
	buf = new char[buf_size*2];
	memcpy(buf, old_buf, buf_size);
	buf_size *= 2;
	delete old_buf;
      }
    }
    tok.next();
    if (tok == start
	&& (compatible_flag || input_stack::get_level() == start_level))
      break;
    if ((buf[i] = tok.ch()) == 0) {
      error("missing delimiter (got %1)", tok.description());
      if (buf != abuf)
	delete buf;
      return NULL_SYMBOL;
    }
    i++;
  }
  buf[i] = '\0';
  if (buf == abuf) {
    if (i == 0) {
      error("empty delimited name");
      return NULL_SYMBOL;
    }
    else
      return symbol(buf);
  }
  else {
    symbol s(buf);
    delete buf;
    return s;
  }
}


// Implement \R

static void do_register()
{
  token start;
  start.next();
  if (!start.delimiter(1))
    return;
  tok.next();
  symbol nm = get_long_name(1);
  if (nm.is_null())
    return;
  while (tok.space())
    tok.next();
  reg *r = (reg *)number_reg_dictionary.lookup(nm);
  int prev_value;
  if (!r || !r->get_value(&prev_value))
    prev_value = 0;
  int val;
  if (!get_number(&val, 'u', prev_value))
    return;
  if (start != tok)
    warning(WARN_DELIM, "closing delimiter does not match");
  if (r)
    r->set_value(val);
  else
    set_number_reg(nm, val);
}

// this implements the \w escape sequence

static void do_width()
{
  token start;
  start.next();
  int start_level = input_stack::get_level();
  environment env(curenv);
  environment *oldenv = curenv;
  curenv = &env;
  for (;;) {
    tok.next();
    if (tok.eof()) {
      warning(WARN_DELIM, "missing closing delimiter");
      break;
    }
    if (tok.newline()) {
      warning(WARN_DELIM, "missing closing delimiter");
      input_stack::push(make_temp_iterator("\n"));
      break;
    }
    if (tok == start
	&& (compatible_flag || input_stack::get_level() == start_level))
      break;
    tok.process();
  }
  env.wrap_up_tab();
  units x = env.get_input_line_position().to_units();
  input_stack::push(make_temp_iterator(itoa(x)));
  env.width_registers();
  curenv = oldenv;
}

charinfo *page_character;

void set_page_character()
{
  page_character = get_optional_char();
  skip_line();
}

static const symbol percent_symbol("%");

void read_title_parts(node **part, hunits *part_width)
{
  tok.skip();
  if (tok.newline() || tok.eof())
    return;
  token start(tok);
  int start_level = input_stack::get_level();
  tok.next();
  for (int i = 0; i < 3; i++) {
    while (!tok.newline() && !tok.eof()) {
      if (tok == start
	  && (compatible_flag || input_stack::get_level() == start_level)) {
	tok.next();
	break;
      }
      if (page_character != 0 && tok.get_char() == page_character)
	interpolate_number_reg(percent_symbol, 0);
      else
	tok.process();
      tok.next();
    }
    curenv->wrap_up_tab();
    part_width[i] = curenv->get_input_line_position();
    part[i] = curenv->extract_output_line();
  }
  while (!tok.newline() && !tok.eof())
    tok.next();
}

class non_interpreted_node : public node {
  macro mac;
public:
  non_interpreted_node(const macro &);
  int interpret(macro *);
  node *copy();
  int same(node *);
  const char *type();
};

non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
{
}

int non_interpreted_node::same(node *nd)
{
  return mac == ((non_interpreted_node *)nd)->mac;
}

const char *non_interpreted_node::type()
{
  return "non_interpreted_node";
}

node *non_interpreted_node::copy()
{
  return new non_interpreted_node(mac);
}

int non_interpreted_node::interpret(macro *m)
{
  string_iterator si(mac);
  node *n;
  for (;;) {
    int c = si.get(&n);
    if (c == EOF)
      break;
    if (c == 0)
      m->append(n);
    else
      m->append(c);
  }
  return 1;
}

static node *do_non_interpreted()
{
  node *n;
  int c;
  macro mac;
  while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
    if (c == 0)
      mac.append(n);
    else
      mac.append(c);
  if (c == EOF || c == '\n') {
    error("missing \\?");
    return 0;
  }
  return new non_interpreted_node(mac);
}

node *do_special()
{
  token start;
  start.next();
  int start_level = input_stack::get_level();
  macro mac;
  for (tok.next();
       tok != start || input_stack::get_level() != start_level;
       tok.next()) {
    if (tok.eof()) {
      warning(WARN_DELIM, "missing closing delimiter");
      return 0;
    }
    if (tok.newline()) {
      input_stack::push(make_temp_iterator("\n"));
      warning(WARN_DELIM, "missing closing delimiter");
      break;
    }
    unsigned char c;
    if (tok.space())
      c = ' ';
    else if (tok.tab())
      c = '\t';
    else if (tok.leader())
      c = '\001';
    else if (tok.backspace())
      c = '\b';
    else
      c = tok.ch();
    if (c == '\0')
      error("%1 is illegal within \\X", tok.description());
    else 
      mac.append(c);
  }
  return new special_node(mac);
}

void special_node::tprint(troff_output_file *out)
{
  tprint_start(out);
  string_iterator iter(mac);
  for (;;) {
    int c = iter.get(NULL);
    if (c == EOF)
      break;
    for (const char *s = ::asciify(c); *s; s++)
      tprint_char(out, *s);
  }
  tprint_end(out);
}

int get_file_line(const char **filename, int *lineno)
{
  return input_stack::get_location(0, filename, lineno);
}

void line_file()
{
  int n;
  if (get_integer(&n)) {
    const char *filename = 0;
    if (has_arg()) {
      symbol s = get_long_name();
      filename = s.contents();
    }
    (void)input_stack::set_location(filename, n-1);
  }
  skip_line();
}

static int nroff_mode = 0;

static void nroff_request()
{
  nroff_mode = 1;
  skip_line();
}

static void troff_request()
{
  nroff_mode = 0;
  skip_line();
}

static void skip_alternative()
{
  int level = 0;
  // ensure that ``.if 0\{'' works as expected
  if (tok.left_brace())
    level++;
  int c;
  for (;;) {
    c = input_stack::get(NULL);
    if (c == EOF)
      break;
    if (c == ESCAPE_LEFT_BRACE)
      ++level;
    else if (c == ESCAPE_RIGHT_BRACE)
      --level;
    else if (c == escape_char && escape_char > 0)
      switch(input_stack::get(NULL)) {
      case '{':
	++level;
	break;
      case '}':
	--level;
	break;
      case '"':
	while ((c = input_stack::get(NULL)) != '\n' && c != EOF)
	  ;
      }
    /*
      Note that the level can properly be < 0, eg
	
	.if 1 \{\
	.if 0 \{\
	.\}\}

      So don't give an error message in this case.
    */
    if (level <= 0 && c == '\n')
      break;
  }
  tok.next();
}

static void begin_alternative()
{
  while (tok.space() || tok.left_brace())
    tok.next();
}


static int_stack if_else_stack;

int do_if_request()
{
  int invert = 0;
  while (tok.space())
    tok.next();
  while (tok.ch() == '!') {
    tok.next();
    invert = !invert;
  }
  int result;
  unsigned char c = tok.ch();
  if (c == 't') {
    tok.next();
    result = !nroff_mode;
  }
  else if (c == 'n') {
    tok.next();
    result = nroff_mode;
  }
  else if (c == 'v') {
    tok.next();
    result = 0;
  }
  else if (c == 'o') {
    result = (topdiv->get_page_number() & 1);
    tok.next();
  }
  else if (c == 'e') {
    result = !(topdiv->get_page_number() & 1);
    tok.next();
  }
  else if (c == 'd' || c == 'r') {
    tok.next();
    symbol nm = get_name(1);
    if (nm.is_null()) {
      skip_alternative();
      return 0;
    }
    result = (c == 'd' 
	      ? request_dictionary.lookup(nm) != 0
	      : number_reg_dictionary.lookup(nm) != 0);
  }
  else if (c == 'c') {
    tok.next();
    tok.skip();
    charinfo *ci = tok.get_char(1);
    if (ci == 0) {
      skip_alternative();
      return 0;
    }
    result = character_exists(ci, curenv);
    tok.next();
  }
  else if (tok.space())
    result = 0;
  else if (tok.delimiter()) {
    token delim = tok;
    int delim_level = input_stack::get_level();
    environment env1(curenv);
    environment env2(curenv);
    environment *oldenv = curenv;
    curenv = &env1;
    for (int i = 0; i < 2; i++) {
      for (;;) {
	tok.next();
	if (tok.newline() || tok.eof()) {
	  warning(WARN_DELIM, "missing closing delimiter");
	  tok.next();
	  curenv = oldenv;
	  return 0;
	}
	if (tok == delim
	    && (compatible_flag || input_stack::get_level() == delim_level))
	  break;
	tok.process();
      }
      curenv = &env2;
    }
    node *n1 = env1.extract_output_line();
    node *n2 = env2.extract_output_line();
    result = same_node_list(n1, n2);
    delete_node_list(n1);
    delete_node_list(n2);
    tok.next();
    curenv = oldenv;
  }
  else {
    units n;
    if (!get_number(&n, 'u')) {
      skip_alternative();
      return 0;
    }
    else
      result = n > 0;
  }
  if (invert)
    result = !result;
  if (result)
    begin_alternative();
  else
    skip_alternative();
  return result;
}

void if_else_request()
{
  if_else_stack.push(do_if_request());
}

void if_request()
{
  do_if_request();
}

void else_request()
{
  if (if_else_stack.is_empty()) {
    warning(WARN_EL, "unbalanced .el request");
    skip_alternative();
  }
  else {
    if (if_else_stack.pop())
      skip_alternative();
    else
      begin_alternative();
  }
}

static int while_depth = 0;
static int while_break_flag = 0;

void while_request()
{
  macro mac;
  int escaped = 0;
  int level = 0;
  mac.append(new token_node(tok));
  for (;;) {
    node *n;
    int c = input_stack::get(&n);
    if (c == EOF)
      break;
    if (c == 0) {
      escaped = 0;
      mac.append(n);
    }
    else if (escaped) {
      if (c == '{')
	level += 1;
      else if (c == '}')
	level -= 1;
      escaped = 0;
      mac.append(c);
    }
    else {
      if (c == ESCAPE_LEFT_BRACE)
	level += 1;
      else if (c == ESCAPE_RIGHT_BRACE)
	level -= 1;
      else if (c == escape_char)
	escaped = 1;
      mac.append(c);
      if (c == '\n' && level <= 0)
	break;
    }
  }
  if (level != 0)
    error("unbalanced \\{ \\}");
  else {
    while_depth++;
    input_stack::add_boundary();
    for (;;) {
      input_stack::push(new string_iterator(mac, "while loop"));
      tok.next();
      if (!do_if_request()) {
	while (input_stack::get(NULL) != EOF)
	  ;
	break;
      }
      process_input_stack();
      if (while_break_flag) {
	while_break_flag = 0;
	break;
      }
    }
    input_stack::remove_boundary();
    while_depth--;
  }
  tok.next();
}

void while_break_request()
{
  if (!while_depth) {
    error("no while loop");
    skip_line();
  }
  else {
    while_break_flag = 1;
    while (input_stack::get(NULL) != EOF)
      ;
    tok.next();
  }
}

void while_continue_request()
{
  if (!while_depth) {
    error("no while loop");
    skip_line();
  }
  else {
    while (input_stack::get(NULL) != EOF)
      ;
    tok.next();
  }
}

// .so

void source()
{
  symbol nm = get_long_name(1);
  if (nm.is_null())
    skip_line();
  else {
    while (!tok.newline() && !tok.eof())
      tok.next();
    errno = 0;
    FILE *fp = fopen(nm.contents(), "r");
    if (fp)
      input_stack::push(new file_iterator(fp, nm.contents()));
    else
      error("can't open `%1': %2", nm.contents(), strerror(errno));
    tok.next();
  }
}

const char *asciify(int c)
{
  static char buf[3];
  buf[0] = escape_char == '\0' ? '\\' : escape_char;
  buf[1] = buf[2] = '\0';
  switch (c) {
  case ESCAPE_QUESTION:
    buf[1] = '?';
    break;
  case ESCAPE_AMPERSAND:
    buf[1] = '&';
    break;
  case ESCAPE_UNDERSCORE:
    buf[1] = '_';
    break;
  case ESCAPE_BAR:
    buf[1] = '|';
    break;
  case ESCAPE_CIRCUMFLEX:
    buf[1] = '^';
    break;
  case ESCAPE_LEFT_BRACE:
    buf[1] = '{';
    break;
  case ESCAPE_RIGHT_BRACE:
    buf[1] = '}';
    break;
  case ESCAPE_LEFT_QUOTE:
    buf[1] = '`';
    break;
  case ESCAPE_RIGHT_QUOTE:
    buf[1] = '\'';
    break;
  case ESCAPE_HYPHEN:
    buf[1] = '-';
    break;
  case ESCAPE_BANG:
    buf[1] = '!';
    break;
  case ESCAPE_c:
    buf[1] = 'c';
    break;
  case ESCAPE_e:
    buf[1] = 'e';
    break;
  case ESCAPE_E:
    buf[1] = 'E';
    break;
  case ESCAPE_PERCENT:
    buf[1] = '%';
    break;
  case ESCAPE_SPACE:
    buf[1] = ' ';
    break;
  default:
    if (illegal_input_char(c))
      buf[0] = '\0';
    else
      buf[0] = c;
    break;
  }
  return buf;
}
  

const char *input_char_description(int c)
{
  switch (c) {
  case '\n':
    return "a newline character";
  case '\b':
    return "a backspace character";
  case '\001':
    return "a leader character";
  case '\t':
    return "a tab character ";
  case ' ':
    return "a space character";
  case '\0':
    return "a node";
  }
  static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
  if (illegal_input_char(c)) {
    const char *s = asciify(c);
    if (*s) {
      buf[0] = '`';
      strcpy(buf + 1, s);
      strcat(buf, "'");
      return buf;
    }
    sprintf(buf, "magic character code %d", c);
    return buf;
  }
  if (csprint(c)) {
    buf[0] = '`';
    buf[1] = c;
    buf[2] = '\'';
    return buf;
  }
  sprintf(buf, "character code %d", c);
  return buf;
}

// .tm

void terminal()
{
  int c;
  while ((c = get_copy(NULL)) == ' ' || c == '\t')
    ;
  for (; c != '\n' && c != EOF; c = get_copy(NULL))
    fputs(asciify(c), stderr);
  fputc('\n', stderr);
  fflush(stderr);
  tok.next();
}

dictionary stream_dictionary(20);

void do_open(int append)
{
  symbol stream = get_name(1);
  if (!stream.is_null()) {
    symbol filename = get_long_name(1);
    if (!filename.is_null()) {
      errno = 0;
      FILE *fp = fopen(filename.contents(), append ? "a" : "w");
      if (!fp) {
	error("can't open `%1' for %2: %3",
	      filename.contents(),
	      append ? "appending" : "writing",
	      strerror(errno));
	fp = (FILE *)stream_dictionary.remove(stream);
      }
      else
	fp = (FILE *)stream_dictionary.lookup(stream, fp);
      if (fp)
	fclose(fp);
    }
  }
  skip_line();
}

void open_request()
{
  do_open(0);
}

void opena_request()
{
  do_open(1);
}

void close_request()
{
  symbol stream = get_name(1);
  if (!stream.is_null()) {
    FILE *fp = (FILE *)stream_dictionary.remove(stream);
    if (!fp)
      error("no stream named `%1'", stream.contents());
    else
      fclose(fp);
  }
  skip_line();
}

void write_request()
{
  symbol stream = get_name(1);
  if (stream.is_null()) {
    skip_line();
    return;
  }
  FILE *fp = (FILE *)stream_dictionary.lookup(stream);
  if (!fp) {
    error("no stream named `%1'", stream.contents());
    skip_line();
    return;
  }
  int c;
  while ((c = get_copy(NULL)) == ' ')
    ;
  if (c == '"')
    c = get_copy(NULL);
  for (; c != '\n' && c != EOF; c = get_copy(NULL))
    fputs(asciify(c), fp);
  fputc('\n', fp);
  tok.next();
}

static void init_charset_table()
{
  char buf[16];
  strcpy(buf, "char");
  for (int i = 0; i < 256; i++) {
    strcpy(buf + 4, itoa(i));
    charset_table[i] = get_charinfo(symbol(buf));
    charset_table[i]->set_ascii_code(i);
    if (csalpha(i))
      charset_table[i]->set_hyphenation_code(cmlower(i));
  }
  charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
  charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
  charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
  charset_table['-']->set_flags(charinfo::BREAK_AFTER);
  charset_table['"']->set_flags(charinfo::TRANSPARENT);
  charset_table['\'']->set_flags(charinfo::TRANSPARENT);
  charset_table[')']->set_flags(charinfo::TRANSPARENT);
  charset_table[']']->set_flags(charinfo::TRANSPARENT);
  charset_table['*']->set_flags(charinfo::TRANSPARENT);
  get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
  get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
  get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
  get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
  get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
  get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
  get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
  page_character = charset_table['%'];
}

void translate()
{
  tok.skip();
  while (!tok.newline() && !tok.eof()) {
    if (tok.space()) {
      // This is a really bizarre troff feature.
      tok.next();
      translate_space_to_dummy = tok.dummy();
      if (tok.newline() || tok.eof())
	break;
      tok.next();
      continue;
    }
    charinfo *ci1 = tok.get_char(1);
    if (ci1 == 0)
      break;
    tok.next();
    if (tok.newline() || tok.eof()) {
      ci1->set_special_translation(charinfo::TRANSLATE_SPACE);
      break;
    }
    if (tok.space())
      ci1->set_special_translation(charinfo::TRANSLATE_SPACE);
    else if (tok.dummy())
      ci1->set_special_translation(charinfo::TRANSLATE_DUMMY);
    else {
      charinfo *ci2 = tok.get_char(1);
      if (ci2 == 0)
	break;
      if (ci1 == ci2)
	ci1->set_translation(0);
      else
	ci1->set_translation(ci2);
    }
    tok.next();
  }
  skip_line();
}


void char_flags()
{
  int flags;
  if (get_integer(&flags))
    while (has_arg()) {
      charinfo *ci = tok.get_char(1);
      if (ci) {
	charinfo *tem = ci->get_translation();
	if (tem)
	  ci = tem;
	ci->set_flags(flags);
      }
      tok.next();
    }
  skip_line();
}

void hyphenation_code()
{
  tok.skip();
  while (!tok.newline() && !tok.eof()) {
    charinfo *ci = tok.get_char(1);
    if (ci == 0)
      break;
    tok.next();
    tok.skip();
    unsigned char c = tok.ch();
    if (c == 0) {
      error("hyphenation code must be ordinary character");
      break;
    }
    if (csdigit(c)) {
      error("hyphenation code cannot be digit");
      break;
    }
    ci->set_hyphenation_code(c);
    tok.next();
  }
  skip_line();
}

charinfo *token::get_char(int required)
{
  if (type == TOKEN_CHAR)
    return charset_table[c];
  if (type == TOKEN_SPECIAL)
    return get_charinfo(nm);
  if (type == TOKEN_NUMBERED_CHAR)
    return get_charinfo_by_number(val);
  if (type == TOKEN_ESCAPE) {
    if (escape_char != 0)
      return charset_table[escape_char];
    else {
      error("`\\e' used while no current escape character");
      return 0;
    }
  }
  if (required) {
    if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
      warning(WARN_MISSING, "missing normal or special character");
    else
      error("normal or special character expected (got %1)", description());
  }
  return 0;
}

charinfo *get_optional_char()
{
  while (tok.space())
    tok.next();
  charinfo *ci = tok.get_char();
  if (!ci) {
    if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
      error("normal or special character expected (got %1): "
	    "treated as missing",
	    tok.description());
  }
  else
    tok.next();
  return ci;
}

int token::add_to_node_list(node **pp)
{
  hunits w;
  node *n = 0;
  switch (type) {
  case TOKEN_CHAR:
    *pp = (*pp)->add_char(charset_table[c], curenv, &w);
    break;
  case TOKEN_CHAR_HEIGHT:
    curenv->set_char_height(val);
    break;
  case TOKEN_CHAR_SLANT:
    curenv->set_char_slant(val);
    break;
  case TOKEN_DUMMY:
    n = new dummy_node;
    break;
  case TOKEN_ESCAPE:
    if (escape_char != 0)
      *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w);
    break;
  case TOKEN_FONT_NAME:
    curenv->set_font(nm);
    break;
  case TOKEN_FONT_POSITION:
    curenv->set_font(val);
    break;
  case TOKEN_HYPHEN_INDICATOR:
    *pp = (*pp)->add_discretionary_hyphen();
    break;
  case TOKEN_ITALIC_CORRECTION:
    *pp = (*pp)->add_italic_correction(&w);
    break;
  case TOKEN_LEFT_BRACE:
    break;
  case TOKEN_MARK_INPUT:
    set_number_reg(nm, curenv->get_input_line_position().to_units());
    break;
  case TOKEN_NODE:
    n = nd;
    nd = 0;
    break;
  case TOKEN_NUMBERED_CHAR:
    *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w);
    break;
  case TOKEN_RIGHT_BRACE:
    break;
  case TOKEN_SIZE:
    curenv->set_size(val);
    break;
  case TOKEN_SPACE:
    n = new hmotion_node(curenv->get_space_width());
    break;
  case TOKEN_SPECIAL:
    *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w);
    break;
  default:
    return 0;
  }
  if (n) {
    n->next = *pp;
    *pp = n;
  }
  return 1;
}

void token::process()
{
  if (possibly_handle_first_page_transition())
    return;
  switch (type) {
  case TOKEN_BACKSPACE:
    curenv->add_node(new hmotion_node(-curenv->get_space_width()));
    break;
  case TOKEN_CHAR:
    curenv->add_char(charset_table[c]);
    break;
  case TOKEN_CHAR_HEIGHT:
    curenv->set_char_height(val);
    break;
  case TOKEN_CHAR_SLANT:
    curenv->set_char_slant(val);
    break;
  case TOKEN_DUMMY:
    curenv->add_node(new dummy_node);
    break;
  case TOKEN_EOF:
    assert(0);
    break;
  case TOKEN_EMPTY:
    assert(0);
    break;
  case TOKEN_ESCAPE:
    if (escape_char != 0)
      curenv->add_char(charset_table[escape_char]);
    break;
  case TOKEN_FONT_NAME:
    curenv->set_font(nm);
    break;
  case TOKEN_FONT_POSITION:
    curenv->set_font(val);
    break;
  case TOKEN_BEGIN_TRAP:
  case TOKEN_END_TRAP:
  case TOKEN_PAGE_EJECTOR:
    // these are all handled in process_input_stack()
    break;
  case TOKEN_HYPHEN_INDICATOR:
    curenv->add_hyphen_indicator();
    break;
  case TOKEN_INTERRUPT:
    curenv->interrupt();
    break;
  case TOKEN_ITALIC_CORRECTION:
    curenv->add_italic_correction();
    break;
  case TOKEN_LEADER:
    curenv->handle_tab(1);
    break;
  case TOKEN_LEFT_BRACE:
    break;
  case TOKEN_MARK_INPUT:
    set_number_reg(nm, curenv->get_input_line_position().to_units());
    break;
  case TOKEN_NEWLINE:
    curenv->newline();
    break;
  case TOKEN_NODE:
    curenv->add_node(nd);
    nd = 0;
    break;
  case TOKEN_NUMBERED_CHAR:
    curenv->add_char(get_charinfo_by_number(val));
    break;
  case TOKEN_REQUEST:
    // handled in process_input_stack
    break;
  case TOKEN_RIGHT_BRACE:
    break;
  case TOKEN_SIZE:
    curenv->set_size(val);
    break;
  case TOKEN_SPACE:
    curenv->space();
    break;
  case TOKEN_SPECIAL:
    curenv->add_char(get_charinfo(nm));
    break;
  case TOKEN_SPREAD:
    curenv->spread();
    break;
  case TOKEN_TAB:
    curenv->handle_tab(0);
    break;
  case TOKEN_TRANSPARENT:
    break;
  default:
    assert(0);
  }
}

class nargs_reg : public reg {
public:
  const char *get_string();
};

const char *nargs_reg::get_string()
{
  return itoa(input_stack::nargs());
}

class lineno_reg : public reg {
public:
  const char *get_string();
};

const char *lineno_reg::get_string()
{
  int line;
  const char *file;
  if (!input_stack::get_location(0, &file, &line))
    line = 0;
  return itoa(line);
}


class writable_lineno_reg : public general_reg {
public:
  writable_lineno_reg();
  void set_value(units);
  int get_value(units *);
};

writable_lineno_reg::writable_lineno_reg()
{
}

int writable_lineno_reg::get_value(units *res)
{
  int line;
  const char *file;
  if (!input_stack::get_location(0, &file, &line))
    return 0;
  *res = line;
  return 1;
}

void writable_lineno_reg::set_value(units n)
{
  input_stack::set_location(0, n);
}

class filename_reg : public reg {
public:
  const char *get_string();
};

const char *filename_reg::get_string()
{
  int line;
  const char *file;
  if (input_stack::get_location(0, &file, &line))
    return file;
  else
    return 0;
}


class constant_reg : public reg {
  const char *s;
public:
  constant_reg(const char *);
  const char *get_string();
};

constant_reg::constant_reg(const char *p) : s(p)
{
}

const char *constant_reg::get_string()
{
  return s;
}

constant_int_reg::constant_int_reg(int *q) : p(q)
{
}

const char *constant_int_reg::get_string()
{
  return itoa(*p);
}

void abort_request()
{
  int c;
  while ((c = get_copy(0)) == ' ')
    ;
  if (c == EOF || c == '\n')
    fputs("User Abort.", stderr);
  else {
    for (; c != '\n' && c != EOF; c = get_copy(NULL))
      fputs(asciify(c), stderr);
  }
  fputc('\n', stderr);
  cleanup_and_exit(1);
}
      
char *read_string()
{
  int len = 256;
  char *s = new char[len];
  int c;
  while ((c = get_copy(0)) == ' ')
    ;
  int i = 0;
  while (c != '\n' && c != EOF) {
    if (!illegal_input_char(c)) {
      if (i + 2 > len) {
	char *tem = s;
	s = new char[len*2];
	memcpy(s, tem, len);
	len *= 2;
	delete tem;
      }
      s[i++] = c;
    }
    c = get_copy(0);
  }
  s[i] = '\0';
  tok.next();
  if (i == 0) {
    delete s;
    return 0;
  }
  return s;
}

void pipe_output()
{
  if (the_output) {
    error("can't pipe: output already started");
    skip_line();
  }
  else {
    if ((pipe_command = read_string()) == 0)
      error("can't pipe to empty command");
  }
}

static int system_status;

void system_request()
{
  char *command = read_string();
  if (!command)
    error("empty command");
  else {
    system_status = system(command);
    delete command;
  }
}

void copy_file()
{
  if (curdiv == topdiv && !topdiv->first_page_begun) {
    handle_initial_request(COPY_FILE_REQUEST);
    return;
  }
  symbol filename = get_long_name(1);
  while (!tok.newline() && !tok.eof())
    tok.next();
  if (break_flag)
    curenv->do_break();
  if (!filename.is_null())
    curdiv->copy_file(filename.contents());
  tok.next();
}

#ifdef COLUMN

void vjustify()
{
  if (curdiv == topdiv && !topdiv->first_page_begun) {
    handle_initial_request(VJUSTIFY_REQUEST);
    return;
  }
  symbol type = get_long_name(1);
  if (!type.is_null())
    curdiv->vjustify(type);
  skip_line();
}

#endif /* COLUMN */

void transparent_file()
{
  if (curdiv == topdiv && !topdiv->first_page_begun) {
    handle_initial_request(TRANSPARENT_FILE_REQUEST);
    return;
  }
  symbol filename = get_long_name(1);
  while (!tok.newline() && !tok.eof())
    tok.next();
  if (break_flag)
    curenv->do_break();
  if (!filename.is_null()) {
    errno = 0;
    FILE *fp = fopen(filename.contents(), "r");
    if (!fp)
      error("can't open `%1': %2", filename.contents(), strerror(errno));
    else {
      int bol = 1;
      for (;;) {
	int c = getc(fp);
	if (c == EOF)
	  break;
	if (illegal_input_char(c))
	  warning(WARN_INPUT, "illegal input character code %1", int(c));
	else {
	  curdiv->transparent_output(c);
	  bol = c == '\n';
	}
      }
      if (!bol)
	curdiv->transparent_output('\n');
      fclose(fp);
    }
  }
  tok.next();
}

class page_range {
  int first;
  int last;
public:
  page_range *next;
  page_range(int, int, page_range *);
  int contains(int n);
};

page_range::page_range(int i, int j, page_range *p)
: first(i), last(j), next(p)
{
}

int page_range::contains(int n)
{
  return n >= first && (last <= 0 || n <= last);
}

page_range *output_page_list = 0;

int in_output_page_list(int n)
{
  if (!output_page_list)
    return 1;
  for (page_range *p = output_page_list; p; p = p->next)
    if (p->contains(n))
      return 1;
  return 0;
}

static void parse_output_page_list(char *p)
{
  for (;;) {
    int i;
    if (*p == '-')
      i = 1;
    else if (csdigit(*p)) {
      i = 0;
      do
	i = i*10 + *p++ - '0';
      while (csdigit(*p));
    }
    else
      break;
    int j;
    if (*p == '-') {
      p++;
      j = 0;
      if (csdigit(*p)) {
	do
	  j = j*10 + *p++ - '0';
	while (csdigit(*p));
      }
    }
    else
      j = i;
    output_page_list = new page_range(i, j, output_page_list);
    if (*p != ',')
      break;
    ++p;
  }
  if (*p != '\0') {
    error("bad output page list");
    output_page_list = 0;
  }
}

struct string_list {
  const char *s;
  string_list *next;
  string_list(const char *ss) : s(ss), next(0) {}
};

string_list *mac_dirs;
string_list *font_dirs;

// open filename, searching in dirs and put the result in pathp

static FILE *open_file(const char *filename, string_list *dirs, char **pathp)
{
  int fnlen = strlen(filename);
  while (dirs) {
    int dlen = 0;
    int need_slash = 0;
    if (dirs->s) {
      dlen = strlen(dirs->s);
      if (dlen && dirs->s[dlen-1] != '/')
	need_slash = 1;
    }
    char *path = new char[dlen + need_slash + fnlen + 1];
    if (dirs->s)
      strcpy(path, dirs->s);
    else
      path[0] = 0;
    if (need_slash)
      strcat(path, "/");
    strcat(path, filename);
    errno = 0;
    FILE *fp = fopen(path, "r");
    if (fp) {
      *pathp = path;
      return fp;
    }
    delete path;
    dirs = dirs->next;
  }
  return 0;
}

static void add_string(const char *s, string_list **p)
{
  while (*p)
    p = &((*p)->next);
  *p = new string_list(s);
}

static void init_dirs(const char *e, const char *standard, string_list **dirs)
{
  char *var = getenv(e);
  if (!var) {
    var = new char[strlen(standard)+1];
    strcpy(var, standard);
  }
  for (char *p = strtok(var, ":"); p; p = strtok(NULL, ":"))
    add_string(p, dirs);
}


#define MACRO_PREFIX "tmac."

static FILE *open_mac_file(const char *mac, char **path)
{
  char *s = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
  strcpy(s, MACRO_PREFIX);
  strcat(s, mac);
  FILE *fp = open_file(s, mac_dirs, path);
  delete s;
  return fp;
}

static void process_macro_file(const char *mac)
{
  char *path;
  FILE *fp = open_mac_file(mac, &path);
  if (!fp)
    fatal("can't find macro file %1", mac);
  const char *s = symbol(path).contents();
  delete path;
  input_stack::push(new file_iterator(fp, s));
  tok.next();
  process_input_stack();
}

void macro_source()
{
  symbol nm = get_long_name(1);
  if (nm.is_null())
    skip_line();
  else {
    while (!tok.newline() && !tok.eof())
      tok.next();
    char *path;
    FILE *fp = open_file(nm.contents(), mac_dirs, &path);
    if (fp) {
      input_stack::push(new file_iterator(fp, symbol(path).contents()));
      delete path;
    }
    else
      error("can't find macro file `%1'", nm.contents());
    tok.next();
  }
}
  

static void process_input_file(const char *name)
{
  FILE *fp;
  if (strcmp(name, "-") == 0) {
    clearerr(stdin);
    fp = stdin;
  }
  else {
    errno = 0;
    fp = fopen(name, "r");
    if (!fp)
      fatal("can't open `%1': %2", name, strerror(errno));
  }
  input_stack::push(new file_iterator(fp, name));
  tok.next();
  process_input_stack();
}

// make sure the_input is empty before calling this

static int evaluate_expression(const char *expr, units *res)
{
  input_stack::push(make_temp_iterator(expr));
  tok.next();
  int success = get_number(res, 'u');
  while (input_stack::get(NULL) != EOF)
    ;
  return success;
}

static void do_register_assignment(const char *s)
{
  const char *p = strchr(s, '=');
  if (!p) {
    char buf[2];
    buf[0] = s[0];
    buf[1] = 0;
    units n;
    if (evaluate_expression(s + 1, &n))
      set_number_reg(buf, n);
  }
  else {
    char *buf = new char[p - s + 1];
    memcpy(buf, s, p - s);
    buf[p - s] = 0;
    units n;
    if (evaluate_expression(p + 1, &n))
      set_number_reg(buf, n);
    delete buf;
  }
}

static void set_string(const char *name, const char *value)
{
  macro *m = new macro;
  for (const char *p = value; *p; p++)
    if (!illegal_input_char((unsigned char)*p))
      m->append(*p);
  request_dictionary.define(name, m);
}


static void do_string_assignment(const char *s)
{
  const char *p = strchr(s, '=');
  if (!p) {
    char buf[2];
    buf[0] = s[0];
    buf[1] = 0;
    set_string(buf, s + 1);
  }
  else {
    char *buf = new char[p - s + 1];
    memcpy(buf, s, p - s);
    buf[p - s] = 0;
    set_string(buf, p + 1);
    delete buf;
  }
}

#define USAGE_EXIT_CODE 1

void usage(const char *prog)
{
  errprint(
"usage: %1 -abivzCE -wname -Wname -dcstring -mname -nN -olist -rcN\n"
"       -Tname -Fdir -Mdir -Hfile [ files ]\n",
	  prog);
  exit(USAGE_EXIT_CODE);
}

int
#ifdef DUMP
normal_main
#else
main
#endif
    (int argc, char **argv)
{
  program_name = argv[0];
  static char stderr_buf[BUFSIZ];
  setbuf(stderr, stderr_buf);
  int c;
  string_list *macros = 0;
  string_list *register_assignments = 0;
  string_list *string_assignments = 0;
  const char *hyphen_file = HYPHENFILE;
  int iflag = 0;
  int tflag = 0;
  int fflag = 0;
  int nflag = 0;
  int next_page_number;
  opterr = 0;
  hresolution = vresolution = 1;
  const char *tem = getenv("GROFF_TYPESETTER");
  if (tem)
    device = tem;
  tem = getenv("GROFF_HYPHEN");
  if (tem)
    hyphen_file = tem;
  while ((c = getopt(argc, argv, "abivw:W:zCEf:m:n:o:r:d:F:H:M:T:tqs:")) != EOF)
    switch(c) {
    case 'v':
      {
	extern const char *version_string;
	fprintf(stderr, "GNU troff version %s\n", version_string);
	fflush(stderr);
	break;
      }
    case 'T':
      device = optarg;
      tflag = 1;
      break;
    case 'C':
      compatible_flag = 1;
      break;
    case 'M':
      add_string(optarg, &mac_dirs);
      break;
    case 'F':
      font::command_line_font_dir(optarg);
      break;
    case 'H':
      hyphen_file = optarg;
      break;
    case 'm':
      add_string(optarg, &macros);
      break;
    case 'E':
      inhibit_errors = 1;
      break;
    case 'w':
      enable_warning(optarg);
      break;
    case 'W':
      disable_warning(optarg);
      break;
    case 'i':
      iflag = 1;
      break;
    case 'b':
      backtrace_flag = 1;
      break;
    case 'a':
      ascii_output_flag = 1;
      break;
    case 'z':
      suppress_output_flag = 1;
      break;
    case 'n':
      if (sscanf(optarg, "%d", &next_page_number) == 1)
	nflag++;
      else
	error("bad page number");
      break;
    case 'o':
      parse_output_page_list(optarg);
      break;
    case 'd':
      if (*optarg == '\0')
	error("`-d' requires non-empty argument");
      else
	add_string(optarg, &string_assignments);
      break;
    case 'r':
      if (*optarg == '\0')
	error("`-r' requires non-empty argument");
      else
	add_string(optarg, &register_assignments);
      break;
    case 'f':
      default_family = symbol(optarg);
      fflag = 1;
      break;
    case 'q':
    case 's':
    case 't':
      // silently ignore these
      break;
    case '?':
      usage(argv[0]);
    default:
      assert(0);
    }
  set_string(".T", device);
  init_dirs(MACROPATH_ENVVAR, MACROPATH, &mac_dirs);
  init_charset_table();
  font::set_device_name(device);
  if (!font::load_desc())
    fatal("sorry, I can't continue");
  units_per_inch = font::res;
  hresolution = font::hor;
  vresolution = font::vert;
  sizescale = font::sizescale;
  tcommand_flag = font::tcommand;
  if (!fflag && font::family != 0 && *font::family != '\0')
    default_family = symbol(font::family);
  font_size::init_size_table(font::sizes);
  int i;
  int j = 1;
  if (font::style_table) {
    for (i = 0; font::style_table[i]; i++)
      mount_style(j++, symbol(font::style_table[i]));
  }
  for (i = 0; font::font_name_table[i]; i++)
    mount_font(j++, symbol(font::font_name_table[i]));
  curdiv = topdiv = new top_level_diversion;
  if (nflag)
    topdiv->set_next_page_number(next_page_number);
  init_input_requests();
  init_env_requests();
  init_div_requests();
#ifdef COLUMN
  init_column_requests();
#endif /* COLUMN */
  init_node_requests();
  number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
  init_registers();
  init_reg_requests();
  read_hyphen_file(hyphen_file);
  init_hyphen_requests();
  init_environments();
  while (string_assignments) {
    do_string_assignment(string_assignments->s);
    string_list *tem = string_assignments;
    string_assignments = string_assignments->next;
    delete tem;
  }
  while (register_assignments) {
    do_register_assignment(register_assignments->s);
    string_list *tem = register_assignments;
    register_assignments = register_assignments->next;
    delete tem;
  }
  while (macros) {
    process_macro_file(macros->s);
    string_list *tem = macros;
    macros = macros->next;
    delete tem;
  }
  for (i = optind; i < argc; i++)
    process_input_file(argv[i]);
  if (optind >= argc || iflag)
    process_input_file("-");
  exit_groff();
}

#ifdef DUMP

int dumped_main(int argc, char **argv)
{
  program_name = argv[0];
  int c;
  string_list *macros = 0;
  string_list *register_assignments = 0;
  string_list *string_assignments = 0;
  int iflag = 0;
  int nflag = 0;
  int next_page_number;
  opterr = 0;
  while ((c = getopt(argc, argv, "abivw:W:zECf:m:n:o:r:d:F:H:M:T:tqs:")) != EOF)
    switch(c) {
    case 'v':
      {
	extern const char *version_string;
	fprintf(stderr, "GNU troff version %s\n", version_string);
	fflush(stderr);
	break;
      }
    case 'f':
    case 'H':
    case 'T':
      error("`-%1' option illegal when dumped", char(c));
      break;
    case 'C':
      compatible_flag = 1;
      break;
    case 'M':
      add_string(optarg, &mac_dirs);
      break;
    case 'F':
      font::command_line_font_dir(optarg);
      break;
    case 'm':
      add_string(optarg, &macros);
      break;
    case 'E':
      inhibit_errors = 1;
      break;
    case 'w':
      enable_warning(optarg);
      break;
    case 'W':
      disable_warning(optarg);
      break;
    case 'i':
      iflag = 1;
      break;
    case 'b':
      backtrace_flag = 1;
      break;
    case 'a':
      ascii_output_flag = 1;
      break;
    case 'z':
      suppress_output_flag = 1;
      break;
    case 'n':
      if (sscanf(optarg, "%d", &next_page_number) == 1)
	nflag++;
      else
	error("bad page number");
      break;
    case 'o':
      parse_output_page_list(optarg);
      break;
    case 'd':
      if (*optarg == '\0')
	error("`-d' requires non-empty argument");
      else
	add_string(optarg, &string_assignments);
      break;
    case 'r':
      if (*optarg == '\0')
	error("`-r' requires non-empty argument");
      else
	add_string(optarg, &register_assignments);
      break;
    case 'q':
    case 's':
    case 't':
      // silently ignore these
      break;
    case '?':
      usage(argv[0]);
    default:
      assert(0);
    }
  init_dirs(MACROPATH_ENVVAR, MACROPATH, &mac_dirs);
  init_registers();
  if (nflag)
    topdiv->set_next_page_number(next_page_number);
  while (string_assignments) {
    do_string_assignment(string_assignments->s);
    string_list *tem = string_assignments;
    string_assignments = string_assignments->next;
    delete tem;
  }
  while (register_assignments) {
    do_register_assignment(register_assignments->s);
    string_list *tem = register_assignments;
    register_assignments = register_assignments->next;
    delete tem;
  }
  while (macros) {
    process_macro_file(macros->s);
    string_list *tem = macros;
    macros = macros->next;
    delete tem;
  }
  int i;
  for (i = optind; i < argc; i++)
    process_input_file(argv[i]);
  if (optind >= argc || iflag)
    process_input_file("-");
  exit_groff();
}

static int dumped = 0;

extern "C" {

int unexec(const char *new_name, const char *a_name, unsigned data_start = 0,
	   unsigned bss_start = 0, unsigned entry_address = 0);

#ifdef __GNUG__
#define CPLUS_INIT_FUNC __main
#else
#define CPLUS_INIT_FUNC _main
#endif

extern int CPLUS_INIT_FUNC();

int c_main(int argc, char **argv)
{
  if (dumped)
    return dumped_main(argc, argv);
  else {
    CPLUS_INIT_FUNC();
    return normal_main(argc, argv);
  }
}

#define OPTIND_INITIAL_VALUE 1

}

void dump_request()
{
  symbol new_name = get_long_name(1);
  if (new_name.is_null()) {
    skip_line();
    return;
  }
  symbol a_name = get_long_name(1);
  if (a_name.is_null()) {
    skip_line();
    return;
  }
  if (topdiv->first_page_begun)
    fatal("can't dump after first page begun");
  while (input_stack::get(NULL) != EOF)
    ;
  suppress_output_flag = 0;
  ascii_output_flag = 0;
  dumped = 1;
  optind = OPTIND_INITIAL_VALUE;
  number_reg_dictionary.remove(".A");
  while (output_page_list) {
    page_range *tem = output_page_list;
    output_page_list = output_page_list->next;
    delete tem;
  }
  while (mac_dirs) {
    string_list *tem = mac_dirs;
    mac_dirs = mac_dirs->next;
    delete tem;
  }
  font::forget_command_line_font_dirs();
  fflush(stderr);
  clearerr(stdin);
  // clean up the diversion_stack
  // clean up environment stack
  if (unexec(new_name.contents(), a_name.contents()) < 0)
    exit(1);
  else
    exit(0);
}

#endif /* DUMP */

void warn_request()
{
  int n;
  if (!has_arg())
    warning_mask = WARN_TOTAL;
  else if (get_integer(&n)) {
    if (n & ~WARN_TOTAL) {
      warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
      n &= WARN_TOTAL;
    }
    warning_mask = n;
  }
  skip_line();
}

static void init_registers()
{
#ifdef LONG_FOR_TIME_T
  long
#else /* not LONG_FOR_TIME_T */
  time_t
#endif /* not LONG_FOR_TIME_T */
    t = time(0);
  tm *tt = localtime(&t);
  set_number_reg("dw", int(tt->tm_wday + 1));
  set_number_reg("dy", int(tt->tm_mday));
  set_number_reg("mo", int(tt->tm_mon + 1));
  set_number_reg("yr", int(tt->tm_year));
  set_number_reg("$$", getpid());
  number_reg_dictionary.define(".A", new constant_reg(ascii_output_flag ? "1" : "0"));
}

void init_input_requests()
{
  init_request("ds", define_string);
  init_request("as", append_string);
  init_request("de", define_macro);
  init_request("am", append_macro);
  init_request("ig", ignore);
  init_request("rm", remove_macro);
  init_request("rn", rename_macro);
  init_request("if", if_request);
  init_request("ie", if_else_request);
  init_request("el", else_request);
  init_request("so", source);
  init_request("nx", next_file);
  init_request("pm", print_macros);
  init_request("eo", escape_off);
  init_request("ec", set_escape_char);
  init_request("pc", set_page_character);
  init_request("tm", terminal);
  init_request("ex", REQUEST_FUNCP(exit_request));
  init_request("em", end_macro);
  init_request("tr", translate);
  init_request("ab", abort_request);
  init_request("pi", pipe_output);
  init_request("cf", copy_file);
  init_request("sy", system_request);
  init_request("lf", line_file);
  init_request("cflags", char_flags);
  init_request("shift", shift);
  init_request("rd", read_request);
  init_request("cp", compatible);
  init_request("char", define_character);
  init_request("hcode", hyphenation_code);
  init_request("while", while_request);
  init_request("break", while_break_request);
  init_request("continue", while_continue_request);
  init_request("als", alias_macro);
  init_request("backtrace", backtrace_request);
  init_request("chop", chop_macro);
  init_request("asciify", asciify_macro);
  init_request("warn", warn_request);
#ifdef DUMP
  init_request("dump", dump_request);
#endif
  init_request("open", open_request);
  init_request("opena", opena_request);
  init_request("close", close_request);
  init_request("write", write_request);
  init_request("trf", transparent_file);
#ifdef WIDOW_CONTROL
  init_request("fpl", flush_pending_lines);
#endif /* WIDOW_CONTROL */
  init_request("nroff", nroff_request);
  init_request("troff", troff_request);
#ifdef COLUMN
  init_request("vj", vjustify);
#endif /* COLUMN */
  init_request("mso", macro_source);
  number_reg_dictionary.define("systat", new variable_reg(&system_status));
  number_reg_dictionary.define("slimit",
			       new variable_reg(&input_stack::limit));
  number_reg_dictionary.define(".$", new nargs_reg);
  number_reg_dictionary.define(".c", new lineno_reg);
  number_reg_dictionary.define("c.", new writable_lineno_reg);
  number_reg_dictionary.define(".F", new filename_reg);
  number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
  number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
  number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
  number_reg_dictionary.define(".R", new constant_reg("10000"));
  extern const char *major_version;
  number_reg_dictionary.define(".x", new constant_reg(major_version));
  extern const char *minor_version;
  number_reg_dictionary.define(".y", new constant_reg(minor_version));
  number_reg_dictionary.define(".g", new constant_reg("1"));
  number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
}

object_dictionary request_dictionary(501);

void init_request(const char *s, REQUEST_FUNCP f)
{
  request_dictionary.define(s, new request(f));
}

static request_or_macro *lookup_request(symbol nm)
{
  assert(!nm.is_null());
  request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
  if (p == 0) {
    warning(WARN_MAC, "`%1' not defined", nm.contents());
    p = new macro;
    request_dictionary.define(nm, p);
  }
  return p;
}


node *charinfo_to_node(charinfo *ci, const environment *envp)
{
  macro *mac = ci->set_macro(0);
  assert(mac != 0);
  environment *oldenv = curenv;
  environment env(envp);
  curenv = &env;
  font_size sz = env.get_font_size();
  token old_tok = tok;
  input_stack::add_boundary();
  string_iterator *si = new string_iterator(*mac, "composite character", ci->nm);
  input_stack::push(si);
  // we don't use process_input_stack, because we don't want to recognise
  // requests
  for (;;) {
    tok.next();
    if (tok.eof())
      break;
    if (tok.newline()) {
      error("composite character mustn't contain newline");
      while (!tok.eof())
	tok.next();
      break;
    }
    else
      tok.process();
  }
  node *n = curenv->extract_output_line();
  composite_node *cn = new composite_node(n, ci, sz);
  input_stack::remove_boundary();
  ci->set_macro(mac);
  tok = old_tok;
  curenv = oldenv;
  return cn;
}

static node *read_draw_node()
{
  token start;
  start.next();
  if (!start.delimiter(1)){
    do {
      tok.next();
    } while (tok != start && !tok.newline() && !tok.eof());
  }
  else {
    tok.next();
    if (tok == start)
      error("missing argument");
    else {
      unsigned char type = tok.ch();
      tok.next();
      int maxpoints = 10;
      hvpair *point = new hvpair[maxpoints];
      int npoints = 0;
      int no_last_v = 0;
      int err = 0;
      int i;
      for (i = 0; tok != start; i++) {
	if (i == maxpoints) {
	  hvpair *oldpoint = point;
	  point = new hvpair[maxpoints*2];
	  for (int j = 0; j < maxpoints; j++)
	    point[j] = oldpoint[j];
	  maxpoints *= 2;
	  delete oldpoint;
	}
	if (!get_hunits(&point[i].h, 'm')) {
	  err = 1;
	  break;
	}
	++npoints;
	tok.skip();
	point[i].v = V0;
	if (tok == start) {
	  no_last_v = 1;
	  break;
	}
	if (!get_vunits(&point[i].v, 'v')) {
	  err = 1;
	  break;
	}
	tok.skip();
      }
      while (tok != start && !tok.newline() && !tok.eof())
	tok.next();
      if (!err) {
	switch (type) {
	case 'l':
	  if (npoints != 1 || no_last_v) {
	    error("two arguments needed for line");
	    npoints = 1;
	  }
	  break;
	case 'c':
	  if (npoints != 1 || !no_last_v) {
	    error("one argument needed for circle");
	    npoints = 1;
	    point[0].v = V0;
	  }
	  break;
	case 'e':
	  if (npoints != 1 || no_last_v) {
	    error("two arguments needed for ellipse");
	    npoints = 1;
	  }
	  break;
	case 'a':
	  if (npoints != 2 || no_last_v) {
	    error("four arguments needed for arc");
	    npoints = 2;
	  }
	  break;
	case '~':
	  if (no_last_v)
	    error("even number of arguments needed for spline");
	  break;
	default:
	  // silently pass it through
	  break;
	}
	draw_node *dn = new draw_node(type, point, npoints,
				      curenv->get_font_size());
	delete point;
	return dn;
      }
      else {
	delete point;
      }
    }
  }
  return 0;
}

static struct {
  const char *name;
  int mask;
} warning_table[] = {
  "char", WARN_CHAR,
  "range", WARN_RANGE,
  "break", WARN_BREAK,
  "delim", WARN_DELIM,
  "el", WARN_EL,
  "scale", WARN_SCALE,
  "number", WARN_NUMBER,
  "syntax", WARN_SYNTAX,
  "tab", WARN_TAB,
  "right-brace", WARN_RIGHT_BRACE,
  "missing", WARN_MISSING,
  "input", WARN_INPUT,
  "escape", WARN_ESCAPE,
  "space", WARN_SPACE,
  "di", WARN_DI,
  "mac", WARN_MAC,
  "reg", WARN_REG,
  "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG),
  "w", WARN_TOTAL,
  "default", DEFAULT_WARNING_MASK,
};

static int lookup_warning(const char *name)
{
  for (int i = 0;
       i < sizeof(warning_table)/sizeof(warning_table[0]);
       i++)
    if (strcmp(name, warning_table[i].name) == 0)
      return warning_table[i].mask;
  return 0;
}

static void enable_warning(const char *name)
{
  int mask = lookup_warning(name);
  if (mask)
    warning_mask |= mask;
  else
    error("unknown warning `%1'", name);
}

static void disable_warning(const char *name)
{
  int mask = lookup_warning(name);
  if (mask)
    warning_mask &= ~mask;
  else
    error("unknown warning `%1'", name);
}

enum error_type { WARNING, ERROR, FATAL };

static void do_error(error_type type, 
		     const char *format, 
		     const errarg &arg1,
		     const errarg &arg2,
		     const errarg &arg3)
{
  const char *filename;
  int lineno;
  if (inhibit_errors && type < FATAL)
    return;
  if (backtrace_flag)
    input_stack::backtrace();
  if (!get_file_line(&filename, &lineno))
    filename = 0;
  if (filename)
    errprint("%1:%2: ", filename, lineno);
  else if (program_name)
    fprintf(stderr, "%s: ", program_name);
  switch (type) {
  case FATAL:
    fputs("fatal error: ", stderr);
    break;
  case ERROR:
    break;
  case WARNING:
    fputs("warning: ", stderr);
    break;
  }
  errprint(format, arg1, arg2, arg3);
  fputc('\n', stderr);
  fflush(stderr);
  if (type == FATAL)
    cleanup_and_exit(1);
}

int warning(warning_type t,
	    const char *format,
	    const errarg &arg1,
	    const errarg &arg2,
	    const errarg &arg3)
{
  if ((t & warning_mask) != 0) {
    do_error(WARNING, format, arg1, arg2, arg3);
    return 1;
  }
  else
    return 0;
}

void error(const char *format, 
	   const errarg &arg1,
	   const errarg &arg2,
	   const errarg &arg3)
{
  do_error(ERROR, format, arg1, arg2, arg3);
}

void fatal(const char *format, 
	   const errarg &arg1,
	   const errarg &arg2,
	   const errarg &arg3)
{
  do_error(FATAL, format, arg1, arg2, arg3);
}

void fatal_with_file_and_line(const char *filename, int lineno,
			      const char *format,
			      const errarg &arg1,
			      const errarg &arg2,
			      const errarg &arg3)
{
  fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
  errprint(format, arg1, arg2, arg3);
  fputc('\n', stderr);
  fflush(stderr);
  cleanup_and_exit(1);
}

void error_with_file_and_line(const char *filename, int lineno,
			      const char *format,
			      const errarg &arg1,
			      const errarg &arg2,
			      const errarg &arg3)
{
  fprintf(stderr, "%s:%d: error: ", filename, lineno);
  errprint(format, arg1, arg2, arg3);
  fputc('\n', stderr);
  fflush(stderr);
}

dictionary charinfo_dictionary(501);

charinfo *get_charinfo(symbol nm)
{
  void *p = charinfo_dictionary.lookup(nm);
  if (p != 0)
    return (charinfo *)p;
  charinfo *cp = new charinfo(nm);
  (void)charinfo_dictionary.lookup(nm, cp);
  return cp;
}

int charinfo::next_index = 0;

charinfo::charinfo(symbol s)
: nm(s), hyphenation_code(0), translation(0), flags(0), ascii_code(0),
  special_translation(TRANSLATE_NONE), mac(0), not_found(0)
{
  index = next_index++;
}

void charinfo::set_hyphenation_code(unsigned char c)
{
  hyphenation_code = c;
}

void charinfo::set_translation(charinfo *ci)
{
  translation = ci;
  special_translation = TRANSLATE_NONE;
}

void charinfo::set_special_translation(int c)
{
  special_translation = c;
  translation = 0;
}

void charinfo::set_ascii_code(unsigned char c)
{
  ascii_code = c;
}

macro *charinfo::set_macro(macro *m)
{
  macro *tem = mac;
  mac = m;
  return tem;
}

void charinfo::set_number(int n)
{
  number = n;
  flags |= NUMBERED;
}

int charinfo::get_number()
{
  assert(flags & NUMBERED);
  return number;
}

symbol UNNAMED_SYMBOL("---");

// For numbered characters not between 0 and 255, we make a symbol out
// of the number and store them in this dictionary.

dictionary numbered_charinfo_dictionary(11);

charinfo *get_charinfo_by_number(int n)
{
  static charinfo *number_table[256];

  if (n >= 0 && n < 256) {
    charinfo *ci = number_table[n];
    if (!ci) {
      ci = new charinfo(UNNAMED_SYMBOL);
      ci->set_number(n);
      number_table[n] = ci;
    }
    return ci;
  }
  else {
    symbol ns(itoa(n));
    charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
    if (!ci) {
      ci = new charinfo(UNNAMED_SYMBOL);
      ci->set_number(n);
      numbered_charinfo_dictionary.lookup(ns, ci);
    }
    return ci;
  }
}

int font::name_to_index(const char *nm)
{
  charinfo *ci;
  if (nm[1] == 0)
    ci = charset_table[nm[0] & 0xff];
  else if (nm[0] == '\\' && nm[2] == 0)
    ci = get_charinfo(symbol(nm + 1));
  else
    ci = get_charinfo(symbol(nm));
  if (ci == 0)
    return -1;
  else
    return ci->get_index();
}

int font::number_to_index(int n)
{
  return get_charinfo_by_number(n)->get_index();
}
