386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Sat, 23 Mar 1991 19:35:11 +0000 (11:35 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Sat, 23 Mar 1991 19:35:11 +0000 (11:35 -0800)
Work on file usr/src/usr.bin/groff/troff/input.cc

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/usr.bin/groff/troff/input.cc [new file with mode: 0644]

diff --git a/usr/src/usr.bin/groff/troff/input.cc b/usr/src/usr.bin/groff/troff/input.cc
new file mode 100644 (file)
index 0000000..4dc6488
--- /dev/null
@@ -0,0 +1,5800 @@
+// -*- 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. */
+
+
+#ifndef MACROPATH
+#define MACROPATH ".:/usr/local/lib/groff"
+#endif
+
+#ifndef DEVICE
+#define DEVICE "ps"
+#endif
+
+#ifndef HYPHENFILE
+#define HYPHENFILE "/usr/local/lib/groff/hyphen"
+#endif
+
+#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
+
+#include "groff.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"
+
+// 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 n) {}
+  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()) {
+    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()
+{
+  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 == ']')
+      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 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 = 0;
+       symbol s = read_escape_name();
+       const char *p = s.contents();
+       if (p != 0) {
+         if ((p[0] == '+' || p[0] == '-') && p[1] == '\0') {
+           inc = p[0] == '+' ? 1 : -1;
+           s = read_escape_name();
+           if (s.is_null())
+             break;
+         }
+         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 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 result = 1;
+  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())
+      result = 0;
+  }
+  return result;
+}
+
+#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 '"':
+       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':
+       {
+         symbol nm = read_escape_name();
+         if (nm.is_null())
+           break;
+         int inc = 0;
+         const char *p = nm.contents();
+         if (p != 0 && (p[0] == '+' || p[0] == '-') && p[1] == '\0') {
+           inc = p[0] == '+' ? 1 : -1;
+           nm = read_escape_name();
+           if (nm.is_null())
+             break;
+         }
+         interpolate_number_reg(nm, inc);
+         break;
+       }
+      case 'N':
+       if (!get_delim_number(&x, 0))
+         break;
+       if (x < 0 || x > 255) {
+         error("code %1 out of range", x);
+         break;
+       }
+       c = (unsigned char)x;
+       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 '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:
+  case TOKEN_NUMBERED_CHAR:
+    return c == t.c;
+  case TOKEN_SPECIAL:
+    return nm == t.nm;
+  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);
+#if 1
+  ~small_temp_iterator();
+  enum { BLOCK = 16 };
+  static small_temp_iterator *free_list;
+  void *operator new(size_t);
+  void operator delete(void *);
+#endif
+  enum { SIZE = 12 };
+  unsigned char buf[SIZE];
+  friend input_iterator *make_temp_iterator(const char *);
+};
+
+#if 1
+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()
+{
+}
+
+#endif
+
+#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(const macro &m) : mac(m), next(0)
+{
+}
+
+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();
+  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 (c == EOF) {
+      error("end of file while defining macro");
+      tok.next();
+      return;
+    }
+    if (bol && c == '.') {
+      const unsigned char *s = (const unsigned char *)term.contents();
+      unsigned char d;
+      // see if it matches term
+      for (int i = 0; s[i] != 0; i++) {
+       d = get_copy(&n);
+       if (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 (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;
+  }
+}
+
+// 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.newline() || tok.eof()) {
+      warning(WARN_DELIM, "missing closing delimiter");
+      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();
+    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()) {
+      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()) {
+    charinfo *ci1 = tok.get_char(1);
+    if (ci1 == 0)
+      break;
+    tok.next();
+    if (tok.newline() || tok.space()) {
+      ci1->set_special_translation(charinfo::TRANSLATE_SPACE);
+      break;
+    }
+    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(c);
+  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(c), 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(c));
+    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()) {
+    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);
+    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."
+
+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();
+}
+
+static void process_input_file(const char *name)
+{
+  FILE *fp;
+  if (strcmp(name, "-") == 0) {
+    clearerr(stdin);
+    fp = stdin;
+  }
+  else {
+    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 unsigned char *p = (const unsigned char *)value; *p; p++)
+    if (!illegal_input_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()
+{
+  long t;
+  time(&t);
+  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 */
+  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 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(unsigned char c)
+{
+  number = c;
+  flags |= NUMBERED;
+}
+
+int charinfo::get_number()
+{
+  return (flags & NUMBERED) ? int(number) : -1;
+}
+
+symbol UNNAMED_SYMBOL("---");
+
+charinfo *get_charinfo_by_number(unsigned char n)
+{
+  static charinfo *number_table[256];
+
+  charinfo *ci = number_table[n];
+  if (!ci) {
+    ci = new charinfo(UNNAMED_SYMBOL);
+    ci->set_number(n);
+    number_table[n] = 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(unsigned char c)
+{
+  return get_charinfo_by_number(c)->get_index();
+}