Home | History | Annotate | Line # | Download | only in troff
      1 /*	$NetBSD: input.cpp,v 1.5 2020/06/16 00:47:21 christos Exp $	*/
      2 
      3 // -*- C++ -*-
      4 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005
      5    Free Software Foundation, Inc.
      6      Written by James Clark (jjc (at) jclark.com)
      7 
      8 This file is part of groff.
      9 
     10 groff is free software; you can redistribute it and/or modify it under
     11 the terms of the GNU General Public License as published by the Free
     12 Software Foundation; either version 2, or (at your option) any later
     13 version.
     14 
     15 groff is distributed in the hope that it will be useful, but WITHOUT ANY
     16 WARRANTY; without even the implied warranty of MERCHANTABILITY or
     17 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     18 for more details.
     19 
     20 You should have received a copy of the GNU General Public License along
     21 with groff; see the file COPYING.  If not, write to the Free Software
     22 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
     23 
     24 #define DEBUGGING
     25 
     26 #include "troff.h"
     27 #include "dictionary.h"
     28 #include "hvunits.h"
     29 #include "stringclass.h"
     30 #include "mtsm.h"
     31 #include "env.h"
     32 #include "request.h"
     33 #include "node.h"
     34 #include "token.h"
     35 #include "div.h"
     36 #include "reg.h"
     37 #include "charinfo.h"
     38 #include "macropath.h"
     39 #include "input.h"
     40 #include "defs.h"
     41 #include "font.h"
     42 #include "unicode.h"
     43 
     44 // Needed for getpid() and isatty()
     45 #include "posix.h"
     46 
     47 #include "nonposix.h"
     48 
     49 #ifdef NEED_DECLARATION_PUTENV
     50 extern "C" {
     51   int putenv(const char *);
     52 }
     53 #endif /* NEED_DECLARATION_PUTENV */
     54 
     55 #define MACRO_PREFIX "tmac."
     56 #define MACRO_POSTFIX ".tmac"
     57 #define INITIAL_STARTUP_FILE "troffrc"
     58 #define FINAL_STARTUP_FILE   "troffrc-end"
     59 #define DEFAULT_INPUT_STACK_LIMIT 1000
     60 
     61 #ifndef DEFAULT_WARNING_MASK
     62 // warnings that are enabled by default
     63 #define DEFAULT_WARNING_MASK \
     64      (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
     65 #endif
     66 
     67 // initial size of buffer for reading names; expanded as necessary
     68 #define ABUF_SIZE 16
     69 
     70 extern "C" const char *program_name;
     71 extern "C" const char *Version_string;
     72 
     73 #ifdef COLUMN
     74 void init_column_requests();
     75 #endif /* COLUMN */
     76 
     77 static node *read_draw_node();
     78 static void read_color_draw_node(token &);
     79 static void push_token(const token &);
     80 void copy_file();
     81 #ifdef COLUMN
     82 void vjustify();
     83 #endif /* COLUMN */
     84 void transparent_file();
     85 
     86 token tok;
     87 int break_flag = 0;
     88 int color_flag = 1;		// colors are on by default
     89 static int backtrace_flag = 0;
     90 #ifndef POPEN_MISSING
     91 char *pipe_command = 0;
     92 #endif
     93 charinfo *charset_table[256];
     94 unsigned char hpf_code_table[256];
     95 
     96 static int warning_mask = DEFAULT_WARNING_MASK;
     97 static int inhibit_errors = 0;
     98 static int ignoring = 0;
     99 
    100 static void enable_warning(const char *);
    101 static void disable_warning(const char *);
    102 
    103 static int escape_char = '\\';
    104 static symbol end_macro_name;
    105 static symbol blank_line_macro_name;
    106 static int compatible_flag = 0;
    107 int ascii_output_flag = 0;
    108 int suppress_output_flag = 0;
    109 int is_html = 0;
    110 int begin_level = 0;		// number of nested \O escapes
    111 
    112 int have_input = 0;		// whether \f, \F, \D'F...', \H, \m, \M,
    113 				// \R, \s, or \S has been processed in
    114 				// token::next()
    115 int old_have_input = 0;		// value of have_input right before \n
    116 int tcommand_flag = 0;
    117 int safer_flag = 1;		// safer by default
    118 
    119 int have_string_arg = 0;	// whether we have \*[foo bar...]
    120 
    121 double spread_limit = -3.0 - 1.0;	// negative means deactivated
    122 
    123 double warn_scale;
    124 char warn_scaling_indicator;
    125 int debug_state = 0;            // turns on debugging of the html troff state
    126 
    127 search_path *mac_path = &safer_macro_path;
    128 
    129 // Defaults to the current directory.
    130 search_path include_search_path(0, 0, 0, 1);
    131 
    132 static int get_copy(node**, int = 0);
    133 static void copy_mode_error(const char *,
    134 			    const errarg & = empty_errarg,
    135 			    const errarg & = empty_errarg,
    136 			    const errarg & = empty_errarg);
    137 
    138 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
    139 static symbol read_escape_name(read_mode mode = NO_ARGS);
    140 static symbol read_long_escape_name(read_mode mode = NO_ARGS);
    141 static void interpolate_string(symbol);
    142 static void interpolate_string_with_args(symbol);
    143 static void interpolate_macro(symbol);
    144 static void interpolate_number_format(symbol);
    145 static void interpolate_environment_variable(symbol);
    146 
    147 static symbol composite_glyph_name(symbol);
    148 static void interpolate_arg(symbol);
    149 static request_or_macro *lookup_request(symbol);
    150 static int get_delim_number(units *, unsigned char);
    151 static int get_delim_number(units *, unsigned char, units);
    152 static symbol do_get_long_name(int, char);
    153 static int get_line_arg(units *res, unsigned char si, charinfo **cp);
    154 static int read_size(int *);
    155 static symbol get_delim_name();
    156 static void init_registers();
    157 static void trapping_blank_line();
    158 
    159 class input_iterator;
    160 input_iterator *make_temp_iterator(const char *);
    161 const char *input_char_description(int);
    162 
    163 void process_input_stack();
    164 void chop_macro();		// declare to avoid friend name injection
    165 
    166 
    167 void set_escape_char()
    168 {
    169   if (has_arg()) {
    170     if (tok.ch() == 0) {
    171       error("bad escape character");
    172       escape_char = '\\';
    173     }
    174     else
    175       escape_char = tok.ch();
    176   }
    177   else
    178     escape_char = '\\';
    179   skip_line();
    180 }
    181 
    182 void escape_off()
    183 {
    184   escape_char = 0;
    185   skip_line();
    186 }
    187 
    188 static int saved_escape_char = '\\';
    189 
    190 void save_escape_char()
    191 {
    192   saved_escape_char = escape_char;
    193   skip_line();
    194 }
    195 
    196 void restore_escape_char()
    197 {
    198   escape_char = saved_escape_char;
    199   skip_line();
    200 }
    201 
    202 class input_iterator {
    203 public:
    204   input_iterator();
    205   input_iterator(int is_div);
    206   virtual ~input_iterator() {}
    207   int get(node **);
    208   friend class input_stack;
    209   int is_diversion;
    210   statem *diversion_state;
    211 protected:
    212   const unsigned char *ptr;
    213   const unsigned char *eptr;
    214   input_iterator *next;
    215 private:
    216   virtual int fill(node **);
    217   virtual int peek();
    218   virtual int has_args() { return 0; }
    219   virtual int nargs() { return 0; }
    220   virtual input_iterator *get_arg(int) { return 0; }
    221   virtual int get_location(int, const char **, int *) { return 0; }
    222   virtual void backtrace() {}
    223   virtual int set_location(const char *, int) { return 0; }
    224   virtual int next_file(FILE *, const char *) { return 0; }
    225   virtual void shift(int) {}
    226   virtual int is_boundary() {return 0; }
    227   virtual int is_file() { return 0; }
    228   virtual int is_macro() { return 0; }
    229   virtual void save_compatible_flag(int) {}
    230   virtual int get_compatible_flag() { return 0; }
    231 };
    232 
    233 input_iterator::input_iterator()
    234 : is_diversion(0), ptr(0), eptr(0)
    235 {
    236 }
    237 
    238 input_iterator::input_iterator(int is_div)
    239 : is_diversion(is_div), ptr(0), eptr(0)
    240 {
    241 }
    242 
    243 int input_iterator::fill(node **)
    244 {
    245   return EOF;
    246 }
    247 
    248 int input_iterator::peek()
    249 {
    250   return EOF;
    251 }
    252 
    253 inline int input_iterator::get(node **p)
    254 {
    255   return ptr < eptr ? *ptr++ : fill(p);
    256 }
    257 
    258 class input_boundary : public input_iterator {
    259 public:
    260   int is_boundary() { return 1; }
    261 };
    262 
    263 class input_return_boundary : public input_iterator {
    264 public:
    265   int is_boundary() { return 2; }
    266 };
    267 
    268 class file_iterator : public input_iterator {
    269   FILE *fp;
    270   int lineno;
    271   const char *filename;
    272   int popened;
    273   int newline_flag;
    274   int seen_escape;
    275   enum { BUF_SIZE = 512 };
    276   unsigned char buf[BUF_SIZE];
    277   void close();
    278 public:
    279   file_iterator(FILE *, const char *, int = 0);
    280   ~file_iterator();
    281   int fill(node **);
    282   int peek();
    283   int get_location(int, const char **, int *);
    284   void backtrace();
    285   int set_location(const char *, int);
    286   int next_file(FILE *, const char *);
    287   int is_file();
    288 };
    289 
    290 file_iterator::file_iterator(FILE *f, const char *fn, int po)
    291 : fp(f), lineno(1), filename(fn), popened(po),
    292   newline_flag(0), seen_escape(0)
    293 {
    294   if ((font::use_charnames_in_special) && (fn != 0)) {
    295     if (!the_output)
    296       init_output();
    297     the_output->put_filename(fn);
    298   }
    299 }
    300 
    301 file_iterator::~file_iterator()
    302 {
    303   close();
    304 }
    305 
    306 void file_iterator::close()
    307 {
    308   if (fp == stdin)
    309     clearerr(stdin);
    310 #ifndef POPEN_MISSING
    311   else if (popened)
    312     pclose(fp);
    313 #endif /* not POPEN_MISSING */
    314   else
    315     fclose(fp);
    316 }
    317 
    318 int file_iterator::is_file()
    319 {
    320   return 1;
    321 }
    322 
    323 int file_iterator::next_file(FILE *f, const char *s)
    324 {
    325   close();
    326   filename = s;
    327   fp = f;
    328   lineno = 1;
    329   newline_flag = 0;
    330   seen_escape = 0;
    331   popened = 0;
    332   ptr = 0;
    333   eptr = 0;
    334   return 1;
    335 }
    336 
    337 int file_iterator::fill(node **)
    338 {
    339   if (newline_flag)
    340     lineno++;
    341   newline_flag = 0;
    342   unsigned char *p = buf;
    343   ptr = p;
    344   unsigned char *e = p + BUF_SIZE;
    345   while (p < e) {
    346     int c = getc(fp);
    347     if (c == EOF)
    348       break;
    349     if (invalid_input_char(c))
    350       warning(WARN_INPUT, "invalid input character code %1", int(c));
    351     else {
    352       *p++ = c;
    353       if (c == '\n') {
    354 	seen_escape = 0;
    355 	newline_flag = 1;
    356 	break;
    357       }
    358       seen_escape = (c == '\\');
    359     }
    360   }
    361   if (p > buf) {
    362     eptr = p;
    363     return *ptr++;
    364   }
    365   else {
    366     eptr = p;
    367     return EOF;
    368   }
    369 }
    370 
    371 int file_iterator::peek()
    372 {
    373   int c = getc(fp);
    374   while (invalid_input_char(c)) {
    375     warning(WARN_INPUT, "invalid input character code %1", int(c));
    376     c = getc(fp);
    377   }
    378   if (c != EOF)
    379     ungetc(c, fp);
    380   return c;
    381 }
    382 
    383 int file_iterator::get_location(int /*allow_macro*/,
    384 				const char **filenamep, int *linenop)
    385 {
    386   *linenop = lineno;
    387   if (filename != 0 && strcmp(filename, "-") == 0)
    388     *filenamep = "<standard input>";
    389   else
    390     *filenamep = filename;
    391   return 1;
    392 }
    393 
    394 void file_iterator::backtrace()
    395 {
    396   errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
    397 	   popened ? "process" : "file");
    398 }
    399 
    400 int file_iterator::set_location(const char *f, int ln)
    401 {
    402   if (f) {
    403     filename = f;
    404     if (!the_output)
    405       init_output();
    406     the_output->put_filename(f);
    407   }
    408   lineno = ln;
    409   return 1;
    410 }
    411 
    412 input_iterator nil_iterator;
    413 
    414 class input_stack {
    415 public:
    416   static int get(node **);
    417   static int peek();
    418   static void push(input_iterator *);
    419   static input_iterator *get_arg(int);
    420   static int nargs();
    421   static int get_location(int, const char **, int *);
    422   static int set_location(const char *, int);
    423   static void backtrace();
    424   static void backtrace_all();
    425   static void next_file(FILE *, const char *);
    426   static void end_file();
    427   static void shift(int n);
    428   static void add_boundary();
    429   static void add_return_boundary();
    430   static int is_return_boundary();
    431   static void remove_boundary();
    432   static int get_level();
    433   static int get_div_level();
    434   static void increase_level();
    435   static void decrease_level();
    436   static void clear();
    437   static void pop_macro();
    438   static void save_compatible_flag(int);
    439   static int get_compatible_flag();
    440   static statem *get_diversion_state();
    441   static void check_end_diversion(input_iterator *t);
    442   static int limit;
    443   static int div_level;
    444   static statem *diversion_state;
    445 private:
    446   static input_iterator *top;
    447   static int level;
    448   static int finish_get(node **);
    449   static int finish_peek();
    450 };
    451 
    452 input_iterator *input_stack::top = &nil_iterator;
    453 int input_stack::level = 0;
    454 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
    455 int input_stack::div_level = 0;
    456 statem *input_stack::diversion_state = NULL;
    457 int suppress_push=0;
    458 
    459 
    460 inline int input_stack::get_level()
    461 {
    462   return level;
    463 }
    464 
    465 inline void input_stack::increase_level()
    466 {
    467   level++;
    468 }
    469 
    470 inline void input_stack::decrease_level()
    471 {
    472   level--;
    473 }
    474 
    475 inline int input_stack::get_div_level()
    476 {
    477   return div_level;
    478 }
    479 
    480 inline int input_stack::get(node **np)
    481 {
    482   int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
    483   if (res == '\n') {
    484     old_have_input = have_input;
    485     have_input = 0;
    486   }
    487   return res;
    488 }
    489 
    490 int input_stack::finish_get(node **np)
    491 {
    492   for (;;) {
    493     int c = top->fill(np);
    494     if (c != EOF || top->is_boundary())
    495       return c;
    496     if (top == &nil_iterator)
    497       break;
    498     input_iterator *tem = top;
    499     check_end_diversion(tem);
    500 #if defined(DEBUGGING)
    501   if (debug_state)
    502     if (tem->is_diversion)
    503       fprintf(stderr,
    504 	      "in diversion level = %d\n", input_stack::get_div_level());
    505 #endif
    506     top = top->next;
    507     level--;
    508     delete tem;
    509     if (top->ptr < top->eptr)
    510       return *top->ptr++;
    511   }
    512   assert(level == 0);
    513   return EOF;
    514 }
    515 
    516 inline int input_stack::peek()
    517 {
    518   return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
    519 }
    520 
    521 void input_stack::check_end_diversion(input_iterator *t)
    522 {
    523   if (t->is_diversion) {
    524     div_level--;
    525     diversion_state = t->diversion_state;
    526   }
    527 }
    528 
    529 int input_stack::finish_peek()
    530 {
    531   for (;;) {
    532     int c = top->peek();
    533     if (c != EOF || top->is_boundary())
    534       return c;
    535     if (top == &nil_iterator)
    536       break;
    537     input_iterator *tem = top;
    538     check_end_diversion(tem);
    539     top = top->next;
    540     level--;
    541     delete tem;
    542     if (top->ptr < top->eptr)
    543       return *top->ptr;
    544   }
    545   assert(level == 0);
    546   return EOF;
    547 }
    548 
    549 void input_stack::add_boundary()
    550 {
    551   push(new input_boundary);
    552 }
    553 
    554 void input_stack::add_return_boundary()
    555 {
    556   push(new input_return_boundary);
    557 }
    558 
    559 int input_stack::is_return_boundary()
    560 {
    561   return top->is_boundary() == 2;
    562 }
    563 
    564 void input_stack::remove_boundary()
    565 {
    566   assert(top->is_boundary());
    567   input_iterator *temp = top->next;
    568   check_end_diversion(top);
    569 
    570   delete top;
    571   top = temp;
    572   level--;
    573 }
    574 
    575 void input_stack::push(input_iterator *in)
    576 {
    577   if (in == 0)
    578     return;
    579   if (++level > limit && limit > 0)
    580     fatal("input stack limit exceeded (probable infinite loop)");
    581   in->next = top;
    582   top = in;
    583   if (top->is_diversion) {
    584     div_level++;
    585     in->diversion_state = diversion_state;
    586     diversion_state = curenv->construct_state(0);
    587 #if defined(DEBUGGING)
    588     if (debug_state) {
    589       curenv->dump_troff_state();
    590       fflush(stderr);
    591     }
    592 #endif
    593   }
    594 #if defined(DEBUGGING)
    595   if (debug_state)
    596     if (top->is_diversion) {
    597       fprintf(stderr,
    598 	      "in diversion level = %d\n", input_stack::get_div_level());
    599       fflush(stderr);
    600     }
    601 #endif
    602 }
    603 
    604 statem *get_diversion_state()
    605 {
    606   return input_stack::get_diversion_state();
    607 }
    608 
    609 statem *input_stack::get_diversion_state()
    610 {
    611   if (diversion_state == NULL)
    612     return NULL;
    613   else
    614     return new statem(diversion_state);
    615 }
    616 
    617 input_iterator *input_stack::get_arg(int i)
    618 {
    619   input_iterator *p;
    620   for (p = top; p != 0; p = p->next)
    621     if (p->has_args())
    622       return p->get_arg(i);
    623   return 0;
    624 }
    625 
    626 void input_stack::shift(int n)
    627 {
    628   for (input_iterator *p = top; p; p = p->next)
    629     if (p->has_args()) {
    630       p->shift(n);
    631       return;
    632     }
    633 }
    634 
    635 int input_stack::nargs()
    636 {
    637   for (input_iterator *p =top; p != 0; p = p->next)
    638     if (p->has_args())
    639       return p->nargs();
    640   return 0;
    641 }
    642 
    643 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
    644 {
    645   for (input_iterator *p = top; p; p = p->next)
    646     if (p->get_location(allow_macro, filenamep, linenop))
    647       return 1;
    648   return 0;
    649 }
    650 
    651 void input_stack::backtrace()
    652 {
    653   const char *f;
    654   int n;
    655   // only backtrace down to (not including) the topmost file
    656   for (input_iterator *p = top;
    657        p && !p->get_location(0, &f, &n);
    658        p = p->next)
    659     p->backtrace();
    660 }
    661 
    662 void input_stack::backtrace_all()
    663 {
    664   for (input_iterator *p = top; p; p = p->next)
    665     p->backtrace();
    666 }
    667 
    668 int input_stack::set_location(const char *filename, int lineno)
    669 {
    670   for (input_iterator *p = top; p; p = p->next)
    671     if (p->set_location(filename, lineno))
    672       return 1;
    673   return 0;
    674 }
    675 
    676 void input_stack::next_file(FILE *fp, const char *s)
    677 {
    678   input_iterator **pp;
    679   for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
    680     if ((*pp)->next_file(fp, s))
    681       return;
    682   if (++level > limit && limit > 0)
    683     fatal("input stack limit exceeded");
    684   *pp = new file_iterator(fp, s);
    685   (*pp)->next = &nil_iterator;
    686 }
    687 
    688 void input_stack::end_file()
    689 {
    690   for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
    691     if ((*pp)->is_file()) {
    692       input_iterator *tem = *pp;
    693       check_end_diversion(tem);
    694       *pp = (*pp)->next;
    695       delete tem;
    696       level--;
    697       return;
    698     }
    699 }
    700 
    701 void input_stack::clear()
    702 {
    703   int nboundaries = 0;
    704   while (top != &nil_iterator) {
    705     if (top->is_boundary())
    706       nboundaries++;
    707     input_iterator *tem = top;
    708     check_end_diversion(tem);
    709     top = top->next;
    710     level--;
    711     delete tem;
    712   }
    713   // Keep while_request happy.
    714   for (; nboundaries > 0; --nboundaries)
    715     add_return_boundary();
    716 }
    717 
    718 void input_stack::pop_macro()
    719 {
    720   int nboundaries = 0;
    721   int is_macro = 0;
    722   do {
    723     if (top->next == &nil_iterator)
    724       break;
    725     if (top->is_boundary())
    726       nboundaries++;
    727     is_macro = top->is_macro();
    728     input_iterator *tem = top;
    729     check_end_diversion(tem);
    730     top = top->next;
    731     level--;
    732     delete tem;
    733   } while (!is_macro);
    734   // Keep while_request happy.
    735   for (; nboundaries > 0; --nboundaries)
    736     add_return_boundary();
    737 }
    738 
    739 inline void input_stack::save_compatible_flag(int f)
    740 {
    741   top->save_compatible_flag(f);
    742 }
    743 
    744 inline int input_stack::get_compatible_flag()
    745 {
    746   return top->get_compatible_flag();
    747 }
    748 
    749 void backtrace_request()
    750 {
    751   input_stack::backtrace_all();
    752   fflush(stderr);
    753   skip_line();
    754 }
    755 
    756 void next_file()
    757 {
    758   symbol nm = get_long_name();
    759   while (!tok.newline() && !tok.eof())
    760     tok.next();
    761   if (nm.is_null())
    762     input_stack::end_file();
    763   else {
    764     errno = 0;
    765     FILE *fp = include_search_path.open_file_cautious(nm.contents());
    766     if (!fp)
    767       error("can't open `%1': %2", nm.contents(), strerror(errno));
    768     else
    769       input_stack::next_file(fp, nm.contents());
    770   }
    771   tok.next();
    772 }
    773 
    774 void shift()
    775 {
    776   int n;
    777   if (!has_arg() || !get_integer(&n))
    778     n = 1;
    779   input_stack::shift(n);
    780   skip_line();
    781 }
    782 
    783 static char get_char_for_escape_name(int allow_space = 0)
    784 {
    785   int c = get_copy(0);
    786   switch (c) {
    787   case EOF:
    788     copy_mode_error("end of input in escape name");
    789     return '\0';
    790   default:
    791     if (!invalid_input_char(c))
    792       break;
    793     // fall through
    794   case '\n':
    795     if (c == '\n')
    796       input_stack::push(make_temp_iterator("\n"));
    797     // fall through
    798   case ' ':
    799     if (c == ' ' && allow_space)
    800       break;
    801     // fall through
    802   case '\t':
    803   case '\001':
    804   case '\b':
    805     copy_mode_error("%1 is not allowed in an escape name",
    806 		    input_char_description(c));
    807     return '\0';
    808   }
    809   return c;
    810 }
    811 
    812 static symbol read_two_char_escape_name()
    813 {
    814   char buf[3];
    815   buf[0] = get_char_for_escape_name();
    816   if (buf[0] != '\0') {
    817     buf[1] = get_char_for_escape_name();
    818     if (buf[1] == '\0')
    819       buf[0] = 0;
    820     else
    821       buf[2] = 0;
    822   }
    823   return symbol(buf);
    824 }
    825 
    826 static symbol read_long_escape_name(read_mode mode)
    827 {
    828   int start_level = input_stack::get_level();
    829   char abuf[ABUF_SIZE];
    830   char *buf = abuf;
    831   int buf_size = ABUF_SIZE;
    832   int i = 0;
    833   char c;
    834   int have_char = 0;
    835   for (;;) {
    836     c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
    837     if (c == 0) {
    838       if (buf != abuf)
    839 	a_delete buf;
    840       return NULL_SYMBOL;
    841     }
    842     have_char = 1;
    843     if (mode == WITH_ARGS && c == ' ')
    844       break;
    845     if (i + 2 > buf_size) {
    846       if (buf == abuf) {
    847 	buf = new char[ABUF_SIZE*2];
    848 	memcpy(buf, abuf, buf_size);
    849 	buf_size = ABUF_SIZE*2;
    850       }
    851       else {
    852 	char *old_buf = buf;
    853 	buf = new char[buf_size*2];
    854 	memcpy(buf, old_buf, buf_size);
    855 	buf_size *= 2;
    856 	a_delete old_buf;
    857       }
    858     }
    859     if (c == ']' && input_stack::get_level() == start_level)
    860       break;
    861     buf[i++] = c;
    862   }
    863   buf[i] = 0;
    864   if (c == ' ')
    865     have_string_arg = 1;
    866   if (buf == abuf) {
    867     if (i == 0) {
    868       if (mode != ALLOW_EMPTY)
    869         copy_mode_error("empty escape name");
    870       return EMPTY_SYMBOL;
    871     }
    872     return symbol(abuf);
    873   }
    874   else {
    875     symbol s(buf);
    876     a_delete buf;
    877     return s;
    878   }
    879 }
    880 
    881 static symbol read_escape_name(read_mode mode)
    882 {
    883   char c = get_char_for_escape_name();
    884   if (c == 0)
    885     return NULL_SYMBOL;
    886   if (c == '(')
    887     return read_two_char_escape_name();
    888   if (c == '[' && !compatible_flag)
    889     return read_long_escape_name(mode);
    890   char buf[2];
    891   buf[0] = c;
    892   buf[1] = '\0';
    893   return symbol(buf);
    894 }
    895 
    896 static symbol read_increment_and_escape_name(int *incp)
    897 {
    898   char c = get_char_for_escape_name();
    899   switch (c) {
    900   case 0:
    901     *incp = 0;
    902     return NULL_SYMBOL;
    903   case '(':
    904     *incp = 0;
    905     return read_two_char_escape_name();
    906   case '+':
    907     *incp = 1;
    908     return read_escape_name();
    909   case '-':
    910     *incp = -1;
    911     return read_escape_name();
    912   case '[':
    913     if (!compatible_flag) {
    914       *incp = 0;
    915       return read_long_escape_name();
    916     }
    917     break;
    918   }
    919   *incp = 0;
    920   char buf[2];
    921   buf[0] = c;
    922   buf[1] = '\0';
    923   return symbol(buf);
    924 }
    925 
    926 static int get_copy(node **nd, int defining)
    927 {
    928   for (;;) {
    929     int c = input_stack::get(nd);
    930     if (c == PUSH_GROFF_MODE) {
    931       input_stack::save_compatible_flag(compatible_flag);
    932       compatible_flag = 0;
    933       continue;
    934     }
    935     if (c == PUSH_COMP_MODE) {
    936       input_stack::save_compatible_flag(compatible_flag);
    937       compatible_flag = 1;
    938       continue;
    939     }
    940     if (c == POP_GROFFCOMP_MODE) {
    941       compatible_flag = input_stack::get_compatible_flag();
    942       continue;
    943     }
    944     if (c == BEGIN_QUOTE) {
    945       input_stack::increase_level();
    946       continue;
    947     }
    948     if (c == END_QUOTE) {
    949       input_stack::decrease_level();
    950       continue;
    951     }
    952     if (c == ESCAPE_NEWLINE) {
    953       if (defining)
    954 	return c;
    955       do {
    956 	c = input_stack::get(nd);
    957       } while (c == ESCAPE_NEWLINE);
    958     }
    959     if (c != escape_char || escape_char <= 0)
    960       return c;
    961     c = input_stack::peek();
    962     switch(c) {
    963     case 0:
    964       return escape_char;
    965     case '"':
    966       (void)input_stack::get(0);
    967       while ((c = input_stack::get(0)) != '\n' && c != EOF)
    968 	;
    969       return c;
    970     case '#':			// Like \" but newline is ignored.
    971       (void)input_stack::get(0);
    972       while ((c = input_stack::get(0)) != '\n')
    973 	if (c == EOF)
    974 	  return EOF;
    975       break;
    976     case '$':
    977       {
    978 	(void)input_stack::get(0);
    979 	symbol s = read_escape_name();
    980 	if (!(s.is_null() || s.is_empty()))
    981 	  interpolate_arg(s);
    982 	break;
    983       }
    984     case '*':
    985       {
    986 	(void)input_stack::get(0);
    987 	symbol s = read_escape_name(WITH_ARGS);
    988 	if (!(s.is_null() || s.is_empty())) {
    989 	  if (have_string_arg) {
    990 	    have_string_arg = 0;
    991 	    interpolate_string_with_args(s);
    992 	  }
    993 	  else
    994 	    interpolate_string(s);
    995 	}
    996 	break;
    997       }
    998     case 'a':
    999       (void)input_stack::get(0);
   1000       return '\001';
   1001     case 'e':
   1002       (void)input_stack::get(0);
   1003       return ESCAPE_e;
   1004     case 'E':
   1005       (void)input_stack::get(0);
   1006       return ESCAPE_E;
   1007     case 'n':
   1008       {
   1009 	(void)input_stack::get(0);
   1010 	int inc;
   1011 	symbol s = read_increment_and_escape_name(&inc);
   1012 	if (!(s.is_null() || s.is_empty()))
   1013 	  interpolate_number_reg(s, inc);
   1014 	break;
   1015       }
   1016     case 'g':
   1017       {
   1018 	(void)input_stack::get(0);
   1019 	symbol s = read_escape_name();
   1020 	if (!(s.is_null() || s.is_empty()))
   1021 	  interpolate_number_format(s);
   1022 	break;
   1023       }
   1024     case 't':
   1025       (void)input_stack::get(0);
   1026       return '\t';
   1027     case 'V':
   1028       {
   1029 	(void)input_stack::get(0);
   1030 	symbol s = read_escape_name();
   1031 	if (!(s.is_null() || s.is_empty()))
   1032 	  interpolate_environment_variable(s);
   1033 	break;
   1034       }
   1035     case '\n':
   1036       (void)input_stack::get(0);
   1037       if (defining)
   1038 	return ESCAPE_NEWLINE;
   1039       break;
   1040     case ' ':
   1041       (void)input_stack::get(0);
   1042       return ESCAPE_SPACE;
   1043     case '~':
   1044       (void)input_stack::get(0);
   1045       return ESCAPE_TILDE;
   1046     case ':':
   1047       (void)input_stack::get(0);
   1048       return ESCAPE_COLON;
   1049     case '|':
   1050       (void)input_stack::get(0);
   1051       return ESCAPE_BAR;
   1052     case '^':
   1053       (void)input_stack::get(0);
   1054       return ESCAPE_CIRCUMFLEX;
   1055     case '{':
   1056       (void)input_stack::get(0);
   1057       return ESCAPE_LEFT_BRACE;
   1058     case '}':
   1059       (void)input_stack::get(0);
   1060       return ESCAPE_RIGHT_BRACE;
   1061     case '`':
   1062       (void)input_stack::get(0);
   1063       return ESCAPE_LEFT_QUOTE;
   1064     case '\'':
   1065       (void)input_stack::get(0);
   1066       return ESCAPE_RIGHT_QUOTE;
   1067     case '-':
   1068       (void)input_stack::get(0);
   1069       return ESCAPE_HYPHEN;
   1070     case '_':
   1071       (void)input_stack::get(0);
   1072       return ESCAPE_UNDERSCORE;
   1073     case 'c':
   1074       (void)input_stack::get(0);
   1075       return ESCAPE_c;
   1076     case '!':
   1077       (void)input_stack::get(0);
   1078       return ESCAPE_BANG;
   1079     case '?':
   1080       (void)input_stack::get(0);
   1081       return ESCAPE_QUESTION;
   1082     case '&':
   1083       (void)input_stack::get(0);
   1084       return ESCAPE_AMPERSAND;
   1085     case ')':
   1086       (void)input_stack::get(0);
   1087       return ESCAPE_RIGHT_PARENTHESIS;
   1088     case '.':
   1089       (void)input_stack::get(0);
   1090       return c;
   1091     case '%':
   1092       (void)input_stack::get(0);
   1093       return ESCAPE_PERCENT;
   1094     default:
   1095       if (c == escape_char) {
   1096 	(void)input_stack::get(0);
   1097 	return c;
   1098       }
   1099       else
   1100 	return escape_char;
   1101     }
   1102   }
   1103 }
   1104 
   1105 class non_interpreted_char_node : public node {
   1106   unsigned char c;
   1107 public:
   1108   non_interpreted_char_node(unsigned char);
   1109   node *copy();
   1110   int interpret(macro *);
   1111   int same(node *);
   1112   const char *type();
   1113   int force_tprint();
   1114   int is_tag();
   1115 };
   1116 
   1117 int non_interpreted_char_node::same(node *nd)
   1118 {
   1119   return c == ((non_interpreted_char_node *)nd)->c;
   1120 }
   1121 
   1122 const char *non_interpreted_char_node::type()
   1123 {
   1124   return "non_interpreted_char_node";
   1125 }
   1126 
   1127 int non_interpreted_char_node::force_tprint()
   1128 {
   1129   return 0;
   1130 }
   1131 
   1132 int non_interpreted_char_node::is_tag()
   1133 {
   1134   return 0;
   1135 }
   1136 
   1137 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
   1138 {
   1139   assert(n != 0);
   1140 }
   1141 
   1142 node *non_interpreted_char_node::copy()
   1143 {
   1144   return new non_interpreted_char_node(c);
   1145 }
   1146 
   1147 int non_interpreted_char_node::interpret(macro *mac)
   1148 {
   1149   mac->append(c);
   1150   return 1;
   1151 }
   1152 
   1153 static void do_width();
   1154 static node *do_non_interpreted();
   1155 static node *do_special();
   1156 static node *do_suppress(symbol nm);
   1157 static void do_register();
   1158 
   1159 dictionary color_dictionary(501);
   1160 
   1161 static color *lookup_color(symbol nm)
   1162 {
   1163   assert(!nm.is_null());
   1164   if (nm == default_symbol)
   1165     return &default_color;
   1166   color *c = (color *)color_dictionary.lookup(nm);
   1167   if (c == 0)
   1168     warning(WARN_COLOR, "color `%1' not defined", nm.contents());
   1169   return c;
   1170 }
   1171 
   1172 void do_glyph_color(symbol nm)
   1173 {
   1174   if (nm.is_null())
   1175     return;
   1176   if (nm.is_empty())
   1177     curenv->set_glyph_color(curenv->get_prev_glyph_color());
   1178   else {
   1179     color *tem = lookup_color(nm);
   1180     if (tem)
   1181       curenv->set_glyph_color(tem);
   1182     else
   1183       (void)color_dictionary.lookup(nm, new color(nm));
   1184   }
   1185 }
   1186 
   1187 void do_fill_color(symbol nm)
   1188 {
   1189   if (nm.is_null())
   1190     return;
   1191   if (nm.is_empty())
   1192     curenv->set_fill_color(curenv->get_prev_fill_color());
   1193   else {
   1194     color *tem = lookup_color(nm);
   1195     if (tem)
   1196       curenv->set_fill_color(tem);
   1197     else
   1198       (void)color_dictionary.lookup(nm, new color(nm));
   1199   }
   1200 }
   1201 
   1202 static unsigned int get_color_element(const char *scheme, const char *col)
   1203 {
   1204   units val;
   1205   if (!get_number(&val, 'f')) {
   1206     warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
   1207     tok.next();
   1208     return 0;
   1209   }
   1210   if (val < 0) {
   1211     warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
   1212     return 0;
   1213   }
   1214   if (val > color::MAX_COLOR_VAL+1) {
   1215     warning(WARN_RANGE, "%1 cannot be greater than 1", col);
   1216     // we change 0x10000 to 0xffff
   1217     return color::MAX_COLOR_VAL;
   1218   }
   1219   return (unsigned int)val;
   1220 }
   1221 
   1222 static color *read_rgb(char end = 0)
   1223 {
   1224   symbol component = do_get_long_name(0, end);
   1225   if (component.is_null()) {
   1226     warning(WARN_COLOR, "missing rgb color values");
   1227     return 0;
   1228   }
   1229   const char *s = component.contents();
   1230   color *col = new color;
   1231   if (*s == '#') {
   1232     if (!col->read_rgb(s)) {
   1233       warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
   1234       delete col;
   1235       return 0;
   1236     }
   1237   }
   1238   else {
   1239     if (!end)
   1240       input_stack::push(make_temp_iterator(" "));
   1241     input_stack::push(make_temp_iterator(s));
   1242     tok.next();
   1243     unsigned int r = get_color_element("rgb color", "red component");
   1244     unsigned int g = get_color_element("rgb color", "green component");
   1245     unsigned int b = get_color_element("rgb color", "blue component");
   1246     col->set_rgb(r, g, b);
   1247   }
   1248   return col;
   1249 }
   1250 
   1251 static color *read_cmy(char end = 0)
   1252 {
   1253   symbol component = do_get_long_name(0, end);
   1254   if (component.is_null()) {
   1255     warning(WARN_COLOR, "missing cmy color values");
   1256     return 0;
   1257   }
   1258   const char *s = component.contents();
   1259   color *col = new color;
   1260   if (*s == '#') {
   1261     if (!col->read_cmy(s)) {
   1262       warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
   1263       delete col;
   1264       return 0;
   1265     }
   1266   }
   1267   else {
   1268     if (!end)
   1269       input_stack::push(make_temp_iterator(" "));
   1270     input_stack::push(make_temp_iterator(s));
   1271     tok.next();
   1272     unsigned int c = get_color_element("cmy color", "cyan component");
   1273     unsigned int m = get_color_element("cmy color", "magenta component");
   1274     unsigned int y = get_color_element("cmy color", "yellow component");
   1275     col->set_cmy(c, m, y);
   1276   }
   1277   return col;
   1278 }
   1279 
   1280 static color *read_cmyk(char end = 0)
   1281 {
   1282   symbol component = do_get_long_name(0, end);
   1283   if (component.is_null()) {
   1284     warning(WARN_COLOR, "missing cmyk color values");
   1285     return 0;
   1286   }
   1287   const char *s = component.contents();
   1288   color *col = new color;
   1289   if (*s == '#') {
   1290     if (!col->read_cmyk(s)) {
   1291       warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
   1292       delete col;
   1293       return 0;
   1294     }
   1295   }
   1296   else {
   1297     if (!end)
   1298       input_stack::push(make_temp_iterator(" "));
   1299     input_stack::push(make_temp_iterator(s));
   1300     tok.next();
   1301     unsigned int c = get_color_element("cmyk color", "cyan component");
   1302     unsigned int m = get_color_element("cmyk color", "magenta component");
   1303     unsigned int y = get_color_element("cmyk color", "yellow component");
   1304     unsigned int k = get_color_element("cmyk color", "black component");
   1305     col->set_cmyk(c, m, y, k);
   1306   }
   1307   return col;
   1308 }
   1309 
   1310 static color *read_gray(char end = 0)
   1311 {
   1312   symbol component = do_get_long_name(0, end);
   1313   if (component.is_null()) {
   1314     warning(WARN_COLOR, "missing gray values");
   1315     return 0;
   1316   }
   1317   const char *s = component.contents();
   1318   color *col = new color;
   1319   if (*s == '#') {
   1320     if (!col->read_gray(s)) {
   1321       warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
   1322       delete col;
   1323       return 0;
   1324     }
   1325   }
   1326   else {
   1327     if (!end)
   1328       input_stack::push(make_temp_iterator("\n"));
   1329     input_stack::push(make_temp_iterator(s));
   1330     tok.next();
   1331     unsigned int g = get_color_element("gray", "gray value");
   1332     col->set_gray(g);
   1333   }
   1334   return col;
   1335 }
   1336 
   1337 static void activate_color()
   1338 {
   1339   int n;
   1340   if (has_arg() && get_integer(&n))
   1341     color_flag = n != 0;
   1342   else
   1343     color_flag = 1;
   1344   skip_line();
   1345 }
   1346 
   1347 static void define_color()
   1348 {
   1349   symbol color_name = get_long_name(1);
   1350   if (color_name.is_null()) {
   1351     skip_line();
   1352     return;
   1353   }
   1354   if (color_name == default_symbol) {
   1355     warning(WARN_COLOR, "default color can't be redefined");
   1356     skip_line();
   1357     return;
   1358   }
   1359   symbol style = get_long_name(1);
   1360   if (style.is_null()) {
   1361     skip_line();
   1362     return;
   1363   }
   1364   color *col;
   1365   if (strcmp(style.contents(), "rgb") == 0)
   1366     col = read_rgb();
   1367   else if (strcmp(style.contents(), "cmyk") == 0)
   1368     col = read_cmyk();
   1369   else if (strcmp(style.contents(), "gray") == 0)
   1370     col = read_gray();
   1371   else if (strcmp(style.contents(), "grey") == 0)
   1372     col = read_gray();
   1373   else if (strcmp(style.contents(), "cmy") == 0)
   1374     col = read_cmy();
   1375   else {
   1376     warning(WARN_COLOR,
   1377 	    "unknown color space `%1'; use rgb, cmyk, gray or cmy",
   1378 	    style.contents());
   1379     skip_line();
   1380     return;
   1381   }
   1382   if (col) {
   1383     col->nm = color_name;
   1384     (void)color_dictionary.lookup(color_name, col);
   1385   }
   1386   skip_line();
   1387 }
   1388 
   1389 static node *do_overstrike()
   1390 {
   1391   token start;
   1392   overstrike_node *on = new overstrike_node;
   1393   int start_level = input_stack::get_level();
   1394   start.next();
   1395   for (;;) {
   1396     tok.next();
   1397     if (tok.newline() || tok.eof()) {
   1398       warning(WARN_DELIM, "missing closing delimiter");
   1399       input_stack::push(make_temp_iterator("\n"));
   1400       break;
   1401     }
   1402     if (tok == start
   1403 	&& (compatible_flag || input_stack::get_level() == start_level))
   1404       break;
   1405     charinfo *ci = tok.get_char(1);
   1406     if (ci) {
   1407       node *n = curenv->make_char_node(ci);
   1408       if (n)
   1409 	on->overstrike(n);
   1410     }
   1411   }
   1412   return on;
   1413 }
   1414 
   1415 static node *do_bracket()
   1416 {
   1417   token start;
   1418   bracket_node *bn = new bracket_node;
   1419   start.next();
   1420   int start_level = input_stack::get_level();
   1421   for (;;) {
   1422     tok.next();
   1423     if (tok.eof()) {
   1424       warning(WARN_DELIM, "missing closing delimiter");
   1425       break;
   1426     }
   1427     if (tok.newline()) {
   1428       warning(WARN_DELIM, "missing closing delimiter");
   1429       input_stack::push(make_temp_iterator("\n"));
   1430       break;
   1431     }
   1432     if (tok == start
   1433 	&& (compatible_flag || input_stack::get_level() == start_level))
   1434       break;
   1435     charinfo *ci = tok.get_char(1);
   1436     if (ci) {
   1437       node *n = curenv->make_char_node(ci);
   1438       if (n)
   1439 	bn->bracket(n);
   1440     }
   1441   }
   1442   return bn;
   1443 }
   1444 
   1445 static int do_name_test()
   1446 {
   1447   token start;
   1448   start.next();
   1449   int start_level = input_stack::get_level();
   1450   int bad_char = 0;
   1451   int some_char = 0;
   1452   for (;;) {
   1453     tok.next();
   1454     if (tok.newline() || tok.eof()) {
   1455       warning(WARN_DELIM, "missing closing delimiter");
   1456       input_stack::push(make_temp_iterator("\n"));
   1457       break;
   1458     }
   1459     if (tok == start
   1460 	&& (compatible_flag || input_stack::get_level() == start_level))
   1461       break;
   1462     if (!tok.ch())
   1463       bad_char = 1;
   1464     some_char = 1;
   1465   }
   1466   return some_char && !bad_char;
   1467 }
   1468 
   1469 static int do_expr_test()
   1470 {
   1471   token start;
   1472   start.next();
   1473   int start_level = input_stack::get_level();
   1474   if (!start.delimiter(1))
   1475     return 0;
   1476   tok.next();
   1477   // disable all warning and error messages temporarily
   1478   int saved_warning_mask = warning_mask;
   1479   int saved_inhibit_errors = inhibit_errors;
   1480   warning_mask = 0;
   1481   inhibit_errors = 1;
   1482   int dummy;
   1483   int result = get_number_rigidly(&dummy, 'u');
   1484   warning_mask = saved_warning_mask;
   1485   inhibit_errors = saved_inhibit_errors;
   1486   if (tok == start && input_stack::get_level() == start_level)
   1487     return result;
   1488   // ignore everything up to the delimiter in case we aren't right there
   1489   for (;;) {
   1490     tok.next();
   1491     if (tok.newline() || tok.eof()) {
   1492       warning(WARN_DELIM, "missing closing delimiter");
   1493       input_stack::push(make_temp_iterator("\n"));
   1494       break;
   1495     }
   1496     if (tok == start && input_stack::get_level() == start_level)
   1497       break;
   1498   }
   1499   return 0;
   1500 }
   1501 
   1502 #if 0
   1503 static node *do_zero_width()
   1504 {
   1505   token start;
   1506   start.next();
   1507   int start_level = input_stack::get_level();
   1508   environment env(curenv);
   1509   environment *oldenv = curenv;
   1510   curenv = &env;
   1511   for (;;) {
   1512     tok.next();
   1513     if (tok.newline() || tok.eof()) {
   1514       error("missing closing delimiter");
   1515       break;
   1516     }
   1517     if (tok == start
   1518 	&& (compatible_flag || input_stack::get_level() == start_level))
   1519       break;
   1520     tok.process();
   1521   }
   1522   curenv = oldenv;
   1523   node *rev = env.extract_output_line();
   1524   node *n = 0;
   1525   while (rev) {
   1526     node *tem = rev;
   1527     rev = rev->next;
   1528     tem->next = n;
   1529     n = tem;
   1530   }
   1531   return new zero_width_node(n);
   1532 }
   1533 
   1534 #else
   1535 
   1536 // It's undesirable for \Z to change environments, because then
   1537 // \n(.w won't work as expected.
   1538 
   1539 static node *do_zero_width()
   1540 {
   1541   node *rev = new dummy_node;
   1542   token start;
   1543   start.next();
   1544   int start_level = input_stack::get_level();
   1545   for (;;) {
   1546     tok.next();
   1547     if (tok.newline() || tok.eof()) {
   1548       warning(WARN_DELIM, "missing closing delimiter");
   1549       input_stack::push(make_temp_iterator("\n"));
   1550       break;
   1551     }
   1552     if (tok == start
   1553 	&& (compatible_flag || input_stack::get_level() == start_level))
   1554       break;
   1555     if (!tok.add_to_node_list(&rev))
   1556       error("invalid token in argument to \\Z");
   1557   }
   1558   node *n = 0;
   1559   while (rev) {
   1560     node *tem = rev;
   1561     rev = rev->next;
   1562     tem->next = n;
   1563     n = tem;
   1564   }
   1565   return new zero_width_node(n);
   1566 }
   1567 
   1568 #endif
   1569 
   1570 token_node *node::get_token_node()
   1571 {
   1572   return 0;
   1573 }
   1574 
   1575 class token_node : public node {
   1576 public:
   1577   token tk;
   1578   token_node(const token &t);
   1579   node *copy();
   1580   token_node *get_token_node();
   1581   int same(node *);
   1582   const char *type();
   1583   int force_tprint();
   1584   int is_tag();
   1585 };
   1586 
   1587 token_node::token_node(const token &t) : tk(t)
   1588 {
   1589 }
   1590 
   1591 node *token_node::copy()
   1592 {
   1593   return new token_node(tk);
   1594 }
   1595 
   1596 token_node *token_node::get_token_node()
   1597 {
   1598   return this;
   1599 }
   1600 
   1601 int token_node::same(node *nd)
   1602 {
   1603   return tk == ((token_node *)nd)->tk;
   1604 }
   1605 
   1606 const char *token_node::type()
   1607 {
   1608   return "token_node";
   1609 }
   1610 
   1611 int token_node::force_tprint()
   1612 {
   1613   return 0;
   1614 }
   1615 
   1616 int token_node::is_tag()
   1617 {
   1618   return 0;
   1619 }
   1620 
   1621 token::token() : nd(0), type(TOKEN_EMPTY)
   1622 {
   1623 }
   1624 
   1625 token::~token()
   1626 {
   1627   delete nd;
   1628 }
   1629 
   1630 token::token(const token &t)
   1631 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
   1632 {
   1633   // Use two statements to work around bug in SGI C++.
   1634   node *tem = t.nd;
   1635   nd = tem ? tem->copy() : 0;
   1636 }
   1637 
   1638 void token::operator=(const token &t)
   1639 {
   1640   delete nd;
   1641   nm = t.nm;
   1642   // Use two statements to work around bug in SGI C++.
   1643   node *tem = t.nd;
   1644   nd = tem ? tem->copy() : 0;
   1645   c = t.c;
   1646   val = t.val;
   1647   dim = t.dim;
   1648   type = t.type;
   1649 }
   1650 
   1651 void token::skip()
   1652 {
   1653   while (space())
   1654     next();
   1655 }
   1656 
   1657 int has_arg()
   1658 {
   1659   while (tok.space())
   1660     tok.next();
   1661   return !tok.newline();
   1662 }
   1663 
   1664 void token::make_space()
   1665 {
   1666   type = TOKEN_SPACE;
   1667 }
   1668 
   1669 void token::make_newline()
   1670 {
   1671   type = TOKEN_NEWLINE;
   1672 }
   1673 
   1674 void token::next()
   1675 {
   1676   if (nd) {
   1677     delete nd;
   1678     nd = 0;
   1679   }
   1680   units x;
   1681   for (;;) {
   1682     node *n = 0;
   1683     int cc = input_stack::get(&n);
   1684     if (cc != escape_char || escape_char == 0) {
   1685     handle_normal_char:
   1686       switch(cc) {
   1687       case PUSH_GROFF_MODE:
   1688 	input_stack::save_compatible_flag(compatible_flag);
   1689 	compatible_flag = 0;
   1690 	continue;
   1691       case PUSH_COMP_MODE:
   1692 	input_stack::save_compatible_flag(compatible_flag);
   1693 	compatible_flag = 1;
   1694 	continue;
   1695       case POP_GROFFCOMP_MODE:
   1696 	compatible_flag = input_stack::get_compatible_flag();
   1697 	continue;
   1698       case BEGIN_QUOTE:
   1699 	input_stack::increase_level();
   1700 	continue;
   1701       case END_QUOTE:
   1702 	input_stack::decrease_level();
   1703 	continue;
   1704       case EOF:
   1705 	type = TOKEN_EOF;
   1706 	return;
   1707       case TRANSPARENT_FILE_REQUEST:
   1708       case TITLE_REQUEST:
   1709       case COPY_FILE_REQUEST:
   1710 #ifdef COLUMN
   1711       case VJUSTIFY_REQUEST:
   1712 #endif /* COLUMN */
   1713 	type = TOKEN_REQUEST;
   1714 	c = cc;
   1715 	return;
   1716       case BEGIN_TRAP:
   1717 	type = TOKEN_BEGIN_TRAP;
   1718 	return;
   1719       case END_TRAP:
   1720 	type = TOKEN_END_TRAP;
   1721 	return;
   1722       case LAST_PAGE_EJECTOR:
   1723 	seen_last_page_ejector = 1;
   1724 	// fall through
   1725       case PAGE_EJECTOR:
   1726 	type = TOKEN_PAGE_EJECTOR;
   1727 	return;
   1728       case ESCAPE_PERCENT:
   1729       ESCAPE_PERCENT:
   1730 	type = TOKEN_HYPHEN_INDICATOR;
   1731 	return;
   1732       case ESCAPE_SPACE:
   1733       ESCAPE_SPACE:
   1734 	type = TOKEN_UNSTRETCHABLE_SPACE;
   1735 	return;
   1736       case ESCAPE_TILDE:
   1737       ESCAPE_TILDE:
   1738 	type = TOKEN_STRETCHABLE_SPACE;
   1739 	return;
   1740       case ESCAPE_COLON:
   1741       ESCAPE_COLON:
   1742 	type = TOKEN_ZERO_WIDTH_BREAK;
   1743 	return;
   1744       case ESCAPE_e:
   1745       ESCAPE_e:
   1746 	type = TOKEN_ESCAPE;
   1747 	return;
   1748       case ESCAPE_E:
   1749 	goto handle_escape_char;
   1750       case ESCAPE_BAR:
   1751       ESCAPE_BAR:
   1752 	type = TOKEN_NODE;
   1753 	nd = new hmotion_node(curenv->get_narrow_space_width(),
   1754 			      curenv->get_fill_color());
   1755 	return;
   1756       case ESCAPE_CIRCUMFLEX:
   1757       ESCAPE_CIRCUMFLEX:
   1758 	type = TOKEN_NODE;
   1759 	nd = new hmotion_node(curenv->get_half_narrow_space_width(),
   1760 			      curenv->get_fill_color());
   1761 	return;
   1762       case ESCAPE_NEWLINE:
   1763 	have_input = 0;
   1764 	break;
   1765       case ESCAPE_LEFT_BRACE:
   1766       ESCAPE_LEFT_BRACE:
   1767 	type = TOKEN_LEFT_BRACE;
   1768 	return;
   1769       case ESCAPE_RIGHT_BRACE:
   1770       ESCAPE_RIGHT_BRACE:
   1771 	type = TOKEN_RIGHT_BRACE;
   1772 	return;
   1773       case ESCAPE_LEFT_QUOTE:
   1774       ESCAPE_LEFT_QUOTE:
   1775 	type = TOKEN_SPECIAL;
   1776 	nm = symbol("ga");
   1777 	return;
   1778       case ESCAPE_RIGHT_QUOTE:
   1779       ESCAPE_RIGHT_QUOTE:
   1780 	type = TOKEN_SPECIAL;
   1781 	nm = symbol("aa");
   1782 	return;
   1783       case ESCAPE_HYPHEN:
   1784       ESCAPE_HYPHEN:
   1785 	type = TOKEN_SPECIAL;
   1786 	nm = symbol("-");
   1787 	return;
   1788       case ESCAPE_UNDERSCORE:
   1789       ESCAPE_UNDERSCORE:
   1790 	type = TOKEN_SPECIAL;
   1791 	nm = symbol("ul");
   1792 	return;
   1793       case ESCAPE_c:
   1794       ESCAPE_c:
   1795 	type = TOKEN_INTERRUPT;
   1796 	return;
   1797       case ESCAPE_BANG:
   1798       ESCAPE_BANG:
   1799 	type = TOKEN_TRANSPARENT;
   1800 	return;
   1801       case ESCAPE_QUESTION:
   1802       ESCAPE_QUESTION:
   1803 	nd = do_non_interpreted();
   1804 	if (nd) {
   1805 	  type = TOKEN_NODE;
   1806 	  return;
   1807 	}
   1808 	break;
   1809       case ESCAPE_AMPERSAND:
   1810       ESCAPE_AMPERSAND:
   1811 	type = TOKEN_DUMMY;
   1812 	return;
   1813       case ESCAPE_RIGHT_PARENTHESIS:
   1814       ESCAPE_RIGHT_PARENTHESIS:
   1815 	type = TOKEN_TRANSPARENT_DUMMY;
   1816 	return;
   1817       case '\b':
   1818 	type = TOKEN_BACKSPACE;
   1819 	return;
   1820       case ' ':
   1821 	type = TOKEN_SPACE;
   1822 	return;
   1823       case '\t':
   1824 	type = TOKEN_TAB;
   1825 	return;
   1826       case '\n':
   1827 	type = TOKEN_NEWLINE;
   1828 	return;
   1829       case '\001':
   1830 	type = TOKEN_LEADER;
   1831 	return;
   1832       case 0:
   1833 	{
   1834 	  assert(n != 0);
   1835 	  token_node *tn = n->get_token_node();
   1836 	  if (tn) {
   1837 	    *this = tn->tk;
   1838 	    delete tn;
   1839 	  }
   1840 	  else {
   1841 	    nd = n;
   1842 	    type = TOKEN_NODE;
   1843 	  }
   1844 	}
   1845 	return;
   1846       default:
   1847 	type = TOKEN_CHAR;
   1848 	c = cc;
   1849 	return;
   1850       }
   1851     }
   1852     else {
   1853     handle_escape_char:
   1854       cc = input_stack::get(&n);
   1855       switch(cc) {
   1856       case '(':
   1857 	nm = read_two_char_escape_name();
   1858 	type = TOKEN_SPECIAL;
   1859 	return;
   1860       case EOF:
   1861 	type = TOKEN_EOF;
   1862 	error("end of input after escape character");
   1863 	return;
   1864       case '`':
   1865 	goto ESCAPE_LEFT_QUOTE;
   1866       case '\'':
   1867 	goto ESCAPE_RIGHT_QUOTE;
   1868       case '-':
   1869 	goto ESCAPE_HYPHEN;
   1870       case '_':
   1871 	goto ESCAPE_UNDERSCORE;
   1872       case '%':
   1873 	goto ESCAPE_PERCENT;
   1874       case ' ':
   1875 	goto ESCAPE_SPACE;
   1876       case '0':
   1877 	nd = new hmotion_node(curenv->get_digit_width(),
   1878 			      curenv->get_fill_color());
   1879 	type = TOKEN_NODE;
   1880 	return;
   1881       case '|':
   1882 	goto ESCAPE_BAR;
   1883       case '^':
   1884 	goto ESCAPE_CIRCUMFLEX;
   1885       case '/':
   1886 	type = TOKEN_ITALIC_CORRECTION;
   1887 	return;
   1888       case ',':
   1889 	type = TOKEN_NODE;
   1890 	nd = new left_italic_corrected_node;
   1891 	return;
   1892       case '&':
   1893 	goto ESCAPE_AMPERSAND;
   1894       case ')':
   1895 	goto ESCAPE_RIGHT_PARENTHESIS;
   1896       case '!':
   1897 	goto ESCAPE_BANG;
   1898       case '?':
   1899 	goto ESCAPE_QUESTION;
   1900       case '~':
   1901 	goto ESCAPE_TILDE;
   1902       case ':':
   1903 	goto ESCAPE_COLON;
   1904       case '"':
   1905 	while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
   1906 	  ;
   1907 	if (cc == '\n')
   1908 	  type = TOKEN_NEWLINE;
   1909 	else
   1910 	  type = TOKEN_EOF;
   1911 	return;
   1912       case '#':			// Like \" but newline is ignored.
   1913 	while ((cc = input_stack::get(0)) != '\n')
   1914 	  if (cc == EOF) {
   1915 	    type = TOKEN_EOF;
   1916 	    return;
   1917 	  }
   1918 	break;
   1919       case '$':
   1920 	{
   1921 	  symbol s = read_escape_name();
   1922 	  if (!(s.is_null() || s.is_empty()))
   1923 	    interpolate_arg(s);
   1924 	  break;
   1925 	}
   1926       case '*':
   1927 	{
   1928 	  symbol s = read_escape_name(WITH_ARGS);
   1929 	  if (!(s.is_null() || s.is_empty())) {
   1930 	    if (have_string_arg) {
   1931 	      have_string_arg = 0;
   1932 	      interpolate_string_with_args(s);
   1933 	    }
   1934 	    else
   1935 	      interpolate_string(s);
   1936 	  }
   1937 	  break;
   1938 	}
   1939       case 'a':
   1940 	nd = new non_interpreted_char_node('\001');
   1941 	type = TOKEN_NODE;
   1942 	return;
   1943       case 'A':
   1944 	c = '0' + do_name_test();
   1945 	type = TOKEN_CHAR;
   1946 	return;
   1947       case 'b':
   1948 	nd = do_bracket();
   1949 	type = TOKEN_NODE;
   1950 	return;
   1951       case 'B':
   1952 	c = '0' + do_expr_test();
   1953 	type = TOKEN_CHAR;
   1954 	return;
   1955       case 'c':
   1956 	goto ESCAPE_c;
   1957       case 'C':
   1958 	nm = get_delim_name();
   1959 	if (nm.is_null())
   1960 	  break;
   1961 	type = TOKEN_SPECIAL;
   1962 	return;
   1963       case 'd':
   1964 	type = TOKEN_NODE;
   1965 	nd = new vmotion_node(curenv->get_size() / 2,
   1966 			      curenv->get_fill_color());
   1967 	return;
   1968       case 'D':
   1969 	nd = read_draw_node();
   1970 	if (!nd)
   1971 	  break;
   1972 	type = TOKEN_NODE;
   1973 	return;
   1974       case 'e':
   1975 	goto ESCAPE_e;
   1976       case 'E':
   1977 	goto handle_escape_char;
   1978       case 'f':
   1979 	{
   1980 	  symbol s = read_escape_name(ALLOW_EMPTY);
   1981 	  if (s.is_null())
   1982 	    break;
   1983 	  const char *p;
   1984 	  for (p = s.contents(); *p != '\0'; p++)
   1985 	    if (!csdigit(*p))
   1986 	      break;
   1987 	  if (*p || s.is_empty())
   1988 	    curenv->set_font(s);
   1989 	  else
   1990 	    curenv->set_font(atoi(s.contents()));
   1991 	  if (!compatible_flag)
   1992 	    have_input = 1;
   1993 	  break;
   1994 	}
   1995       case 'F':
   1996 	{
   1997 	  symbol s = read_escape_name(ALLOW_EMPTY);
   1998 	  if (s.is_null())
   1999 	    break;
   2000 	  curenv->set_family(s);
   2001 	  have_input = 1;
   2002 	  break;
   2003 	}
   2004       case 'g':
   2005 	{
   2006 	  symbol s = read_escape_name();
   2007 	  if (!(s.is_null() || s.is_empty()))
   2008 	    interpolate_number_format(s);
   2009 	  break;
   2010 	}
   2011       case 'h':
   2012 	if (!get_delim_number(&x, 'm'))
   2013 	  break;
   2014 	type = TOKEN_NODE;
   2015 	nd = new hmotion_node(x, curenv->get_fill_color());
   2016 	return;
   2017       case 'H':
   2018 	// don't take height increments relative to previous height if
   2019 	// in compatibility mode
   2020 	if (!compatible_flag && curenv->get_char_height())
   2021 	{
   2022 	  if (get_delim_number(&x, 'z', curenv->get_char_height()))
   2023 	    curenv->set_char_height(x);
   2024 	}
   2025 	else
   2026 	{
   2027 	  if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
   2028 	    curenv->set_char_height(x);
   2029 	}
   2030 	if (!compatible_flag)
   2031 	  have_input = 1;
   2032 	break;
   2033       case 'k':
   2034 	nm = read_escape_name();
   2035 	if (nm.is_null() || nm.is_empty())
   2036 	  break;
   2037 	type = TOKEN_MARK_INPUT;
   2038 	return;
   2039       case 'l':
   2040       case 'L':
   2041 	{
   2042 	  charinfo *s = 0;
   2043 	  if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
   2044 	    break;
   2045 	  if (s == 0)
   2046 	    s = get_charinfo(cc == 'l' ? "ru" : "br");
   2047 	  type = TOKEN_NODE;
   2048 	  node *char_node = curenv->make_char_node(s);
   2049 	  if (cc == 'l')
   2050 	    nd = new hline_node(x, char_node);
   2051 	  else
   2052 	    nd = new vline_node(x, char_node);
   2053 	  return;
   2054 	}
   2055       case 'm':
   2056 	do_glyph_color(read_escape_name(ALLOW_EMPTY));
   2057 	if (!compatible_flag)
   2058 	  have_input = 1;
   2059 	break;
   2060       case 'M':
   2061 	do_fill_color(read_escape_name(ALLOW_EMPTY));
   2062 	if (!compatible_flag)
   2063 	  have_input = 1;
   2064 	break;
   2065       case 'n':
   2066 	{
   2067 	  int inc;
   2068 	  symbol s = read_increment_and_escape_name(&inc);
   2069 	  if (!(s.is_null() || s.is_empty()))
   2070 	    interpolate_number_reg(s, inc);
   2071 	  break;
   2072 	}
   2073       case 'N':
   2074 	if (!get_delim_number(&val, 0))
   2075 	  break;
   2076 	type = TOKEN_NUMBERED_CHAR;
   2077 	return;
   2078       case 'o':
   2079 	nd = do_overstrike();
   2080 	type = TOKEN_NODE;
   2081 	return;
   2082       case 'O':
   2083 	nd = do_suppress(read_escape_name());
   2084 	if (!nd)
   2085 	  break;
   2086 	type = TOKEN_NODE;
   2087 	return;
   2088       case 'p':
   2089 	type = TOKEN_SPREAD;
   2090 	return;
   2091       case 'r':
   2092 	type = TOKEN_NODE;
   2093 	nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
   2094 	return;
   2095       case 'R':
   2096 	do_register();
   2097 	if (!compatible_flag)
   2098 	  have_input = 1;
   2099 	break;
   2100       case 's':
   2101 	if (read_size(&x))
   2102 	  curenv->set_size(x);
   2103 	if (!compatible_flag)
   2104 	  have_input = 1;
   2105 	break;
   2106       case 'S':
   2107 	if (get_delim_number(&x, 0))
   2108 	  curenv->set_char_slant(x);
   2109 	if (!compatible_flag)
   2110 	  have_input = 1;
   2111 	break;
   2112       case 't':
   2113 	type = TOKEN_NODE;
   2114 	nd = new non_interpreted_char_node('\t');
   2115 	return;
   2116       case 'u':
   2117 	type = TOKEN_NODE;
   2118 	nd = new vmotion_node(-curenv->get_size() / 2,
   2119 			      curenv->get_fill_color());
   2120 	return;
   2121       case 'v':
   2122 	if (!get_delim_number(&x, 'v'))
   2123 	  break;
   2124 	type = TOKEN_NODE;
   2125 	nd = new vmotion_node(x, curenv->get_fill_color());
   2126 	return;
   2127       case 'V':
   2128 	{
   2129 	  symbol s = read_escape_name();
   2130 	  if (!(s.is_null() || s.is_empty()))
   2131 	    interpolate_environment_variable(s);
   2132 	  break;
   2133 	}
   2134       case 'w':
   2135 	do_width();
   2136 	break;
   2137       case 'x':
   2138 	if (!get_delim_number(&x, 'v'))
   2139 	  break;
   2140 	type = TOKEN_NODE;
   2141 	nd = new extra_size_node(x);
   2142 	return;
   2143       case 'X':
   2144 	nd = do_special();
   2145 	if (!nd)
   2146 	  break;
   2147 	type = TOKEN_NODE;
   2148 	return;
   2149       case 'Y':
   2150 	{
   2151 	  symbol s = read_escape_name();
   2152 	  if (s.is_null() || s.is_empty())
   2153 	    break;
   2154 	  request_or_macro *p = lookup_request(s);
   2155 	  macro *m = p->to_macro();
   2156 	  if (!m) {
   2157 	    error("can't transparently throughput a request");
   2158 	    break;
   2159 	  }
   2160 	  nd = new special_node(*m);
   2161 	  type = TOKEN_NODE;
   2162 	  return;
   2163 	}
   2164       case 'z':
   2165 	{
   2166 	  next();
   2167 	  if (type == TOKEN_NODE)
   2168 	    nd = new zero_width_node(nd);
   2169 	  else {
   2170   	    charinfo *ci = get_char(1);
   2171 	    if (ci == 0)
   2172 	      break;
   2173 	    node *gn = curenv->make_char_node(ci);
   2174 	    if (gn == 0)
   2175 	      break;
   2176 	    nd = new zero_width_node(gn);
   2177 	    type = TOKEN_NODE;
   2178 	  }
   2179 	  return;
   2180 	}
   2181       case 'Z':
   2182 	nd = do_zero_width();
   2183 	if (nd == 0)
   2184 	  break;
   2185 	type = TOKEN_NODE;
   2186 	return;
   2187       case '{':
   2188 	goto ESCAPE_LEFT_BRACE;
   2189       case '}':
   2190 	goto ESCAPE_RIGHT_BRACE;
   2191       case '\n':
   2192 	break;
   2193       case '[':
   2194 	if (!compatible_flag) {
   2195 	  symbol s = read_long_escape_name(WITH_ARGS);
   2196 	  if (s.is_null() || s.is_empty())
   2197 	    break;
   2198 	  if (have_string_arg) {
   2199 	    have_string_arg = 0;
   2200 	    nm = composite_glyph_name(s);
   2201 	  }
   2202 	  else {
   2203 	    const char *gn = check_unicode_name(s.contents());
   2204 	    if (gn) {
   2205 	      const char *gn_decomposed = decompose_unicode(gn);
   2206 	      if (gn_decomposed)
   2207 		gn = &gn_decomposed[1];
   2208 	      const char *groff_gn = unicode_to_glyph_name(gn);
   2209 	      if (groff_gn)
   2210 		nm = symbol(groff_gn);
   2211 	      else {
   2212 		char *buf = new char[strlen(gn) + 1 + 1];
   2213 		strcpy(buf, "u");
   2214 		strcat(buf, gn);
   2215 		nm = symbol(buf);
   2216 		a_delete buf;
   2217 	      }
   2218 	    }
   2219 	    else
   2220 	      nm = symbol(s.contents());
   2221 	  }
   2222 	  type = TOKEN_SPECIAL;
   2223 	  return;
   2224 	}
   2225 	goto handle_normal_char;
   2226       default:
   2227 	if (cc != escape_char && cc != '.')
   2228 	  warning(WARN_ESCAPE, "escape character ignored before %1",
   2229 		  input_char_description(cc));
   2230 	goto handle_normal_char;
   2231       }
   2232     }
   2233   }
   2234 }
   2235 
   2236 int token::operator==(const token &t)
   2237 {
   2238   if (type != t.type)
   2239     return 0;
   2240   switch(type) {
   2241   case TOKEN_CHAR:
   2242     return c == t.c;
   2243   case TOKEN_SPECIAL:
   2244     return nm == t.nm;
   2245   case TOKEN_NUMBERED_CHAR:
   2246     return val == t.val;
   2247   default:
   2248     return 1;
   2249   }
   2250 }
   2251 
   2252 int token::operator!=(const token &t)
   2253 {
   2254   return !(*this == t);
   2255 }
   2256 
   2257 // is token a suitable delimiter (like ')?
   2258 
   2259 int token::delimiter(int err)
   2260 {
   2261   switch(type) {
   2262   case TOKEN_CHAR:
   2263     switch(c) {
   2264     case '0':
   2265     case '1':
   2266     case '2':
   2267     case '3':
   2268     case '4':
   2269     case '5':
   2270     case '6':
   2271     case '7':
   2272     case '8':
   2273     case '9':
   2274     case '+':
   2275     case '-':
   2276     case '/':
   2277     case '*':
   2278     case '%':
   2279     case '<':
   2280     case '>':
   2281     case '=':
   2282     case '&':
   2283     case ':':
   2284     case '(':
   2285     case ')':
   2286     case '.':
   2287       if (err)
   2288 	error("cannot use character `%1' as a starting delimiter", char(c));
   2289       return 0;
   2290     default:
   2291       return 1;
   2292     }
   2293   case TOKEN_NODE:
   2294   case TOKEN_SPACE:
   2295   case TOKEN_STRETCHABLE_SPACE:
   2296   case TOKEN_UNSTRETCHABLE_SPACE:
   2297   case TOKEN_TAB:
   2298   case TOKEN_NEWLINE:
   2299     if (err)
   2300       error("cannot use %1 as a starting delimiter", description());
   2301     return 0;
   2302   default:
   2303     return 1;
   2304   }
   2305 }
   2306 
   2307 const char *token::description()
   2308 {
   2309   static char buf[4];
   2310   switch (type) {
   2311   case TOKEN_BACKSPACE:
   2312     return "a backspace character";
   2313   case TOKEN_CHAR:
   2314     buf[0] = '`';
   2315     buf[1] = c;
   2316     buf[2] = '\'';
   2317     buf[3] = '\0';
   2318     return buf;
   2319   case TOKEN_DUMMY:
   2320     return "`\\&'";
   2321   case TOKEN_ESCAPE:
   2322     return "`\\e'";
   2323   case TOKEN_HYPHEN_INDICATOR:
   2324     return "`\\%'";
   2325   case TOKEN_INTERRUPT:
   2326     return "`\\c'";
   2327   case TOKEN_ITALIC_CORRECTION:
   2328     return "`\\/'";
   2329   case TOKEN_LEADER:
   2330     return "a leader character";
   2331   case TOKEN_LEFT_BRACE:
   2332     return "`\\{'";
   2333   case TOKEN_MARK_INPUT:
   2334     return "`\\k'";
   2335   case TOKEN_NEWLINE:
   2336     return "newline";
   2337   case TOKEN_NODE:
   2338     return "a node";
   2339   case TOKEN_NUMBERED_CHAR:
   2340     return "`\\N'";
   2341   case TOKEN_RIGHT_BRACE:
   2342     return "`\\}'";
   2343   case TOKEN_SPACE:
   2344     return "a space";
   2345   case TOKEN_SPECIAL:
   2346     return "a special character";
   2347   case TOKEN_SPREAD:
   2348     return "`\\p'";
   2349   case TOKEN_STRETCHABLE_SPACE:
   2350     return "`\\~'";
   2351   case TOKEN_UNSTRETCHABLE_SPACE:
   2352     return "`\\ '";
   2353   case TOKEN_TAB:
   2354     return "a tab character";
   2355   case TOKEN_TRANSPARENT:
   2356     return "`\\!'";
   2357   case TOKEN_TRANSPARENT_DUMMY:
   2358     return "`\\)'";
   2359   case TOKEN_ZERO_WIDTH_BREAK:
   2360     return "`\\:'";
   2361   case TOKEN_EOF:
   2362     return "end of input";
   2363   default:
   2364     break;
   2365   }
   2366   return "a magic token";
   2367 }
   2368 
   2369 void skip_line()
   2370 {
   2371   while (!tok.newline())
   2372     if (tok.eof())
   2373       return;
   2374     else
   2375       tok.next();
   2376   tok.next();
   2377 }
   2378 
   2379 void compatible()
   2380 {
   2381   int n;
   2382   if (has_arg() && get_integer(&n))
   2383     compatible_flag = n != 0;
   2384   else
   2385     compatible_flag = 1;
   2386   skip_line();
   2387 }
   2388 
   2389 static void empty_name_warning(int required)
   2390 {
   2391   if (tok.newline() || tok.eof()) {
   2392     if (required)
   2393       warning(WARN_MISSING, "missing name");
   2394   }
   2395   else if (tok.right_brace() || tok.tab()) {
   2396     const char *start = tok.description();
   2397     do {
   2398       tok.next();
   2399     } while (tok.space() || tok.right_brace() || tok.tab());
   2400     if (!tok.newline() && !tok.eof())
   2401       error("%1 is not allowed before an argument", start);
   2402     else if (required)
   2403       warning(WARN_MISSING, "missing name");
   2404   }
   2405   else if (required)
   2406     error("name expected (got %1)", tok.description());
   2407   else
   2408     error("name expected (got %1): treated as missing", tok.description());
   2409 }
   2410 
   2411 static void non_empty_name_warning()
   2412 {
   2413   if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
   2414       && !tok.right_brace()
   2415       // We don't want to give a warning for .el\{
   2416       && !tok.left_brace())
   2417     error("%1 is not allowed in a name", tok.description());
   2418 }
   2419 
   2420 symbol get_name(int required)
   2421 {
   2422   if (compatible_flag) {
   2423     char buf[3];
   2424     tok.skip();
   2425     if ((buf[0] = tok.ch()) != 0) {
   2426       tok.next();
   2427       if ((buf[1] = tok.ch()) != 0) {
   2428 	buf[2] = 0;
   2429 	tok.make_space();
   2430       }
   2431       else
   2432 	non_empty_name_warning();
   2433       return symbol(buf);
   2434     }
   2435     else {
   2436       empty_name_warning(required);
   2437       return NULL_SYMBOL;
   2438     }
   2439   }
   2440   else
   2441     return get_long_name(required);
   2442 }
   2443 
   2444 symbol get_long_name(int required)
   2445 {
   2446   return do_get_long_name(required, 0);
   2447 }
   2448 
   2449 static symbol do_get_long_name(int required, char end)
   2450 {
   2451   while (tok.space())
   2452     tok.next();
   2453   char abuf[ABUF_SIZE];
   2454   char *buf = abuf;
   2455   int buf_size = ABUF_SIZE;
   2456   int i = 0;
   2457   for (;;) {
   2458     // If end != 0 we normally have to append a null byte
   2459     if (i + 2 > buf_size) {
   2460       if (buf == abuf) {
   2461 	buf = new char[ABUF_SIZE*2];
   2462 	memcpy(buf, abuf, buf_size);
   2463 	buf_size = ABUF_SIZE*2;
   2464       }
   2465       else {
   2466 	char *old_buf = buf;
   2467 	buf = new char[buf_size*2];
   2468 	memcpy(buf, old_buf, buf_size);
   2469 	buf_size *= 2;
   2470 	a_delete old_buf;
   2471       }
   2472     }
   2473     if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
   2474       break;
   2475     i++;
   2476     tok.next();
   2477   }
   2478   if (i == 0) {
   2479     empty_name_warning(required);
   2480     return NULL_SYMBOL;
   2481   }
   2482   if (end && buf[i] == end)
   2483     buf[i+1] = '\0';
   2484   else
   2485     non_empty_name_warning();
   2486   if (buf == abuf)
   2487     return symbol(buf);
   2488   else {
   2489     symbol s(buf);
   2490     a_delete buf;
   2491     return s;
   2492   }
   2493 }
   2494 
   2495 void exit_troff()
   2496 {
   2497   exit_started = 1;
   2498   topdiv->set_last_page();
   2499   if (!end_macro_name.is_null()) {
   2500     spring_trap(end_macro_name);
   2501     tok.next();
   2502     process_input_stack();
   2503   }
   2504   curenv->final_break();
   2505   tok.next();
   2506   process_input_stack();
   2507   end_diversions();
   2508   if (topdiv->get_page_length() > 0) {
   2509     done_end_macro = 1;
   2510     topdiv->set_ejecting();
   2511     static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
   2512     input_stack::push(make_temp_iterator((char *)buf));
   2513     topdiv->space(topdiv->get_page_length(), 1);
   2514     tok.next();
   2515     process_input_stack();
   2516     seen_last_page_ejector = 1;	// should be set already
   2517     topdiv->set_ejecting();
   2518     push_page_ejector();
   2519     topdiv->space(topdiv->get_page_length(), 1);
   2520     tok.next();
   2521     process_input_stack();
   2522   }
   2523   // This will only happen if a trap-invoked macro starts a diversion,
   2524   // or if vertical position traps have been disabled.
   2525   cleanup_and_exit(0);
   2526 }
   2527 
   2528 // This implements .ex.  The input stack must be cleared before calling
   2529 // exit_troff().
   2530 
   2531 void exit_request()
   2532 {
   2533   input_stack::clear();
   2534   if (exit_started)
   2535     tok.next();
   2536   else
   2537     exit_troff();
   2538 }
   2539 
   2540 void return_macro_request()
   2541 {
   2542   if (has_arg() && tok.ch())
   2543     input_stack::pop_macro();
   2544   input_stack::pop_macro();
   2545   tok.next();
   2546 }
   2547 
   2548 void end_macro()
   2549 {
   2550   end_macro_name = get_name();
   2551   skip_line();
   2552 }
   2553 
   2554 void blank_line_macro()
   2555 {
   2556   blank_line_macro_name = get_name();
   2557   skip_line();
   2558 }
   2559 
   2560 static void trapping_blank_line()
   2561 {
   2562   if (!blank_line_macro_name.is_null())
   2563     spring_trap(blank_line_macro_name);
   2564   else
   2565     blank_line();
   2566 }
   2567 
   2568 void do_request()
   2569 {
   2570   int old_compatible_flag = compatible_flag;
   2571   compatible_flag = 0;
   2572   symbol nm = get_name();
   2573   if (nm.is_null())
   2574     skip_line();
   2575   else
   2576     interpolate_macro(nm);
   2577   compatible_flag = old_compatible_flag;
   2578 }
   2579 
   2580 inline int possibly_handle_first_page_transition()
   2581 {
   2582   if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
   2583     handle_first_page_transition();
   2584     return 1;
   2585   }
   2586   else
   2587     return 0;
   2588 }
   2589 
   2590 static int transparent_translate(int cc)
   2591 {
   2592   if (!invalid_input_char(cc)) {
   2593     charinfo *ci = charset_table[cc];
   2594     switch (ci->get_special_translation(1)) {
   2595     case charinfo::TRANSLATE_SPACE:
   2596       return ' ';
   2597     case charinfo::TRANSLATE_STRETCHABLE_SPACE:
   2598       return ESCAPE_TILDE;
   2599     case charinfo::TRANSLATE_DUMMY:
   2600       return ESCAPE_AMPERSAND;
   2601     case charinfo::TRANSLATE_HYPHEN_INDICATOR:
   2602       return ESCAPE_PERCENT;
   2603     }
   2604     // This is really ugly.
   2605     ci = ci->get_translation(1);
   2606     if (ci) {
   2607       int c = ci->get_ascii_code();
   2608       if (c != '\0')
   2609 	return c;
   2610       error("can't translate %1 to special character `%2'"
   2611 	    " in transparent throughput",
   2612 	    input_char_description(cc),
   2613 	    ci->nm.contents());
   2614     }
   2615   }
   2616   return cc;
   2617 }
   2618 
   2619 class int_stack {
   2620   struct int_stack_element {
   2621     int n;
   2622     int_stack_element *next;
   2623   } *top;
   2624 public:
   2625   int_stack();
   2626   ~int_stack();
   2627   void push(int);
   2628   int is_empty();
   2629   int pop();
   2630 };
   2631 
   2632 int_stack::int_stack()
   2633 {
   2634   top = 0;
   2635 }
   2636 
   2637 int_stack::~int_stack()
   2638 {
   2639   while (top != 0) {
   2640     int_stack_element *temp = top;
   2641     top = top->next;
   2642     delete temp;
   2643   }
   2644 }
   2645 
   2646 int int_stack::is_empty()
   2647 {
   2648   return top == 0;
   2649 }
   2650 
   2651 void int_stack::push(int n)
   2652 {
   2653   int_stack_element *p = new int_stack_element;
   2654   p->next = top;
   2655   p->n = n;
   2656   top = p;
   2657 }
   2658 
   2659 int int_stack::pop()
   2660 {
   2661   assert(top != 0);
   2662   int_stack_element *p = top;
   2663   top = top->next;
   2664   int n = p->n;
   2665   delete p;
   2666   return n;
   2667 }
   2668 
   2669 int node::reread(int *)
   2670 {
   2671   return 0;
   2672 }
   2673 
   2674 int global_diverted_space = 0;
   2675 
   2676 int diverted_space_node::reread(int *bolp)
   2677 {
   2678   global_diverted_space = 1;
   2679   if (curenv->get_fill())
   2680     trapping_blank_line();
   2681   else
   2682     curdiv->space(n);
   2683   global_diverted_space = 0;
   2684   *bolp = 1;
   2685   return 1;
   2686 }
   2687 
   2688 int diverted_copy_file_node::reread(int *bolp)
   2689 {
   2690   curdiv->copy_file(filename.contents());
   2691   *bolp = 1;
   2692   return 1;
   2693 }
   2694 
   2695 int word_space_node::reread(int *)
   2696 {
   2697   if (unformat) {
   2698     for (width_list *w = orig_width; w; w = w->next)
   2699       curenv->space(w->width, w->sentence_width);
   2700     unformat = 0;
   2701     return 1;
   2702   }
   2703   return 0;
   2704 }
   2705 
   2706 int unbreakable_space_node::reread(int *)
   2707 {
   2708   return 0;
   2709 }
   2710 
   2711 int hmotion_node::reread(int *)
   2712 {
   2713   if (unformat && was_tab) {
   2714     curenv->handle_tab(0);
   2715     unformat = 0;
   2716     return 1;
   2717   }
   2718   return 0;
   2719 }
   2720 
   2721 void process_input_stack()
   2722 {
   2723   int_stack trap_bol_stack;
   2724   int bol = 1;
   2725   for (;;) {
   2726     int suppress_next = 0;
   2727     switch (tok.type) {
   2728     case token::TOKEN_CHAR:
   2729       {
   2730 	unsigned char ch = tok.c;
   2731 	if (bol && !have_input
   2732 	    && (ch == curenv->control_char
   2733 		|| ch == curenv->no_break_control_char)) {
   2734 	  break_flag = ch == curenv->control_char;
   2735 	  // skip tabs as well as spaces here
   2736 	  do {
   2737 	    tok.next();
   2738 	  } while (tok.white_space());
   2739 	  symbol nm = get_name();
   2740 #if defined(DEBUGGING)
   2741 	  if (debug_state) {
   2742 	    if (! nm.is_null()) {
   2743 	      if (strcmp(nm.contents(), "test") == 0) {
   2744 		fprintf(stderr, "found it!\n");
   2745 		fflush(stderr);
   2746 	      }
   2747 	      fprintf(stderr, "interpreting [%s]", nm.contents());
   2748 	      if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
   2749 		fprintf(stderr, " currently in diversion: %s",
   2750 			curdiv->get_diversion_name());
   2751 	      fprintf(stderr, "\n");
   2752 	      fflush(stderr);
   2753 	    }
   2754 	  }
   2755 #endif
   2756 	  if (nm.is_null())
   2757 	    skip_line();
   2758 	  else {
   2759 	    interpolate_macro(nm);
   2760 #if defined(DEBUGGING)
   2761 	    if (debug_state) {
   2762 	      fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
   2763 	      curenv->dump_troff_state();
   2764 	    }
   2765 #endif
   2766 	  }
   2767 	  suppress_next = 1;
   2768 	}
   2769 	else {
   2770 	  if (possibly_handle_first_page_transition())
   2771 	    ;
   2772 	  else {
   2773 	    for (;;) {
   2774 #if defined(DEBUGGING)
   2775 	      if (debug_state) {
   2776 		fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
   2777 	      }
   2778 #endif
   2779 	      curenv->add_char(charset_table[ch]);
   2780 	      tok.next();
   2781 	      if (tok.type != token::TOKEN_CHAR)
   2782 		break;
   2783 	      ch = tok.c;
   2784 	    }
   2785 	    suppress_next = 1;
   2786 	    bol = 0;
   2787 	  }
   2788 	}
   2789 	break;
   2790       }
   2791     case token::TOKEN_TRANSPARENT:
   2792       {
   2793 	if (bol) {
   2794 	  if (possibly_handle_first_page_transition())
   2795 	    ;
   2796 	  else {
   2797 	    int cc;
   2798 	    do {
   2799 	      node *n;
   2800 	      cc = get_copy(&n);
   2801 	      if (cc != EOF) {
   2802 		if (cc != '\0')
   2803 		  curdiv->transparent_output(transparent_translate(cc));
   2804 		else
   2805 		  curdiv->transparent_output(n);
   2806 	      }
   2807 	    } while (cc != '\n' && cc != EOF);
   2808 	    if (cc == EOF)
   2809 	      curdiv->transparent_output('\n');
   2810 	  }
   2811 	}
   2812 	break;
   2813       }
   2814     case token::TOKEN_NEWLINE:
   2815       {
   2816 	if (bol && !old_have_input
   2817 	    && !curenv->get_prev_line_interrupted())
   2818 	  trapping_blank_line();
   2819 	else {
   2820 	  curenv->newline();
   2821 	  bol = 1;
   2822 	}
   2823 	break;
   2824       }
   2825     case token::TOKEN_REQUEST:
   2826       {
   2827 	int request_code = tok.c;
   2828 	tok.next();
   2829 	switch (request_code) {
   2830 	case TITLE_REQUEST:
   2831 	  title();
   2832 	  break;
   2833 	case COPY_FILE_REQUEST:
   2834 	  copy_file();
   2835 	  break;
   2836 	case TRANSPARENT_FILE_REQUEST:
   2837 	  transparent_file();
   2838 	  break;
   2839 #ifdef COLUMN
   2840 	case VJUSTIFY_REQUEST:
   2841 	  vjustify();
   2842 	  break;
   2843 #endif /* COLUMN */
   2844 	default:
   2845 	  assert(0);
   2846 	  break;
   2847 	}
   2848 	suppress_next = 1;
   2849 	break;
   2850       }
   2851     case token::TOKEN_SPACE:
   2852       {
   2853 	if (possibly_handle_first_page_transition())
   2854 	  ;
   2855 	else if (bol && !curenv->get_prev_line_interrupted()) {
   2856 	  int nspaces = 0;
   2857 	  // save space_width now so that it isn't changed by \f or \s
   2858 	  // which we wouldn't notice here
   2859 	  hunits space_width = curenv->get_space_width();
   2860 	  do {
   2861 	    nspaces += tok.nspaces();
   2862 	    tok.next();
   2863 	  } while (tok.space());
   2864 	  if (tok.newline())
   2865 	    trapping_blank_line();
   2866 	  else {
   2867 	    push_token(tok);
   2868 	    curenv->do_break();
   2869 	    curenv->add_node(new hmotion_node(space_width * nspaces,
   2870 					      curenv->get_fill_color()));
   2871 	    bol = 0;
   2872 	  }
   2873 	}
   2874 	else {
   2875 	  curenv->space();
   2876 	  bol = 0;
   2877 	}
   2878 	break;
   2879       }
   2880     case token::TOKEN_EOF:
   2881       return;
   2882     case token::TOKEN_NODE:
   2883       {
   2884 	if (possibly_handle_first_page_transition())
   2885 	  ;
   2886 	else if (tok.nd->reread(&bol)) {
   2887 	  delete tok.nd;
   2888 	  tok.nd = 0;
   2889 	}
   2890 	else {
   2891 	  curenv->add_node(tok.nd);
   2892 	  tok.nd = 0;
   2893 	  bol = 0;
   2894 	  curenv->possibly_break_line(1);
   2895 	}
   2896 	break;
   2897       }
   2898     case token::TOKEN_PAGE_EJECTOR:
   2899       {
   2900 	continue_page_eject();
   2901 	// I think we just want to preserve bol.
   2902 	// bol = 1;
   2903 	break;
   2904       }
   2905     case token::TOKEN_BEGIN_TRAP:
   2906       {
   2907 	trap_bol_stack.push(bol);
   2908 	bol = 1;
   2909 	have_input = 0;
   2910 	break;
   2911       }
   2912     case token::TOKEN_END_TRAP:
   2913       {
   2914 	if (trap_bol_stack.is_empty())
   2915 	  error("spurious end trap token detected!");
   2916 	else
   2917 	  bol = trap_bol_stack.pop();
   2918 	have_input = 0;
   2919 
   2920 	/* I'm not totally happy about this.  But I can't think of any other
   2921 	  way to do it.  Doing an output_pending_lines() whenever a
   2922 	  TOKEN_END_TRAP is detected doesn't work: for example,
   2923 
   2924 	  .wh -1i x
   2925 	  .de x
   2926 	  'bp
   2927 	  ..
   2928 	  .wh -.5i y
   2929 	  .de y
   2930 	  .tl ''-%-''
   2931 	  ..
   2932 	  .br
   2933 	  .ll .5i
   2934 	  .sp |\n(.pu-1i-.5v
   2935 	  a\%very\%very\%long\%word
   2936 
   2937 	  will print all but the first lines from the word immediately
   2938 	  after the footer, rather than on the next page. */
   2939 
   2940 	if (trap_bol_stack.is_empty())
   2941 	  curenv->output_pending_lines();
   2942 	break;
   2943       }
   2944     default:
   2945       {
   2946 	bol = 0;
   2947 	tok.process();
   2948 	break;
   2949       }
   2950     }
   2951     if (!suppress_next)
   2952       tok.next();
   2953     trap_sprung_flag = 0;
   2954   }
   2955 }
   2956 
   2957 #ifdef WIDOW_CONTROL
   2958 
   2959 void flush_pending_lines()
   2960 {
   2961   while (!tok.newline() && !tok.eof())
   2962     tok.next();
   2963   curenv->output_pending_lines();
   2964   tok.next();
   2965 }
   2966 
   2967 #endif /* WIDOW_CONTROL */
   2968 
   2969 request_or_macro::request_or_macro()
   2970 {
   2971 }
   2972 
   2973 macro *request_or_macro::to_macro()
   2974 {
   2975   return 0;
   2976 }
   2977 
   2978 request::request(REQUEST_FUNCP pp) : p(pp)
   2979 {
   2980 }
   2981 
   2982 void request::invoke(symbol)
   2983 {
   2984   (*p)();
   2985 }
   2986 
   2987 struct char_block {
   2988   enum { SIZE = 128 };
   2989   unsigned char s[SIZE];
   2990   char_block *next;
   2991   char_block();
   2992 };
   2993 
   2994 char_block::char_block()
   2995 : next(0)
   2996 {
   2997 }
   2998 
   2999 class char_list {
   3000 public:
   3001   char_list();
   3002   ~char_list();
   3003   void append(unsigned char);
   3004   void set(unsigned char, int);
   3005   unsigned char get(int);
   3006   int length();
   3007 private:
   3008   unsigned char *ptr;
   3009   int len;
   3010   char_block *head;
   3011   char_block *tail;
   3012   friend class macro_header;
   3013   friend class string_iterator;
   3014 };
   3015 
   3016 char_list::char_list()
   3017 : ptr(0), len(0), head(0), tail(0)
   3018 {
   3019 }
   3020 
   3021 char_list::~char_list()
   3022 {
   3023   while (head != 0) {
   3024     char_block *tem = head;
   3025     head = head->next;
   3026     delete tem;
   3027   }
   3028 }
   3029 
   3030 int char_list::length()
   3031 {
   3032   return len;
   3033 }
   3034 
   3035 void char_list::append(unsigned char c)
   3036 {
   3037   if (tail == 0) {
   3038     head = tail = new char_block;
   3039     ptr = tail->s;
   3040   }
   3041   else {
   3042     if (ptr >= tail->s + char_block::SIZE) {
   3043       tail->next = new char_block;
   3044       tail = tail->next;
   3045       ptr = tail->s;
   3046     }
   3047   }
   3048   *ptr++ = c;
   3049   len++;
   3050 }
   3051 
   3052 void char_list::set(unsigned char c, int offset)
   3053 {
   3054   assert(len > offset);
   3055   // optimization for access at the end
   3056   int boundary = len - len % char_block::SIZE;
   3057   if (offset >= boundary) {
   3058     *(tail->s + offset - boundary) = c;
   3059     return;
   3060   }
   3061   char_block *tem = head;
   3062   int l = 0;
   3063   for (;;) {
   3064     l += char_block::SIZE;
   3065     if (l > offset) {
   3066       *(tem->s + offset % char_block::SIZE) = c;
   3067       return;
   3068     }
   3069     tem = tem->next;
   3070   }
   3071 }
   3072 
   3073 unsigned char char_list::get(int offset)
   3074 {
   3075   assert(len > offset);
   3076   // optimization for access at the end
   3077   int boundary = len - len % char_block::SIZE;
   3078   if (offset >= boundary)
   3079     return *(tail->s + offset - boundary);
   3080   char_block *tem = head;
   3081   int l = 0;
   3082   for (;;) {
   3083     l += char_block::SIZE;
   3084     if (l > offset)
   3085       return *(tem->s + offset % char_block::SIZE);
   3086     tem = tem->next;
   3087   }
   3088 }
   3089 
   3090 class node_list {
   3091   node *head;
   3092   node *tail;
   3093 public:
   3094   node_list();
   3095   ~node_list();
   3096   void append(node *);
   3097   int length();
   3098   node *extract();
   3099 
   3100   friend class macro_header;
   3101   friend class string_iterator;
   3102 };
   3103 
   3104 void node_list::append(node *n)
   3105 {
   3106   if (head == 0) {
   3107     n->next = 0;
   3108     head = tail = n;
   3109   }
   3110   else {
   3111     n->next = 0;
   3112     tail = tail->next = n;
   3113   }
   3114 }
   3115 
   3116 int node_list::length()
   3117 {
   3118   int total = 0;
   3119   for (node *n = head; n != 0; n = n->next)
   3120     ++total;
   3121   return total;
   3122 }
   3123 
   3124 node_list::node_list()
   3125 {
   3126   head = tail = 0;
   3127 }
   3128 
   3129 node *node_list::extract()
   3130 {
   3131   node *temp = head;
   3132   head = tail = 0;
   3133   return temp;
   3134 }
   3135 
   3136 node_list::~node_list()
   3137 {
   3138   delete_node_list(head);
   3139 }
   3140 
   3141 class macro_header {
   3142 public:
   3143   int count;
   3144   char_list cl;
   3145   node_list nl;
   3146   macro_header() { count = 1; }
   3147   macro_header *copy(int);
   3148 };
   3149 
   3150 macro::~macro()
   3151 {
   3152   if (p != 0 && --(p->count) <= 0)
   3153     delete p;
   3154 }
   3155 
   3156 macro::macro()
   3157 : is_a_diversion(0)
   3158 {
   3159   if (!input_stack::get_location(1, &filename, &lineno)) {
   3160     filename = 0;
   3161     lineno = 0;
   3162   }
   3163   len = 0;
   3164   empty_macro = 1;
   3165   p = 0;
   3166 }
   3167 
   3168 macro::macro(const macro &m)
   3169 : filename(m.filename), lineno(m.lineno), len(m.len),
   3170   empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion), p(m.p)
   3171 {
   3172   if (p != 0)
   3173     p->count++;
   3174 }
   3175 
   3176 macro::macro(int is_div)
   3177   : is_a_diversion(is_div)
   3178 {
   3179   if (!input_stack::get_location(1, &filename, &lineno)) {
   3180     filename = 0;
   3181     lineno = 0;
   3182   }
   3183   len = 0;
   3184   empty_macro = 1;
   3185   p = 0;
   3186 }
   3187 
   3188 int macro::is_diversion()
   3189 {
   3190   return is_a_diversion;
   3191 }
   3192 
   3193 macro &macro::operator=(const macro &m)
   3194 {
   3195   // don't assign object
   3196   if (m.p != 0)
   3197     m.p->count++;
   3198   if (p != 0 && --(p->count) <= 0)
   3199     delete p;
   3200   p = m.p;
   3201   filename = m.filename;
   3202   lineno = m.lineno;
   3203   len = m.len;
   3204   empty_macro = m.empty_macro;
   3205   is_a_diversion = m.is_a_diversion;
   3206   return *this;
   3207 }
   3208 
   3209 void macro::append(unsigned char c)
   3210 {
   3211   assert(c != 0);
   3212   if (p == 0)
   3213     p = new macro_header;
   3214   if (p->cl.length() != len) {
   3215     macro_header *tem = p->copy(len);
   3216     if (--(p->count) <= 0)
   3217       delete p;
   3218     p = tem;
   3219   }
   3220   p->cl.append(c);
   3221   ++len;
   3222   if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
   3223     empty_macro = 0;
   3224 }
   3225 
   3226 void macro::set(unsigned char c, int offset)
   3227 {
   3228   assert(p != 0);
   3229   assert(c != 0);
   3230   p->cl.set(c, offset);
   3231 }
   3232 
   3233 unsigned char macro::get(int offset)
   3234 {
   3235   assert(p != 0);
   3236   return p->cl.get(offset);
   3237 }
   3238 
   3239 int macro::length()
   3240 {
   3241   return len;
   3242 }
   3243 
   3244 void macro::append_str(const char *s)
   3245 {
   3246   int i = 0;
   3247 
   3248   if (s) {
   3249     while (s[i] != (char)0) {
   3250       append(s[i]);
   3251       i++;
   3252     }
   3253   }
   3254 }
   3255 
   3256 void macro::append(node *n)
   3257 {
   3258   assert(n != 0);
   3259   if (p == 0)
   3260     p = new macro_header;
   3261   if (p->cl.length() != len) {
   3262     macro_header *tem = p->copy(len);
   3263     if (--(p->count) <= 0)
   3264       delete p;
   3265     p = tem;
   3266   }
   3267   p->cl.append(0);
   3268   p->nl.append(n);
   3269   ++len;
   3270   empty_macro = 0;
   3271 }
   3272 
   3273 void macro::append_unsigned(unsigned int i)
   3274 {
   3275   unsigned int j = i / 10;
   3276   if (j != 0)
   3277     append_unsigned(j);
   3278   append(((unsigned char)(((int)'0') + i % 10)));
   3279 }
   3280 
   3281 void macro::append_int(int i)
   3282 {
   3283   if (i < 0) {
   3284     append('-');
   3285     i = -i;
   3286   }
   3287   append_unsigned((unsigned int)i);
   3288 }
   3289 
   3290 void macro::print_size()
   3291 {
   3292   errprint("%1", len);
   3293 }
   3294 
   3295 // make a copy of the first n bytes
   3296 
   3297 macro_header *macro_header::copy(int n)
   3298 {
   3299   macro_header *p = new macro_header;
   3300   char_block *bp = cl.head;
   3301   unsigned char *ptr = bp->s;
   3302   node *nd = nl.head;
   3303   while (--n >= 0) {
   3304     if (ptr >= bp->s + char_block::SIZE) {
   3305       bp = bp->next;
   3306       ptr = bp->s;
   3307     }
   3308     unsigned char c = *ptr++;
   3309     p->cl.append(c);
   3310     if (c == 0) {
   3311       p->nl.append(nd->copy());
   3312       nd = nd->next;
   3313     }
   3314   }
   3315   return p;
   3316 }
   3317 
   3318 void print_macros()
   3319 {
   3320   object_dictionary_iterator iter(request_dictionary);
   3321   request_or_macro *rm;
   3322   symbol s;
   3323   while (iter.get(&s, (object **)&rm)) {
   3324     assert(!s.is_null());
   3325     macro *m = rm->to_macro();
   3326     if (m) {
   3327       errprint("%1\t", s.contents());
   3328       m->print_size();
   3329       errprint("\n");
   3330     }
   3331   }
   3332   fflush(stderr);
   3333   skip_line();
   3334 }
   3335 
   3336 class string_iterator : public input_iterator {
   3337   macro mac;
   3338   const char *how_invoked;
   3339   int newline_flag;
   3340   int lineno;
   3341   char_block *bp;
   3342   int count;			// of characters remaining
   3343   node *nd;
   3344   int saved_compatible_flag;
   3345 protected:
   3346   symbol nm;
   3347   string_iterator();
   3348 public:
   3349   string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
   3350   int fill(node **);
   3351   int peek();
   3352   int get_location(int, const char **, int *);
   3353   void backtrace();
   3354   void save_compatible_flag(int f) { saved_compatible_flag = f; }
   3355   int get_compatible_flag() { return saved_compatible_flag; }
   3356   int is_diversion();
   3357 };
   3358 
   3359 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
   3360 : input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
   3361   lineno(1), nm(s)
   3362 {
   3363   count = mac.len;
   3364   if (count != 0) {
   3365     bp = mac.p->cl.head;
   3366     nd = mac.p->nl.head;
   3367     ptr = eptr = bp->s;
   3368   }
   3369   else {
   3370     bp = 0;
   3371     nd = 0;
   3372     ptr = eptr = 0;
   3373   }
   3374 }
   3375 
   3376 string_iterator::string_iterator()
   3377 {
   3378   bp = 0;
   3379   nd = 0;
   3380   ptr = eptr = 0;
   3381   newline_flag = 0;
   3382   how_invoked = 0;
   3383   lineno = 1;
   3384   count = 0;
   3385 }
   3386 
   3387 int string_iterator::is_diversion()
   3388 {
   3389   return mac.is_diversion();
   3390 }
   3391 
   3392 int string_iterator::fill(node **np)
   3393 {
   3394   if (newline_flag)
   3395     lineno++;
   3396   newline_flag = 0;
   3397   if (count <= 0)
   3398     return EOF;
   3399   const unsigned char *p = eptr;
   3400   if (p >= bp->s + char_block::SIZE) {
   3401     bp = bp->next;
   3402     p = bp->s;
   3403   }
   3404   if (*p == '\0') {
   3405     if (np) {
   3406       *np = nd->copy();
   3407       if (is_diversion())
   3408 	(*np)->div_nest_level = input_stack::get_div_level();
   3409       else
   3410 	(*np)->div_nest_level = 0;
   3411     }
   3412     nd = nd->next;
   3413     eptr = ptr = p + 1;
   3414     count--;
   3415     return 0;
   3416   }
   3417   const unsigned char *e = bp->s + char_block::SIZE;
   3418   if (e - p > count)
   3419     e = p + count;
   3420   ptr = p;
   3421   while (p < e) {
   3422     unsigned char c = *p;
   3423     if (c == '\n' || c == ESCAPE_NEWLINE) {
   3424       newline_flag = 1;
   3425       p++;
   3426       break;
   3427     }
   3428     if (c == '\0')
   3429       break;
   3430     p++;
   3431   }
   3432   eptr = p;
   3433   count -= p - ptr;
   3434   return *ptr++;
   3435 }
   3436 
   3437 int string_iterator::peek()
   3438 {
   3439   if (count <= 0)
   3440     return EOF;
   3441   const unsigned char *p = eptr;
   3442   if (p >= bp->s + char_block::SIZE) {
   3443     p = bp->next->s;
   3444   }
   3445   return *p;
   3446 }
   3447 
   3448 int string_iterator::get_location(int allow_macro,
   3449 				  const char **filep, int *linep)
   3450 {
   3451   if (!allow_macro)
   3452     return 0;
   3453   if (mac.filename == 0)
   3454     return 0;
   3455   *filep = mac.filename;
   3456   *linep = mac.lineno + lineno - 1;
   3457   return 1;
   3458 }
   3459 
   3460 void string_iterator::backtrace()
   3461 {
   3462   if (mac.filename) {
   3463     errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
   3464     if (how_invoked) {
   3465       if (!nm.is_null())
   3466 	errprint(": %1 `%2'\n", how_invoked, nm.contents());
   3467       else
   3468 	errprint(": %1\n", how_invoked);
   3469     }
   3470     else
   3471       errprint("\n");
   3472   }
   3473 }
   3474 
   3475 class temp_iterator : public input_iterator {
   3476   unsigned char *base;
   3477   temp_iterator(const char *, int len);
   3478 public:
   3479   ~temp_iterator();
   3480   friend input_iterator *make_temp_iterator(const char *);
   3481 };
   3482 
   3483 #ifdef __GNUG__
   3484 inline
   3485 #endif
   3486 temp_iterator::temp_iterator(const char *s, int len)
   3487 {
   3488   base = new unsigned char[len];
   3489   memcpy(base, s, len);
   3490   ptr = base;
   3491   eptr = base + len;
   3492 }
   3493 
   3494 temp_iterator::~temp_iterator()
   3495 {
   3496   a_delete base;
   3497 }
   3498 
   3499 class small_temp_iterator : public input_iterator {
   3500 private:
   3501   small_temp_iterator(const char *, int);
   3502   ~small_temp_iterator();
   3503   enum { BLOCK = 16 };
   3504   static small_temp_iterator *free_list;
   3505   void *operator new(size_t);
   3506   void operator delete(void *);
   3507   enum { SIZE = 12 };
   3508   unsigned char buf[SIZE];
   3509   friend input_iterator *make_temp_iterator(const char *);
   3510 };
   3511 
   3512 small_temp_iterator *small_temp_iterator::free_list = 0;
   3513 
   3514 void *small_temp_iterator::operator new(size_t n)
   3515 {
   3516   assert(n == sizeof(small_temp_iterator));
   3517   if (!free_list) {
   3518     free_list =
   3519       (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
   3520     for (int i = 0; i < BLOCK - 1; i++)
   3521       free_list[i].next = free_list + i + 1;
   3522     free_list[BLOCK-1].next = 0;
   3523   }
   3524   small_temp_iterator *p = free_list;
   3525   free_list = (small_temp_iterator *)(free_list->next);
   3526   p->next = 0;
   3527   return p;
   3528 }
   3529 
   3530 #ifdef __GNUG__
   3531 inline
   3532 #endif
   3533 void small_temp_iterator::operator delete(void *p)
   3534 {
   3535   if (p) {
   3536     ((small_temp_iterator *)p)->next = free_list;
   3537     free_list = (small_temp_iterator *)p;
   3538   }
   3539 }
   3540 
   3541 small_temp_iterator::~small_temp_iterator()
   3542 {
   3543 }
   3544 
   3545 #ifdef __GNUG__
   3546 inline
   3547 #endif
   3548 small_temp_iterator::small_temp_iterator(const char *s, int len)
   3549 {
   3550   for (int i = 0; i < len; i++)
   3551     buf[i] = s[i];
   3552   ptr = buf;
   3553   eptr = buf + len;
   3554 }
   3555 
   3556 input_iterator *make_temp_iterator(const char *s)
   3557 {
   3558   if (s == 0)
   3559     return new small_temp_iterator(s, 0);
   3560   else {
   3561     int n = strlen(s);
   3562     if (n <= small_temp_iterator::SIZE)
   3563       return new small_temp_iterator(s, n);
   3564     else
   3565       return new temp_iterator(s, n);
   3566   }
   3567 }
   3568 
   3569 // this is used when macros with arguments are interpolated
   3570 
   3571 struct arg_list {
   3572   macro mac;
   3573   arg_list *next;
   3574   arg_list(const macro &);
   3575   ~arg_list();
   3576 };
   3577 
   3578 arg_list::arg_list(const macro &m) : mac(m), next(0)
   3579 {
   3580 }
   3581 
   3582 arg_list::~arg_list()
   3583 {
   3584 }
   3585 
   3586 class macro_iterator : public string_iterator {
   3587   arg_list *args;
   3588   int argc;
   3589 public:
   3590   macro_iterator(symbol, macro &, const char *how_invoked = "macro");
   3591   macro_iterator();
   3592   ~macro_iterator();
   3593   int has_args() { return 1; }
   3594   input_iterator *get_arg(int i);
   3595   int nargs() { return argc; }
   3596   void add_arg(const macro &m);
   3597   void shift(int n);
   3598   int is_macro() { return 1; }
   3599   int is_diversion();
   3600 };
   3601 
   3602 input_iterator *macro_iterator::get_arg(int i)
   3603 {
   3604   if (i == 0)
   3605     return make_temp_iterator(nm.contents());
   3606   if (i > 0 && i <= argc) {
   3607     arg_list *p = args;
   3608     for (int j = 1; j < i; j++) {
   3609       assert(p != 0);
   3610       p = p->next;
   3611     }
   3612     return new string_iterator(p->mac);
   3613   }
   3614   else
   3615     return 0;
   3616 }
   3617 
   3618 void macro_iterator::add_arg(const macro &m)
   3619 {
   3620   arg_list **p;
   3621   for (p = &args; *p; p = &((*p)->next))
   3622     ;
   3623   *p = new arg_list(m);
   3624   ++argc;
   3625 }
   3626 
   3627 void macro_iterator::shift(int n)
   3628 {
   3629   while (n > 0 && argc > 0) {
   3630     arg_list *tem = args;
   3631     args = args->next;
   3632     delete tem;
   3633     --argc;
   3634     --n;
   3635   }
   3636 }
   3637 
   3638 // This gets used by eg .if '\?xxx\?''.
   3639 
   3640 int operator==(const macro &m1, const macro &m2)
   3641 {
   3642   if (m1.len != m2.len)
   3643     return 0;
   3644   string_iterator iter1(m1);
   3645   string_iterator iter2(m2);
   3646   int n = m1.len;
   3647   while (--n >= 0) {
   3648     node *nd1 = 0;
   3649     int c1 = iter1.get(&nd1);
   3650     assert(c1 != EOF);
   3651     node *nd2 = 0;
   3652     int c2 = iter2.get(&nd2);
   3653     assert(c2 != EOF);
   3654     if (c1 != c2) {
   3655       if (c1 == 0)
   3656 	delete nd1;
   3657       else if (c2 == 0)
   3658 	delete nd2;
   3659       return 0;
   3660     }
   3661     if (c1 == 0) {
   3662       assert(nd1 != 0);
   3663       assert(nd2 != 0);
   3664       int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
   3665       delete nd1;
   3666       delete nd2;
   3667       if (!are_same)
   3668 	return 0;
   3669     }
   3670   }
   3671   return 1;
   3672 }
   3673 
   3674 static void interpolate_macro(symbol nm)
   3675 {
   3676   request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
   3677   if (p == 0) {
   3678     int warned = 0;
   3679     const char *s = nm.contents();
   3680     if (strlen(s) > 2) {
   3681       request_or_macro *r;
   3682       char buf[3];
   3683       buf[0] = s[0];
   3684       buf[1] = s[1];
   3685       buf[2] = '\0';
   3686       r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
   3687       if (r) {
   3688 	macro *m = r->to_macro();
   3689 	if (!m || !m->empty())
   3690 	  warned = warning(WARN_SPACE,
   3691 			   "macro `%1' not defined "
   3692 			   "(probably missing space after `%2')",
   3693 			   nm.contents(), buf);
   3694       }
   3695     }
   3696     if (!warned) {
   3697       warning(WARN_MAC, "macro `%1' not defined", nm.contents());
   3698       p = new macro;
   3699       request_dictionary.define(nm, p);
   3700     }
   3701   }
   3702   if (p)
   3703     p->invoke(nm);
   3704   else {
   3705     skip_line();
   3706     return;
   3707   }
   3708 }
   3709 
   3710 static void decode_args(macro_iterator *mi)
   3711 {
   3712   if (!tok.newline() && !tok.eof()) {
   3713     node *n;
   3714     int c = get_copy(&n);
   3715     for (;;) {
   3716       while (c == ' ')
   3717 	c = get_copy(&n);
   3718       if (c == '\n' || c == EOF)
   3719 	break;
   3720       macro arg;
   3721       int quote_input_level = 0;
   3722       int done_tab_warning = 0;
   3723       if (c == '"') {
   3724 	quote_input_level = input_stack::get_level();
   3725 	c = get_copy(&n);
   3726       }
   3727       arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
   3728       while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
   3729 	if (quote_input_level > 0 && c == '"'
   3730 	    && (compatible_flag
   3731 		|| input_stack::get_level() == quote_input_level)) {
   3732 	  c = get_copy(&n);
   3733 	  if (c == '"') {
   3734 	    arg.append(c);
   3735 	    c = get_copy(&n);
   3736 	  }
   3737 	  else
   3738 	    break;
   3739 	}
   3740 	else {
   3741 	  if (c == 0)
   3742 	    arg.append(n);
   3743 	  else {
   3744 	    if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
   3745 	      warning(WARN_TAB, "tab character in unquoted macro argument");
   3746 	      done_tab_warning = 1;
   3747 	    }
   3748 	    arg.append(c);
   3749 	  }
   3750 	  c = get_copy(&n);
   3751 	}
   3752       }
   3753       arg.append(POP_GROFFCOMP_MODE);
   3754       mi->add_arg(arg);
   3755     }
   3756   }
   3757 }
   3758 
   3759 static void decode_string_args(macro_iterator *mi)
   3760 {
   3761   node *n;
   3762   int c = get_copy(&n);
   3763   for (;;) {
   3764     while (c == ' ')
   3765       c = get_copy(&n);
   3766     if (c == '\n' || c == EOF) {
   3767       error("missing `]'");
   3768       break;
   3769     }
   3770     if (c == ']')
   3771       break;
   3772     macro arg;
   3773     int quote_input_level = 0;
   3774     int done_tab_warning = 0;
   3775     if (c == '"') {
   3776       quote_input_level = input_stack::get_level();
   3777       c = get_copy(&n);
   3778     }
   3779     while (c != EOF && c != '\n'
   3780 	   && !(c == ']' && quote_input_level == 0)
   3781 	   && !(c == ' ' && quote_input_level == 0)) {
   3782       if (quote_input_level > 0 && c == '"'
   3783 	  && input_stack::get_level() == quote_input_level) {
   3784 	c = get_copy(&n);
   3785 	if (c == '"') {
   3786 	  arg.append(c);
   3787 	  c = get_copy(&n);
   3788 	}
   3789 	else
   3790 	  break;
   3791       }
   3792       else {
   3793 	if (c == 0)
   3794 	  arg.append(n);
   3795 	else {
   3796 	  if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
   3797 	    warning(WARN_TAB, "tab character in unquoted string argument");
   3798 	    done_tab_warning = 1;
   3799 	  }
   3800 	  arg.append(c);
   3801 	}
   3802 	c = get_copy(&n);
   3803       }
   3804     }
   3805     mi->add_arg(arg);
   3806   }
   3807 }
   3808 
   3809 void macro::invoke(symbol nm)
   3810 {
   3811   macro_iterator *mi = new macro_iterator(nm, *this);
   3812   decode_args(mi);
   3813   input_stack::push(mi);
   3814   tok.next();
   3815 }
   3816 
   3817 macro *macro::to_macro()
   3818 {
   3819   return this;
   3820 }
   3821 
   3822 int macro::empty()
   3823 {
   3824   return empty_macro == 1;
   3825 }
   3826 
   3827 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called)
   3828 : string_iterator(m, how_called, s), args(0), argc(0)
   3829 {
   3830 }
   3831 
   3832 macro_iterator::macro_iterator() : args(0), argc(0)
   3833 {
   3834 }
   3835 
   3836 macro_iterator::~macro_iterator()
   3837 {
   3838   while (args != 0) {
   3839     arg_list *tem = args;
   3840     args = args->next;
   3841     delete tem;
   3842   }
   3843 }
   3844 
   3845 dictionary composite_dictionary(17);
   3846 
   3847 void composite_request()
   3848 {
   3849   symbol from = get_name(1);
   3850   if (!from.is_null()) {
   3851     const char *from_gn = glyph_name_to_unicode(from.contents());
   3852     if (!from_gn) {
   3853       from_gn = check_unicode_name(from.contents());
   3854       if (!from_gn) {
   3855 	error("invalid composite glyph name `%1'", from.contents());
   3856 	skip_line();
   3857 	return;
   3858       }
   3859     }
   3860     const char *from_decomposed = decompose_unicode(from_gn);
   3861     if (from_decomposed)
   3862       from_gn = &from_decomposed[1];
   3863     symbol to = get_name(1);
   3864     if (to.is_null())
   3865       composite_dictionary.remove(symbol(from_gn));
   3866     else {
   3867       const char *to_gn = glyph_name_to_unicode(to.contents());
   3868       if (!to_gn) {
   3869 	to_gn = check_unicode_name(to.contents());
   3870 	if (!to_gn) {
   3871 	  error("invalid composite glyph name `%1'", to.contents());
   3872 	  skip_line();
   3873 	  return;
   3874 	}
   3875       }
   3876       const char *to_decomposed = decompose_unicode(to_gn);
   3877       if (to_decomposed)
   3878 	to_gn = &to_decomposed[1];
   3879       if (strcmp(from_gn, to_gn) == 0)
   3880 	composite_dictionary.remove(symbol(from_gn));
   3881       else
   3882 	(void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
   3883     }
   3884   }
   3885   skip_line();
   3886 }
   3887 
   3888 static symbol composite_glyph_name(symbol nm)
   3889 {
   3890   macro_iterator *mi = new macro_iterator();
   3891   decode_string_args(mi);
   3892   input_stack::push(mi);
   3893   const char *gn = glyph_name_to_unicode(nm.contents());
   3894   if (!gn) {
   3895     gn = check_unicode_name(nm.contents());
   3896     if (!gn) {
   3897       error("invalid base glyph `%1' in composite glyph name", nm.contents());
   3898       return EMPTY_SYMBOL;
   3899     }
   3900   }
   3901   const char *gn_decomposed = decompose_unicode(gn);
   3902   string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
   3903   string gl;
   3904   int n = input_stack::nargs();
   3905   for (int i = 1; i <= n; i++) {
   3906     glyph_name += '_';
   3907     input_iterator *p = input_stack::get_arg(i);
   3908     gl.clear();
   3909     int c;
   3910     while ((c = p->get(0)) != EOF)
   3911       gl += c;
   3912     gl += '\0';
   3913     const char *u = glyph_name_to_unicode(gl.contents());
   3914     if (!u) {
   3915       u = check_unicode_name(gl.contents());
   3916       if (!u) {
   3917 	error("invalid component `%1' in composite glyph name",
   3918 	      gl.contents());
   3919 	return EMPTY_SYMBOL;
   3920       }
   3921     }
   3922     const char *decomposed = decompose_unicode(u);
   3923     if (decomposed)
   3924       u = &decomposed[1];
   3925     void *mapped_composite = composite_dictionary.lookup(symbol(u));
   3926     if (mapped_composite)
   3927       u = (const char *)mapped_composite;
   3928     glyph_name += u;
   3929   }
   3930   glyph_name += '\0';
   3931   const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
   3932   if (groff_gn)
   3933     return symbol(groff_gn);
   3934   gl.clear();
   3935   gl += 'u';
   3936   gl += glyph_name;
   3937   return symbol(gl.contents());
   3938 }
   3939 
   3940 int trap_sprung_flag = 0;
   3941 int postpone_traps_flag = 0;
   3942 symbol postponed_trap;
   3943 
   3944 void spring_trap(symbol nm)
   3945 {
   3946   assert(!nm.is_null());
   3947   trap_sprung_flag = 1;
   3948   if (postpone_traps_flag) {
   3949     postponed_trap = nm;
   3950     return;
   3951   }
   3952   static char buf[2] = { BEGIN_TRAP, 0 };
   3953   static char buf2[2] = { END_TRAP, '\0' };
   3954   input_stack::push(make_temp_iterator(buf2));
   3955   request_or_macro *p = lookup_request(nm);
   3956   macro *m = p->to_macro();
   3957   if (m)
   3958     input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
   3959   else
   3960     error("you can't invoke a request with a trap");
   3961   input_stack::push(make_temp_iterator(buf));
   3962 }
   3963 
   3964 void postpone_traps()
   3965 {
   3966   postpone_traps_flag = 1;
   3967 }
   3968 
   3969 int unpostpone_traps()
   3970 {
   3971   postpone_traps_flag = 0;
   3972   if (!postponed_trap.is_null()) {
   3973     spring_trap(postponed_trap);
   3974     postponed_trap = NULL_SYMBOL;
   3975     return 1;
   3976   }
   3977   else
   3978     return 0;
   3979 }
   3980 
   3981 void read_request()
   3982 {
   3983   macro_iterator *mi = new macro_iterator;
   3984   int reading_from_terminal = isatty(fileno(stdin));
   3985   int had_prompt = 0;
   3986   if (!tok.newline() && !tok.eof()) {
   3987     int c = get_copy(0);
   3988     while (c == ' ')
   3989       c = get_copy(0);
   3990     while (c != EOF && c != '\n' && c != ' ') {
   3991       if (!invalid_input_char(c)) {
   3992 	if (reading_from_terminal)
   3993 	  fputc(c, stderr);
   3994 	had_prompt = 1;
   3995       }
   3996       c = get_copy(0);
   3997     }
   3998     if (c == ' ') {
   3999       tok.make_space();
   4000       decode_args(mi);
   4001     }
   4002   }
   4003   if (reading_from_terminal) {
   4004     fputc(had_prompt ? ':' : '\a', stderr);
   4005     fflush(stderr);
   4006   }
   4007   input_stack::push(mi);
   4008   macro mac;
   4009   int nl = 0;
   4010   int c;
   4011   while ((c = getchar()) != EOF) {
   4012     if (invalid_input_char(c))
   4013       warning(WARN_INPUT, "invalid input character code %1", int(c));
   4014     else {
   4015       if (c == '\n') {
   4016 	if (nl)
   4017 	  break;
   4018 	else
   4019 	  nl = 1;
   4020       }
   4021       else
   4022 	nl = 0;
   4023       mac.append(c);
   4024     }
   4025   }
   4026   if (reading_from_terminal)
   4027     clearerr(stdin);
   4028   input_stack::push(new string_iterator(mac));
   4029   tok.next();
   4030 }
   4031 
   4032 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
   4033 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
   4034 enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
   4035 
   4036 void do_define_string(define_mode mode, comp_mode comp)
   4037 {
   4038   symbol nm;
   4039   node *n = 0;		// pacify compiler
   4040   int c;
   4041   nm = get_name(1);
   4042   if (nm.is_null()) {
   4043     skip_line();
   4044     return;
   4045   }
   4046   if (tok.newline())
   4047     c = '\n';
   4048   else if (tok.tab())
   4049     c = '\t';
   4050   else if (!tok.space()) {
   4051     error("bad string definition");
   4052     skip_line();
   4053     return;
   4054   }
   4055   else
   4056     c = get_copy(&n);
   4057   while (c == ' ')
   4058     c = get_copy(&n);
   4059   if (c == '"')
   4060     c = get_copy(&n);
   4061   macro mac;
   4062   request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
   4063   macro *mm = rm ? rm->to_macro() : 0;
   4064   if (mode == DEFINE_APPEND && mm)
   4065     mac = *mm;
   4066   if (comp == COMP_DISABLE)
   4067     mac.append(PUSH_GROFF_MODE);
   4068   else if (comp == COMP_ENABLE)
   4069     mac.append(PUSH_COMP_MODE);
   4070   while (c != '\n' && c != EOF) {
   4071     if (c == 0)
   4072       mac.append(n);
   4073     else
   4074       mac.append((unsigned char)c);
   4075     c = get_copy(&n);
   4076   }
   4077   if (!mm) {
   4078     mm = new macro;
   4079     request_dictionary.define(nm, mm);
   4080   }
   4081   if (comp == COMP_DISABLE || comp == COMP_ENABLE)
   4082     mac.append(POP_GROFFCOMP_MODE);
   4083   *mm = mac;
   4084   tok.next();
   4085 }
   4086 
   4087 void define_string()
   4088 {
   4089   do_define_string(DEFINE_NORMAL,
   4090 		   compatible_flag ? COMP_ENABLE: COMP_IGNORE);
   4091 }
   4092 
   4093 void define_nocomp_string()
   4094 {
   4095   do_define_string(DEFINE_NORMAL, COMP_DISABLE);
   4096 }
   4097 
   4098 void append_string()
   4099 {
   4100   do_define_string(DEFINE_APPEND,
   4101 		   compatible_flag ? COMP_ENABLE : COMP_IGNORE);
   4102 }
   4103 
   4104 void append_nocomp_string()
   4105 {
   4106   do_define_string(DEFINE_APPEND, COMP_DISABLE);
   4107 }
   4108 
   4109 void do_define_character(char_mode mode, const char *font_name)
   4110 {
   4111   node *n = 0;		// pacify compiler
   4112   int c;
   4113   tok.skip();
   4114   charinfo *ci = tok.get_char(1);
   4115   if (ci == 0) {
   4116     skip_line();
   4117     return;
   4118   }
   4119   if (font_name) {
   4120     string s(font_name);
   4121     s += ' ';
   4122     s += ci->nm.contents();
   4123     s += '\0';
   4124     ci = get_charinfo(symbol(s.contents()));
   4125   }
   4126   tok.next();
   4127   if (tok.newline())
   4128     c = '\n';
   4129   else if (tok.tab())
   4130     c = '\t';
   4131   else if (!tok.space()) {
   4132     error("bad character definition");
   4133     skip_line();
   4134     return;
   4135   }
   4136   else
   4137     c = get_copy(&n);
   4138   while (c == ' ' || c == '\t')
   4139     c = get_copy(&n);
   4140   if (c == '"')
   4141     c = get_copy(&n);
   4142   macro *m = new macro;
   4143   while (c != '\n' && c != EOF) {
   4144     if (c == 0)
   4145       m->append(n);
   4146     else
   4147       m->append((unsigned char)c);
   4148     c = get_copy(&n);
   4149   }
   4150   m = ci->setx_macro(m, mode);
   4151   if (m)
   4152     delete m;
   4153   tok.next();
   4154 }
   4155 
   4156 void define_character()
   4157 {
   4158   do_define_character(CHAR_NORMAL);
   4159 }
   4160 
   4161 void define_fallback_character()
   4162 {
   4163   do_define_character(CHAR_FALLBACK);
   4164 }
   4165 
   4166 void define_special_character()
   4167 {
   4168   do_define_character(CHAR_SPECIAL);
   4169 }
   4170 
   4171 static void remove_character()
   4172 {
   4173   tok.skip();
   4174   while (!tok.newline() && !tok.eof()) {
   4175     if (!tok.space() && !tok.tab()) {
   4176       charinfo *ci = tok.get_char(1);
   4177       if (!ci)
   4178 	break;
   4179       macro *m = ci->set_macro(0);
   4180       if (m)
   4181 	delete m;
   4182     }
   4183     tok.next();
   4184   }
   4185   skip_line();
   4186 }
   4187 
   4188 static void interpolate_string(symbol nm)
   4189 {
   4190   request_or_macro *p = lookup_request(nm);
   4191   macro *m = p->to_macro();
   4192   if (!m)
   4193     error("you can only invoke a string or macro using \\*");
   4194   else {
   4195     string_iterator *si = new string_iterator(*m, "string", nm);
   4196     input_stack::push(si);
   4197   }
   4198 }
   4199 
   4200 static void interpolate_string_with_args(symbol s)
   4201 {
   4202   request_or_macro *p = lookup_request(s);
   4203   macro *m = p->to_macro();
   4204   if (!m)
   4205     error("you can only invoke a string or macro using \\*");
   4206   else {
   4207     macro_iterator *mi = new macro_iterator(s, *m);
   4208     decode_string_args(mi);
   4209     input_stack::push(mi);
   4210   }
   4211 }
   4212 
   4213 static void interpolate_arg(symbol nm)
   4214 {
   4215   const char *s = nm.contents();
   4216   if (!s || *s == '\0')
   4217     copy_mode_error("missing argument name");
   4218   else if (s[1] == 0 && csdigit(s[0]))
   4219     input_stack::push(input_stack::get_arg(s[0] - '0'));
   4220   else if (s[0] == '*' && s[1] == '\0') {
   4221     int limit = input_stack::nargs();
   4222     string args;
   4223     for (int i = 1; i <= limit; i++) {
   4224       input_iterator *p = input_stack::get_arg(i);
   4225       int c;
   4226       while ((c = p->get(0)) != EOF)
   4227 	args += c;
   4228       if (i != limit)
   4229 	args += ' ';
   4230     }
   4231     if (limit > 0) {
   4232       args += '\0';
   4233       input_stack::push(make_temp_iterator(args.contents()));
   4234     }
   4235   }
   4236   else if (s[0] == '@' && s[1] == '\0') {
   4237     int limit = input_stack::nargs();
   4238     string args;
   4239     for (int i = 1; i <= limit; i++) {
   4240       args += '"';
   4241       args += (char)BEGIN_QUOTE;
   4242       input_iterator *p = input_stack::get_arg(i);
   4243       int c;
   4244       while ((c = p->get(0)) != EOF)
   4245 	args += c;
   4246       args += (char)END_QUOTE;
   4247       args += '"';
   4248       if (i != limit)
   4249 	args += ' ';
   4250     }
   4251     if (limit > 0) {
   4252       args += '\0';
   4253       input_stack::push(make_temp_iterator(args.contents()));
   4254     }
   4255   }
   4256   else {
   4257     const char *p;
   4258     for (p = s; *p && csdigit(*p); p++)
   4259       ;
   4260     if (*p)
   4261       copy_mode_error("bad argument name `%1'", s);
   4262     else
   4263       input_stack::push(input_stack::get_arg(atoi(s)));
   4264   }
   4265 }
   4266 
   4267 void handle_first_page_transition()
   4268 {
   4269   push_token(tok);
   4270   topdiv->begin_page();
   4271 }
   4272 
   4273 // We push back a token by wrapping it up in a token_node, and
   4274 // wrapping that up in a string_iterator.
   4275 
   4276 static void push_token(const token &t)
   4277 {
   4278   macro m;
   4279   m.append(new token_node(t));
   4280   input_stack::push(new string_iterator(m));
   4281 }
   4282 
   4283 void push_page_ejector()
   4284 {
   4285   static char buf[2] = { PAGE_EJECTOR, '\0' };
   4286   input_stack::push(make_temp_iterator(buf));
   4287 }
   4288 
   4289 void handle_initial_request(unsigned char code)
   4290 {
   4291   char buf[2];
   4292   buf[0] = code;
   4293   buf[1] = '\0';
   4294   macro mac;
   4295   mac.append(new token_node(tok));
   4296   input_stack::push(new string_iterator(mac));
   4297   input_stack::push(make_temp_iterator(buf));
   4298   topdiv->begin_page();
   4299   tok.next();
   4300 }
   4301 
   4302 void handle_initial_title()
   4303 {
   4304   handle_initial_request(TITLE_REQUEST);
   4305 }
   4306 
   4307 // this should be local to define_macro, but cfront 1.2 doesn't support that
   4308 static symbol dot_symbol(".");
   4309 
   4310 void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
   4311 {
   4312   symbol nm, term;
   4313   if (calling == CALLING_INDIRECT) {
   4314     symbol temp1 = get_name(1);
   4315     if (temp1.is_null()) {
   4316       skip_line();
   4317       return;
   4318     }
   4319     symbol temp2 = get_name();
   4320     input_stack::push(make_temp_iterator("\n"));
   4321     if (!temp2.is_null()) {
   4322       interpolate_string(temp2);
   4323       input_stack::push(make_temp_iterator(" "));
   4324     }
   4325     interpolate_string(temp1);
   4326     input_stack::push(make_temp_iterator(" "));
   4327     tok.next();
   4328   }
   4329   if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
   4330     nm = get_name(1);
   4331     if (nm.is_null()) {
   4332       skip_line();
   4333       return;
   4334     }
   4335   }
   4336   term = get_name();	// the request that terminates the definition
   4337   if (term.is_null())
   4338     term = dot_symbol;
   4339   while (!tok.newline() && !tok.eof())
   4340     tok.next();
   4341   const char *start_filename;
   4342   int start_lineno;
   4343   int have_start_location = input_stack::get_location(0, &start_filename,
   4344 						      &start_lineno);
   4345   node *n;
   4346   // doing this here makes the line numbers come out right
   4347   int c = get_copy(&n, 1);
   4348   macro mac;
   4349   macro *mm = 0;
   4350   if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
   4351     request_or_macro *rm =
   4352       (request_or_macro *)request_dictionary.lookup(nm);
   4353     if (rm)
   4354       mm = rm->to_macro();
   4355     if (mm && mode == DEFINE_APPEND)
   4356       mac = *mm;
   4357   }
   4358   int bol = 1;
   4359   if (comp == COMP_DISABLE)
   4360     mac.append(PUSH_GROFF_MODE);
   4361   else if (comp == COMP_ENABLE)
   4362     mac.append(PUSH_COMP_MODE);
   4363   for (;;) {
   4364     while (c == ESCAPE_NEWLINE) {
   4365       if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
   4366 	mac.append(c);
   4367       c = get_copy(&n, 1);
   4368     }
   4369     if (bol && c == '.') {
   4370       const char *s = term.contents();
   4371       int d = 0;
   4372       // see if it matches term
   4373       int i = 0;
   4374       if (s[0] != 0) {
   4375 	while ((d = get_copy(&n)) == ' ' || d == '\t')
   4376 	  ;
   4377 	if ((unsigned char)s[0] == d) {
   4378 	  for (i = 1; s[i] != 0; i++) {
   4379 	    d = get_copy(&n);
   4380 	    if ((unsigned char)s[i] != d)
   4381 	      break;
   4382 	  }
   4383 	}
   4384       }
   4385       if (s[i] == 0
   4386 	  && ((i == 2 && compatible_flag)
   4387 	      || (d = get_copy(&n)) == ' '
   4388 	      || d == '\n')) {	// we found it
   4389 	if (d == '\n')
   4390 	  tok.make_newline();
   4391 	else
   4392 	  tok.make_space();
   4393 	if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
   4394 	  if (!mm) {
   4395 	    mm = new macro;
   4396 	    request_dictionary.define(nm, mm);
   4397 	  }
   4398 	  if (comp == COMP_DISABLE || comp == COMP_ENABLE)
   4399 	    mac.append(POP_GROFFCOMP_MODE);
   4400 	  *mm = mac;
   4401 	}
   4402 	if (term != dot_symbol) {
   4403 	  ignoring = 0;
   4404 	  interpolate_macro(term);
   4405 	}
   4406 	else
   4407 	  skip_line();
   4408 	return;
   4409       }
   4410       if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
   4411 	mac.append(c);
   4412 	for (int j = 0; j < i; j++)
   4413 	  mac.append(s[j]);
   4414       }
   4415       c = d;
   4416     }
   4417     if (c == EOF) {
   4418       if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
   4419 	if (have_start_location)
   4420 	  error_with_file_and_line(start_filename, start_lineno,
   4421 				   "end of file while defining macro `%1'",
   4422 				   nm.contents());
   4423 	else
   4424 	  error("end of file while defining macro `%1'", nm.contents());
   4425       }
   4426       else {
   4427 	if (have_start_location)
   4428 	  error_with_file_and_line(start_filename, start_lineno,
   4429 				   "end of file while ignoring input lines");
   4430 	else
   4431 	  error("end of file while ignoring input lines");
   4432       }
   4433       tok.next();
   4434       return;
   4435     }
   4436     if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
   4437       if (c == 0)
   4438 	mac.append(n);
   4439       else
   4440 	mac.append(c);
   4441     }
   4442     bol = (c == '\n');
   4443     c = get_copy(&n, 1);
   4444   }
   4445 }
   4446 
   4447 void define_macro()
   4448 {
   4449   do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
   4450 		  compatible_flag ? COMP_ENABLE : COMP_IGNORE);
   4451 }
   4452 
   4453 void define_nocomp_macro()
   4454 {
   4455   do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
   4456 }
   4457 
   4458 void define_indirect_macro()
   4459 {
   4460   do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
   4461 		  compatible_flag ? COMP_ENABLE : COMP_IGNORE);
   4462 }
   4463 
   4464 void define_indirect_nocomp_macro()
   4465 {
   4466   do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
   4467 }
   4468 
   4469 void append_macro()
   4470 {
   4471   do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
   4472 		  compatible_flag ? COMP_ENABLE : COMP_IGNORE);
   4473 }
   4474 
   4475 void append_nocomp_macro()
   4476 {
   4477   do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
   4478 }
   4479 
   4480 void append_indirect_macro()
   4481 {
   4482   do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
   4483 		  compatible_flag ? COMP_ENABLE : COMP_IGNORE);
   4484 }
   4485 
   4486 void append_indirect_nocomp_macro()
   4487 {
   4488   do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
   4489 }
   4490 
   4491 void ignore()
   4492 {
   4493   ignoring = 1;
   4494   do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
   4495   ignoring = 0;
   4496 }
   4497 
   4498 void remove_macro()
   4499 {
   4500   for (;;) {
   4501     symbol s = get_name();
   4502     if (s.is_null())
   4503       break;
   4504     request_dictionary.remove(s);
   4505   }
   4506   skip_line();
   4507 }
   4508 
   4509 void rename_macro()
   4510 {
   4511   symbol s1 = get_name(1);
   4512   if (!s1.is_null()) {
   4513     symbol s2 = get_name(1);
   4514     if (!s2.is_null())
   4515       request_dictionary.rename(s1, s2);
   4516   }
   4517   skip_line();
   4518 }
   4519 
   4520 void alias_macro()
   4521 {
   4522   symbol s1 = get_name(1);
   4523   if (!s1.is_null()) {
   4524     symbol s2 = get_name(1);
   4525     if (!s2.is_null()) {
   4526       if (!request_dictionary.alias(s1, s2))
   4527 	warning(WARN_MAC, "macro `%1' not defined", s2.contents());
   4528     }
   4529   }
   4530   skip_line();
   4531 }
   4532 
   4533 void chop_macro()
   4534 {
   4535   symbol s = get_name(1);
   4536   if (!s.is_null()) {
   4537     request_or_macro *p = lookup_request(s);
   4538     macro *m = p->to_macro();
   4539     if (!m)
   4540       error("cannot chop request");
   4541     else if (m->empty())
   4542       error("cannot chop empty macro");
   4543     else {
   4544       int have_restore = 0;
   4545       // we have to check for additional save/restore pairs which could be
   4546       // there due to empty am1 requests.
   4547       for (;;) {
   4548 	if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
   4549           break;
   4550 	have_restore = 1;
   4551 	m->len -= 1;
   4552 	if (m->get(m->len - 1) != PUSH_GROFF_MODE
   4553 	    && m->get(m->len - 1) != PUSH_COMP_MODE)
   4554           break;
   4555 	have_restore = 0;
   4556 	m->len -= 1;
   4557 	if (m->len == 0)
   4558 	  break;
   4559       }
   4560       if (m->len == 0)
   4561 	error("cannot chop empty macro");
   4562       else {
   4563 	if (have_restore)
   4564 	  m->set(POP_GROFFCOMP_MODE, m->len - 1);
   4565 	else
   4566 	  m->len -= 1;
   4567       }
   4568     }
   4569   }
   4570   skip_line();
   4571 }
   4572 
   4573 void substring_request()
   4574 {
   4575   int start;				// 0, 1, ..., n-1  or  -1, -2, ...
   4576   symbol s = get_name(1);
   4577   if (!s.is_null() && get_integer(&start)) {
   4578     request_or_macro *p = lookup_request(s);
   4579     macro *m = p->to_macro();
   4580     if (!m)
   4581       error("cannot apply `substring' on a request");
   4582     else {
   4583       int end = -1;
   4584       if (!has_arg() || get_integer(&end)) {
   4585 	int real_length = 0;			// 1, 2, ..., n
   4586 	string_iterator iter1(*m);
   4587 	for (int l = 0; l < m->len; l++) {
   4588 	  int c = iter1.get(0);
   4589 	  if (c == PUSH_GROFF_MODE
   4590 	      || c == PUSH_COMP_MODE
   4591 	      || c == POP_GROFFCOMP_MODE)
   4592 	    continue;
   4593 	  if (c == EOF)
   4594 	    break;
   4595 	  real_length++;
   4596 	}
   4597 	if (start < 0)
   4598 	  start += real_length;
   4599 	if (end < 0)
   4600 	  end += real_length;
   4601 	if (start > end) {
   4602 	  int tem = start;
   4603 	  start = end;
   4604 	  end = tem;
   4605 	}
   4606 	if (start >= real_length || end < 0) {
   4607 	  warning(WARN_RANGE,
   4608 		  "start and end index of substring out of range");
   4609 	  m->len = 0;
   4610 	  if (m->p) {
   4611 	    if (--(m->p->count) <= 0)
   4612 	      delete m->p;
   4613 	    m->p = 0;
   4614 	  }
   4615 	  skip_line();
   4616 	  return;
   4617 	}
   4618 	if (start < 0) {
   4619 	  warning(WARN_RANGE,
   4620 		  "start index of substring out of range, set to 0");
   4621 	  start = 0;
   4622 	}
   4623 	if (end >= real_length) {
   4624 	  warning(WARN_RANGE,
   4625 		  "end index of substring out of range, set to string length");
   4626 	  end = real_length - 1;
   4627 	}
   4628 	// now extract the substring
   4629 	string_iterator iter(*m);
   4630 	int i;
   4631 	for (i = 0; i < start; i++) {
   4632 	  int c = iter.get(0);
   4633 	  while (c == PUSH_GROFF_MODE
   4634 		 || c == PUSH_COMP_MODE
   4635 		 || c == POP_GROFFCOMP_MODE)
   4636 	    c = iter.get(0);
   4637 	  if (c == EOF)
   4638 	    break;
   4639 	}
   4640 	macro mac;
   4641 	for (; i <= end; i++) {
   4642 	  node *nd = 0;		// pacify compiler
   4643 	  int c = iter.get(&nd);
   4644 	  while (c == PUSH_GROFF_MODE
   4645 		 || c == PUSH_COMP_MODE
   4646 		 || c == POP_GROFFCOMP_MODE)
   4647 	    c = iter.get(0);
   4648 	  if (c == EOF)
   4649 	    break;
   4650 	  if (c == 0)
   4651 	    mac.append(nd);
   4652 	  else
   4653 	    mac.append((unsigned char)c);
   4654 	}
   4655 	*m = mac;
   4656       }
   4657     }
   4658   }
   4659   skip_line();
   4660 }
   4661 
   4662 void length_request()
   4663 {
   4664   symbol ret;
   4665   ret = get_name(1);
   4666   if (ret.is_null()) {
   4667     skip_line();
   4668     return;
   4669   }
   4670   int c;
   4671   node *n;
   4672   if (tok.newline())
   4673     c = '\n';
   4674   else if (tok.tab())
   4675     c = '\t';
   4676   else if (!tok.space()) {
   4677     error("bad string definition");
   4678     skip_line();
   4679     return;
   4680   }
   4681   else
   4682     c = get_copy(&n);
   4683   while (c == ' ')
   4684     c = get_copy(&n);
   4685   if (c == '"')
   4686     c = get_copy(&n);
   4687   int len = 0;
   4688   while (c != '\n' && c != EOF) {
   4689     ++len;
   4690     c = get_copy(&n);
   4691   }
   4692   reg *r = (reg*)number_reg_dictionary.lookup(ret);
   4693   if (r)
   4694     r->set_value(len);
   4695   else
   4696     set_number_reg(ret, len);
   4697   tok.next();
   4698 }
   4699 
   4700 void asciify_macro()
   4701 {
   4702   symbol s = get_name(1);
   4703   if (!s.is_null()) {
   4704     request_or_macro *p = lookup_request(s);
   4705     macro *m = p->to_macro();
   4706     if (!m)
   4707       error("cannot asciify request");
   4708     else {
   4709       macro am;
   4710       string_iterator iter(*m);
   4711       for (;;) {
   4712 	node *nd = 0;		// pacify compiler
   4713 	int c = iter.get(&nd);
   4714 	if (c == EOF)
   4715 	  break;
   4716 	if (c != 0)
   4717 	  am.append(c);
   4718 	else
   4719 	  nd->asciify(&am);
   4720       }
   4721       *m = am;
   4722     }
   4723   }
   4724   skip_line();
   4725 }
   4726 
   4727 void unformat_macro()
   4728 {
   4729   symbol s = get_name(1);
   4730   if (!s.is_null()) {
   4731     request_or_macro *p = lookup_request(s);
   4732     macro *m = p->to_macro();
   4733     if (!m)
   4734       error("cannot unformat request");
   4735     else {
   4736       macro am;
   4737       string_iterator iter(*m);
   4738       for (;;) {
   4739 	node *nd = 0;		// pacify compiler
   4740 	int c = iter.get(&nd);
   4741 	if (c == EOF)
   4742 	  break;
   4743 	if (c != 0)
   4744 	  am.append(c);
   4745 	else {
   4746 	  if (nd->set_unformat_flag())
   4747 	    am.append(nd);
   4748 	}
   4749       }
   4750       *m = am;
   4751     }
   4752   }
   4753   skip_line();
   4754 }
   4755 
   4756 static void interpolate_environment_variable(symbol nm)
   4757 {
   4758   const char *s = getenv(nm.contents());
   4759   if (s && *s)
   4760     input_stack::push(make_temp_iterator(s));
   4761 }
   4762 
   4763 void interpolate_number_reg(symbol nm, int inc)
   4764 {
   4765   reg *r = lookup_number_reg(nm);
   4766   if (inc < 0)
   4767     r->decrement();
   4768   else if (inc > 0)
   4769     r->increment();
   4770   input_stack::push(make_temp_iterator(r->get_string()));
   4771 }
   4772 
   4773 static void interpolate_number_format(symbol nm)
   4774 {
   4775   reg *r = (reg *)number_reg_dictionary.lookup(nm);
   4776   if (r)
   4777     input_stack::push(make_temp_iterator(r->get_format()));
   4778 }
   4779 
   4780 static int get_delim_number(units *n, unsigned char si, int prev_value)
   4781 {
   4782   token start;
   4783   start.next();
   4784   if (start.delimiter(1)) {
   4785     tok.next();
   4786     if (get_number(n, si, prev_value)) {
   4787       if (start != tok)
   4788 	warning(WARN_DELIM, "closing delimiter does not match");
   4789       return 1;
   4790     }
   4791   }
   4792   return 0;
   4793 }
   4794 
   4795 static int get_delim_number(units *n, unsigned char si)
   4796 {
   4797   token start;
   4798   start.next();
   4799   if (start.delimiter(1)) {
   4800     tok.next();
   4801     if (get_number(n, si)) {
   4802       if (start != tok)
   4803 	warning(WARN_DELIM, "closing delimiter does not match");
   4804       return 1;
   4805     }
   4806   }
   4807   return 0;
   4808 }
   4809 
   4810 static int get_line_arg(units *n, unsigned char si, charinfo **cp)
   4811 {
   4812   token start;
   4813   start.next();
   4814   int start_level = input_stack::get_level();
   4815   if (!start.delimiter(1))
   4816     return 0;
   4817   tok.next();
   4818   if (get_number(n, si)) {
   4819     if (tok.dummy() || tok.transparent_dummy())
   4820       tok.next();
   4821     if (!(start == tok && input_stack::get_level() == start_level)) {
   4822       *cp = tok.get_char(1);
   4823       tok.next();
   4824     }
   4825     if (!(start == tok && input_stack::get_level() == start_level))
   4826       warning(WARN_DELIM, "closing delimiter does not match");
   4827     return 1;
   4828   }
   4829   return 0;
   4830 }
   4831 
   4832 static int read_size(int *x)
   4833 {
   4834   tok.next();
   4835   int c = tok.ch();
   4836   int inc = 0;
   4837   if (c == '-') {
   4838     inc = -1;
   4839     tok.next();
   4840     c = tok.ch();
   4841   }
   4842   else if (c == '+') {
   4843     inc = 1;
   4844     tok.next();
   4845     c = tok.ch();
   4846   }
   4847   int val = 0;		// pacify compiler
   4848   int bad = 0;
   4849   if (c == '(') {
   4850     tok.next();
   4851     c = tok.ch();
   4852     if (!inc) {
   4853       // allow an increment either before or after the left parenthesis
   4854       if (c == '-') {
   4855 	inc = -1;
   4856 	tok.next();
   4857 	c = tok.ch();
   4858       }
   4859       else if (c == '+') {
   4860 	inc = 1;
   4861 	tok.next();
   4862 	c = tok.ch();
   4863       }
   4864     }
   4865     if (!csdigit(c))
   4866       bad = 1;
   4867     else {
   4868       val = c - '0';
   4869       tok.next();
   4870       c = tok.ch();
   4871       if (!csdigit(c))
   4872 	bad = 1;
   4873       else {
   4874 	val = val*10 + (c - '0');
   4875 	val *= sizescale;
   4876       }
   4877     }
   4878   }
   4879   else if (csdigit(c)) {
   4880     val = c - '0';
   4881     if (!inc && c != '0' && c < '4') {
   4882       tok.next();
   4883       c = tok.ch();
   4884       if (!csdigit(c))
   4885 	bad = 1;
   4886       else
   4887 	val = val*10 + (c - '0');
   4888     }
   4889     val *= sizescale;
   4890   }
   4891   else if (!tok.delimiter(1))
   4892     return 0;
   4893   else {
   4894     token start(tok);
   4895     tok.next();
   4896     if (!(inc
   4897 	  ? get_number(&val, 'z')
   4898 	  : get_number(&val, 'z', curenv->get_requested_point_size())))
   4899       return 0;
   4900     if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
   4901       if (start.ch() == '[')
   4902 	error("missing `]'");
   4903       else
   4904 	error("missing closing delimiter");
   4905       return 0;
   4906     }
   4907   }
   4908   if (!bad) {
   4909     switch (inc) {
   4910     case 0:
   4911       if (val == 0) {
   4912 	// special case -- \s[0] and \s0 means to revert to previous size
   4913 	*x = 0;
   4914 	return 1;
   4915       }
   4916       *x = val;
   4917       break;
   4918     case 1:
   4919       *x = curenv->get_requested_point_size() + val;
   4920       break;
   4921     case -1:
   4922       *x = curenv->get_requested_point_size() - val;
   4923       break;
   4924     default:
   4925       assert(0);
   4926     }
   4927     if (*x <= 0) {
   4928       warning(WARN_RANGE,
   4929 	      "\\s request results in non-positive point size; set to 1");
   4930       *x = 1;
   4931     }
   4932     return 1;
   4933   }
   4934   else {
   4935     error("bad digit in point size");
   4936     return 0;
   4937   }
   4938 }
   4939 
   4940 static symbol get_delim_name()
   4941 {
   4942   token start;
   4943   start.next();
   4944   if (start.eof()) {
   4945     error("end of input at start of delimited name");
   4946     return NULL_SYMBOL;
   4947   }
   4948   if (start.newline()) {
   4949     error("can't delimit name with a newline");
   4950     return NULL_SYMBOL;
   4951   }
   4952   int start_level = input_stack::get_level();
   4953   char abuf[ABUF_SIZE];
   4954   char *buf = abuf;
   4955   int buf_size = ABUF_SIZE;
   4956   int i = 0;
   4957   for (;;) {
   4958     if (i + 1 > buf_size) {
   4959       if (buf == abuf) {
   4960 	buf = new char[ABUF_SIZE*2];
   4961 	memcpy(buf, abuf, buf_size);
   4962 	buf_size = ABUF_SIZE*2;
   4963       }
   4964       else {
   4965 	char *old_buf = buf;
   4966 	buf = new char[buf_size*2];
   4967 	memcpy(buf, old_buf, buf_size);
   4968 	buf_size *= 2;
   4969 	a_delete old_buf;
   4970       }
   4971     }
   4972     tok.next();
   4973     if (tok == start
   4974 	&& (compatible_flag || input_stack::get_level() == start_level))
   4975       break;
   4976     if ((buf[i] = tok.ch()) == 0) {
   4977       error("missing delimiter (got %1)", tok.description());
   4978       if (buf != abuf)
   4979 	a_delete buf;
   4980       return NULL_SYMBOL;
   4981     }
   4982     i++;
   4983   }
   4984   buf[i] = '\0';
   4985   if (buf == abuf) {
   4986     if (i == 0) {
   4987       error("empty delimited name");
   4988       return NULL_SYMBOL;
   4989     }
   4990     else
   4991       return symbol(buf);
   4992   }
   4993   else {
   4994     symbol s(buf);
   4995     a_delete buf;
   4996     return s;
   4997   }
   4998 }
   4999 
   5000 // Implement \R
   5001 
   5002 static void do_register()
   5003 {
   5004   token start;
   5005   start.next();
   5006   if (!start.delimiter(1))
   5007     return;
   5008   tok.next();
   5009   symbol nm = get_long_name(1);
   5010   if (nm.is_null())
   5011     return;
   5012   while (tok.space())
   5013     tok.next();
   5014   reg *r = (reg *)number_reg_dictionary.lookup(nm);
   5015   int prev_value;
   5016   if (!r || !r->get_value(&prev_value))
   5017     prev_value = 0;
   5018   int val;
   5019   if (!get_number(&val, 'u', prev_value))
   5020     return;
   5021   if (start != tok)
   5022     warning(WARN_DELIM, "closing delimiter does not match");
   5023   if (r)
   5024     r->set_value(val);
   5025   else
   5026     set_number_reg(nm, val);
   5027 }
   5028 
   5029 // this implements the \w escape sequence
   5030 
   5031 static void do_width()
   5032 {
   5033   token start;
   5034   start.next();
   5035   int start_level = input_stack::get_level();
   5036   environment env(curenv);
   5037   environment *oldenv = curenv;
   5038   curenv = &env;
   5039   for (;;) {
   5040     tok.next();
   5041     if (tok.eof()) {
   5042       warning(WARN_DELIM, "missing closing delimiter");
   5043       break;
   5044     }
   5045     if (tok.newline()) {
   5046       warning(WARN_DELIM, "missing closing delimiter");
   5047       input_stack::push(make_temp_iterator("\n"));
   5048       break;
   5049     }
   5050     if (tok == start
   5051 	&& (compatible_flag || input_stack::get_level() == start_level))
   5052       break;
   5053     tok.process();
   5054   }
   5055   env.wrap_up_tab();
   5056   units x = env.get_input_line_position().to_units();
   5057   input_stack::push(make_temp_iterator(i_to_a(x)));
   5058   env.width_registers();
   5059   curenv = oldenv;
   5060   have_input = 0;
   5061 }
   5062 
   5063 charinfo *page_character;
   5064 
   5065 void set_page_character()
   5066 {
   5067   page_character = get_optional_char();
   5068   skip_line();
   5069 }
   5070 
   5071 static const symbol percent_symbol("%");
   5072 
   5073 void read_title_parts(node **part, hunits *part_width)
   5074 {
   5075   tok.skip();
   5076   if (tok.newline() || tok.eof())
   5077     return;
   5078   token start(tok);
   5079   int start_level = input_stack::get_level();
   5080   tok.next();
   5081   for (int i = 0; i < 3; i++) {
   5082     while (!tok.newline() && !tok.eof()) {
   5083       if (tok == start
   5084 	  && (compatible_flag || input_stack::get_level() == start_level)) {
   5085 	tok.next();
   5086 	break;
   5087       }
   5088       if (page_character != 0 && tok.get_char() == page_character)
   5089 	interpolate_number_reg(percent_symbol, 0);
   5090       else
   5091 	tok.process();
   5092       tok.next();
   5093     }
   5094     curenv->wrap_up_tab();
   5095     part_width[i] = curenv->get_input_line_position();
   5096     part[i] = curenv->extract_output_line();
   5097   }
   5098   while (!tok.newline() && !tok.eof())
   5099     tok.next();
   5100 }
   5101 
   5102 class non_interpreted_node : public node {
   5103   macro mac;
   5104 public:
   5105   non_interpreted_node(const macro &);
   5106   int interpret(macro *);
   5107   node *copy();
   5108   int ends_sentence();
   5109   int same(node *);
   5110   const char *type();
   5111   int force_tprint();
   5112   int is_tag();
   5113 };
   5114 
   5115 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
   5116 {
   5117 }
   5118 
   5119 int non_interpreted_node::ends_sentence()
   5120 {
   5121   return 2;
   5122 }
   5123 
   5124 int non_interpreted_node::same(node *nd)
   5125 {
   5126   return mac == ((non_interpreted_node *)nd)->mac;
   5127 }
   5128 
   5129 const char *non_interpreted_node::type()
   5130 {
   5131   return "non_interpreted_node";
   5132 }
   5133 
   5134 int non_interpreted_node::force_tprint()
   5135 {
   5136   return 0;
   5137 }
   5138 
   5139 int non_interpreted_node::is_tag()
   5140 {
   5141   return 0;
   5142 }
   5143 
   5144 node *non_interpreted_node::copy()
   5145 {
   5146   return new non_interpreted_node(mac);
   5147 }
   5148 
   5149 int non_interpreted_node::interpret(macro *m)
   5150 {
   5151   string_iterator si(mac);
   5152   node *n = 0;		// pacify compiler
   5153   for (;;) {
   5154     int c = si.get(&n);
   5155     if (c == EOF)
   5156       break;
   5157     if (c == 0)
   5158       m->append(n);
   5159     else
   5160       m->append(c);
   5161   }
   5162   return 1;
   5163 }
   5164 
   5165 static node *do_non_interpreted()
   5166 {
   5167   node *n;
   5168   int c;
   5169   macro mac;
   5170   while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
   5171     if (c == 0)
   5172       mac.append(n);
   5173     else
   5174       mac.append(c);
   5175   if (c == EOF || c == '\n') {
   5176     error("missing \\?");
   5177     return 0;
   5178   }
   5179   return new non_interpreted_node(mac);
   5180 }
   5181 
   5182 static void encode_char(macro *mac, char c)
   5183 {
   5184   if (c == '\0') {
   5185     if ((font::use_charnames_in_special) && tok.special()) {
   5186       charinfo *ci = tok.get_char(1);
   5187       const char *s = ci->get_symbol()->contents();
   5188       if (s[0] != (char)0) {
   5189 	mac->append('\\');
   5190 	mac->append('(');
   5191 	int i = 0;
   5192 	while (s[i] != (char)0) {
   5193 	  mac->append(s[i]);
   5194 	  i++;
   5195 	}
   5196 	mac->append('\\');
   5197 	mac->append(')');
   5198       }
   5199     }
   5200     else if (tok.stretchable_space()
   5201 	     || tok.unstretchable_space())
   5202       mac->append(' ');
   5203     else if (!(tok.hyphen_indicator()
   5204 	       || tok.dummy()
   5205 	       || tok.transparent_dummy()
   5206 	       || tok.zero_width_break()))
   5207       error("%1 is invalid within \\X", tok.description());
   5208   }
   5209   else {
   5210     if ((font::use_charnames_in_special) && (c == '\\')) {
   5211       /*
   5212        * add escape escape sequence
   5213        */
   5214       mac->append(c);
   5215     }
   5216     mac->append(c);
   5217   }
   5218 }
   5219 
   5220 node *do_special()
   5221 {
   5222   token start;
   5223   start.next();
   5224   int start_level = input_stack::get_level();
   5225   macro mac;
   5226   for (tok.next();
   5227        tok != start || input_stack::get_level() != start_level;
   5228        tok.next()) {
   5229     if (tok.eof()) {
   5230       warning(WARN_DELIM, "missing closing delimiter");
   5231       return 0;
   5232     }
   5233     if (tok.newline()) {
   5234       input_stack::push(make_temp_iterator("\n"));
   5235       warning(WARN_DELIM, "missing closing delimiter");
   5236       break;
   5237     }
   5238     unsigned char c;
   5239     if (tok.space())
   5240       c = ' ';
   5241     else if (tok.tab())
   5242       c = '\t';
   5243     else if (tok.leader())
   5244       c = '\001';
   5245     else if (tok.backspace())
   5246       c = '\b';
   5247     else
   5248       c = tok.ch();
   5249     encode_char(&mac, c);
   5250   }
   5251   return new special_node(mac);
   5252 }
   5253 
   5254 void output_request()
   5255 {
   5256   if (!tok.newline() && !tok.eof()) {
   5257     int c;
   5258     for (;;) {
   5259       c = get_copy(0);
   5260       if (c == '"') {
   5261 	c = get_copy(0);
   5262 	break;
   5263       }
   5264       if (c != ' ' && c != '\t')
   5265 	break;
   5266     }
   5267     for (; c != '\n' && c != EOF; c = get_copy(0))
   5268       topdiv->transparent_output(c);
   5269     topdiv->transparent_output('\n');
   5270   }
   5271   tok.next();
   5272 }
   5273 
   5274 extern int image_no;		// from node.cpp
   5275 
   5276 static node *do_suppress(symbol nm)
   5277 {
   5278   if (nm.is_null() || nm.is_empty()) {
   5279     error("expecting an argument to escape \\O");
   5280     return 0;
   5281   }
   5282   const char *s = nm.contents();
   5283   switch (*s) {
   5284   case '0':
   5285     if (begin_level == 0)
   5286       // suppress generation of glyphs
   5287       return new suppress_node(0, 0);
   5288     break;
   5289   case '1':
   5290     if (begin_level == 0)
   5291       // enable generation of glyphs
   5292       return new suppress_node(1, 0);
   5293     break;
   5294   case '2':
   5295     if (begin_level == 0)
   5296       return new suppress_node(1, 1);
   5297     break;
   5298   case '3':
   5299     begin_level++;
   5300     break;
   5301   case '4':
   5302     begin_level--;
   5303     break;
   5304   case '5':
   5305     {
   5306       s++;			// move over '5'
   5307       char position = *s;
   5308       if (*s == (char)0) {
   5309 	error("missing position and filename in \\O");
   5310 	return 0;
   5311       }
   5312       if (!(position == 'l'
   5313 	    || position == 'r'
   5314 	    || position == 'c'
   5315 	    || position == 'i')) {
   5316 	error("l, r, c, or i position expected (got %1 in \\O)", position);
   5317 	return 0;
   5318       }
   5319       s++;			// onto image name
   5320       if (s == (char *)0) {
   5321 	error("missing image name for \\O");
   5322 	return 0;
   5323       }
   5324       image_no++;
   5325       if (begin_level == 0)
   5326 	return new suppress_node(symbol(s), position, image_no);
   5327     }
   5328     break;
   5329   default:
   5330     error("`%1' is an invalid argument to \\O", *s);
   5331   }
   5332   return 0;
   5333 }
   5334 
   5335 void special_node::tprint(troff_output_file *out)
   5336 {
   5337   tprint_start(out);
   5338   string_iterator iter(mac);
   5339   for (;;) {
   5340     int c = iter.get(0);
   5341     if (c == EOF)
   5342       break;
   5343     for (const char *s = ::asciify(c); *s; s++)
   5344       tprint_char(out, *s);
   5345   }
   5346   tprint_end(out);
   5347 }
   5348 
   5349 int get_file_line(const char **filename, int *lineno)
   5350 {
   5351   return input_stack::get_location(0, filename, lineno);
   5352 }
   5353 
   5354 void line_file()
   5355 {
   5356   int n;
   5357   if (get_integer(&n)) {
   5358     const char *filename = 0;
   5359     if (has_arg()) {
   5360       symbol s = get_long_name();
   5361       filename = s.contents();
   5362     }
   5363     (void)input_stack::set_location(filename, n-1);
   5364   }
   5365   skip_line();
   5366 }
   5367 
   5368 static int nroff_mode = 0;
   5369 
   5370 static void nroff_request()
   5371 {
   5372   nroff_mode = 1;
   5373   skip_line();
   5374 }
   5375 
   5376 static void troff_request()
   5377 {
   5378   nroff_mode = 0;
   5379   skip_line();
   5380 }
   5381 
   5382 static void skip_alternative()
   5383 {
   5384   int level = 0;
   5385   // ensure that ``.if 0\{'' works as expected
   5386   if (tok.left_brace())
   5387     level++;
   5388   int c;
   5389   for (;;) {
   5390     c = input_stack::get(0);
   5391     if (c == EOF)
   5392       break;
   5393     if (c == ESCAPE_LEFT_BRACE)
   5394       ++level;
   5395     else if (c == ESCAPE_RIGHT_BRACE)
   5396       --level;
   5397     else if (c == escape_char && escape_char > 0)
   5398       switch(input_stack::get(0)) {
   5399       case '{':
   5400 	++level;
   5401 	break;
   5402       case '}':
   5403 	--level;
   5404 	break;
   5405       case '"':
   5406 	while ((c = input_stack::get(0)) != '\n' && c != EOF)
   5407 	  ;
   5408       }
   5409     /*
   5410       Note that the level can properly be < 0, eg
   5411 
   5412 	.if 1 \{\
   5413 	.if 0 \{\
   5414 	.\}\}
   5415 
   5416       So don't give an error message in this case.
   5417     */
   5418     if (level <= 0 && c == '\n')
   5419       break;
   5420   }
   5421   tok.next();
   5422 }
   5423 
   5424 static void begin_alternative()
   5425 {
   5426   while (tok.space() || tok.left_brace())
   5427     tok.next();
   5428 }
   5429 
   5430 void nop_request()
   5431 {
   5432   while (tok.space())
   5433     tok.next();
   5434 }
   5435 
   5436 static int_stack if_else_stack;
   5437 
   5438 int do_if_request()
   5439 {
   5440   int invert = 0;
   5441   while (tok.space())
   5442     tok.next();
   5443   while (tok.ch() == '!') {
   5444     tok.next();
   5445     invert = !invert;
   5446   }
   5447   int result;
   5448   unsigned char c = tok.ch();
   5449   if (c == 't') {
   5450     tok.next();
   5451     result = !nroff_mode;
   5452   }
   5453   else if (c == 'n') {
   5454     tok.next();
   5455     result = nroff_mode;
   5456   }
   5457   else if (c == 'v') {
   5458     tok.next();
   5459     result = 0;
   5460   }
   5461   else if (c == 'o') {
   5462     result = (topdiv->get_page_number() & 1);
   5463     tok.next();
   5464   }
   5465   else if (c == 'e') {
   5466     result = !(topdiv->get_page_number() & 1);
   5467     tok.next();
   5468   }
   5469   else if (c == 'd' || c == 'r') {
   5470     tok.next();
   5471     symbol nm = get_name(1);
   5472     if (nm.is_null()) {
   5473       skip_alternative();
   5474       return 0;
   5475     }
   5476     result = (c == 'd'
   5477 	      ? request_dictionary.lookup(nm) != 0
   5478 	      : number_reg_dictionary.lookup(nm) != 0);
   5479   }
   5480   else if (c == 'm') {
   5481     tok.next();
   5482     symbol nm = get_long_name(1);
   5483     if (nm.is_null()) {
   5484       skip_alternative();
   5485       return 0;
   5486     }
   5487     result = (nm == default_symbol
   5488 	      || color_dictionary.lookup(nm) != 0);
   5489   }
   5490   else if (c == 'c') {
   5491     tok.next();
   5492     tok.skip();
   5493     charinfo *ci = tok.get_char(1);
   5494     if (ci == 0) {
   5495       skip_alternative();
   5496       return 0;
   5497     }
   5498     result = character_exists(ci, curenv);
   5499     tok.next();
   5500   }
   5501   else if (c == 'F') {
   5502     tok.next();
   5503     symbol nm = get_long_name(1);
   5504     if (nm.is_null()) {
   5505       skip_alternative();
   5506       return 0;
   5507     }
   5508     result = check_font(curenv->get_family()->nm, nm);
   5509   }
   5510   else if (c == 'S') {
   5511     tok.next();
   5512     symbol nm = get_long_name(1);
   5513     if (nm.is_null()) {
   5514       skip_alternative();
   5515       return 0;
   5516     }
   5517     result = check_style(nm);
   5518   }
   5519   else if (tok.space())
   5520     result = 0;
   5521   else if (tok.delimiter()) {
   5522     token delim = tok;
   5523     int delim_level = input_stack::get_level();
   5524     environment env1(curenv);
   5525     environment env2(curenv);
   5526     environment *oldenv = curenv;
   5527     curenv = &env1;
   5528     suppress_push = 1;
   5529     for (int i = 0; i < 2; i++) {
   5530       for (;;) {
   5531 	tok.next();
   5532 	if (tok.newline() || tok.eof()) {
   5533 	  warning(WARN_DELIM, "missing closing delimiter");
   5534 	  tok.next();
   5535 	  curenv = oldenv;
   5536 	  return 0;
   5537 	}
   5538 	if (tok == delim
   5539 	    && (compatible_flag || input_stack::get_level() == delim_level))
   5540 	  break;
   5541 	tok.process();
   5542       }
   5543       curenv = &env2;
   5544     }
   5545     node *n1 = env1.extract_output_line();
   5546     node *n2 = env2.extract_output_line();
   5547     result = same_node_list(n1, n2);
   5548     delete_node_list(n1);
   5549     delete_node_list(n2);
   5550     curenv = oldenv;
   5551     have_input = 0;
   5552     suppress_push = 0;
   5553     tok.next();
   5554   }
   5555   else {
   5556     units n;
   5557     if (!get_number(&n, 'u')) {
   5558       skip_alternative();
   5559       return 0;
   5560     }
   5561     else
   5562       result = n > 0;
   5563   }
   5564   if (invert)
   5565     result = !result;
   5566   if (result)
   5567     begin_alternative();
   5568   else
   5569     skip_alternative();
   5570   return result;
   5571 }
   5572 
   5573 void if_else_request()
   5574 {
   5575   if_else_stack.push(do_if_request());
   5576 }
   5577 
   5578 void if_request()
   5579 {
   5580   do_if_request();
   5581 }
   5582 
   5583 void else_request()
   5584 {
   5585   if (if_else_stack.is_empty()) {
   5586     warning(WARN_EL, "unbalanced .el request");
   5587     skip_alternative();
   5588   }
   5589   else {
   5590     if (if_else_stack.pop())
   5591       skip_alternative();
   5592     else
   5593       begin_alternative();
   5594   }
   5595 }
   5596 
   5597 static int while_depth = 0;
   5598 static int while_break_flag = 0;
   5599 
   5600 void while_request()
   5601 {
   5602   macro mac;
   5603   int escaped = 0;
   5604   int level = 0;
   5605   mac.append(new token_node(tok));
   5606   for (;;) {
   5607     node *n = 0;		// pacify compiler
   5608     int c = input_stack::get(&n);
   5609     if (c == EOF)
   5610       break;
   5611     if (c == 0) {
   5612       escaped = 0;
   5613       mac.append(n);
   5614     }
   5615     else if (escaped) {
   5616       if (c == '{')
   5617 	level += 1;
   5618       else if (c == '}')
   5619 	level -= 1;
   5620       escaped = 0;
   5621       mac.append(c);
   5622     }
   5623     else {
   5624       if (c == ESCAPE_LEFT_BRACE)
   5625 	level += 1;
   5626       else if (c == ESCAPE_RIGHT_BRACE)
   5627 	level -= 1;
   5628       else if (c == escape_char)
   5629 	escaped = 1;
   5630       mac.append(c);
   5631       if (c == '\n' && level <= 0)
   5632 	break;
   5633     }
   5634   }
   5635   if (level != 0)
   5636     error("unbalanced \\{ \\}");
   5637   else {
   5638     while_depth++;
   5639     input_stack::add_boundary();
   5640     for (;;) {
   5641       input_stack::push(new string_iterator(mac, "while loop"));
   5642       tok.next();
   5643       if (!do_if_request()) {
   5644 	while (input_stack::get(0) != EOF)
   5645 	  ;
   5646 	break;
   5647       }
   5648       process_input_stack();
   5649       if (while_break_flag || input_stack::is_return_boundary()) {
   5650 	while_break_flag = 0;
   5651 	break;
   5652       }
   5653     }
   5654     input_stack::remove_boundary();
   5655     while_depth--;
   5656   }
   5657   tok.next();
   5658 }
   5659 
   5660 void while_break_request()
   5661 {
   5662   if (!while_depth) {
   5663     error("no while loop");
   5664     skip_line();
   5665   }
   5666   else {
   5667     while_break_flag = 1;
   5668     while (input_stack::get(0) != EOF)
   5669       ;
   5670     tok.next();
   5671   }
   5672 }
   5673 
   5674 void while_continue_request()
   5675 {
   5676   if (!while_depth) {
   5677     error("no while loop");
   5678     skip_line();
   5679   }
   5680   else {
   5681     while (input_stack::get(0) != EOF)
   5682       ;
   5683     tok.next();
   5684   }
   5685 }
   5686 
   5687 // .so
   5688 
   5689 void source()
   5690 {
   5691   symbol nm = get_long_name(1);
   5692   if (nm.is_null())
   5693     skip_line();
   5694   else {
   5695     while (!tok.newline() && !tok.eof())
   5696       tok.next();
   5697     errno = 0;
   5698     FILE *fp = include_search_path.open_file_cautious(nm.contents());
   5699     if (fp)
   5700       input_stack::push(new file_iterator(fp, nm.contents()));
   5701     else
   5702       error("can't open `%1': %2", nm.contents(), strerror(errno));
   5703     tok.next();
   5704   }
   5705 }
   5706 
   5707 // like .so but use popen()
   5708 
   5709 void pipe_source()
   5710 {
   5711   if (safer_flag) {
   5712     error(".pso request not allowed in safer mode");
   5713     skip_line();
   5714   }
   5715   else {
   5716 #ifdef POPEN_MISSING
   5717     error("pipes not available on this system");
   5718     skip_line();
   5719 #else /* not POPEN_MISSING */
   5720     if (tok.newline() || tok.eof())
   5721       error("missing command");
   5722     else {
   5723       int c;
   5724       while ((c = get_copy(0)) == ' ' || c == '\t')
   5725 	;
   5726       int buf_size = 24;
   5727       char *buf = new char[buf_size];
   5728       int buf_used = 0;
   5729       for (; c != '\n' && c != EOF; c = get_copy(0)) {
   5730 	const char *s = asciify(c);
   5731 	int slen = strlen(s);
   5732 	if (buf_used + slen + 1> buf_size) {
   5733 	  char *old_buf = buf;
   5734 	  int old_buf_size = buf_size;
   5735 	  buf_size *= 2;
   5736 	  buf = new char[buf_size];
   5737 	  memcpy(buf, old_buf, old_buf_size);
   5738 	  a_delete old_buf;
   5739 	}
   5740 	strcpy(buf + buf_used, s);
   5741 	buf_used += slen;
   5742       }
   5743       buf[buf_used] = '\0';
   5744       errno = 0;
   5745       FILE *fp = popen(buf, POPEN_RT);
   5746       if (fp)
   5747 	input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
   5748       else
   5749 	error("can't open pipe to process `%1': %2", buf, strerror(errno));
   5750       a_delete buf;
   5751     }
   5752     tok.next();
   5753 #endif /* not POPEN_MISSING */
   5754   }
   5755 }
   5756 
   5757 // .psbb
   5758 
   5759 static int llx_reg_contents = 0;
   5760 static int lly_reg_contents = 0;
   5761 static int urx_reg_contents = 0;
   5762 static int ury_reg_contents = 0;
   5763 
   5764 struct bounding_box {
   5765   int llx, lly, urx, ury;
   5766 };
   5767 
   5768 /* Parse the argument to a %%BoundingBox comment.  Return 1 if it
   5769 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
   5770 
   5771 int parse_bounding_box(char *p, bounding_box *bb)
   5772 {
   5773   if (sscanf(p, "%d %d %d %d",
   5774 	     &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
   5775     return 1;
   5776   else {
   5777     /* The Document Structuring Conventions say that the numbers
   5778        should be integers.  Unfortunately some broken applications
   5779        get this wrong. */
   5780     double x1, x2, x3, x4;
   5781     if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
   5782       bb->llx = (int)x1;
   5783       bb->lly = (int)x2;
   5784       bb->urx = (int)x3;
   5785       bb->ury = (int)x4;
   5786       return 1;
   5787     }
   5788     else {
   5789       for (; *p == ' ' || *p == '\t'; p++)
   5790 	;
   5791       if (strncmp(p, "(atend)", 7) == 0) {
   5792 	return 2;
   5793       }
   5794     }
   5795   }
   5796   bb->llx = bb->lly = bb->urx = bb->ury = 0;
   5797   return 0;
   5798 }
   5799 
   5800 // This version is taken from psrm.cpp
   5801 
   5802 #define PS_LINE_MAX 255
   5803 cset white_space("\n\r \t");
   5804 
   5805 int ps_get_line(char *buf, FILE *fp, const char* filename)
   5806 {
   5807   int c = getc(fp);
   5808   if (c == EOF) {
   5809     buf[0] = '\0';
   5810     return 0;
   5811   }
   5812   int i = 0;
   5813   int err = 0;
   5814   while (c != '\r' && c != '\n' && c != EOF) {
   5815     if ((c < 0x1b && !white_space(c)) || c == 0x7f)
   5816       error("invalid input character code %1 in `%2'", int(c), filename);
   5817     else if (i < PS_LINE_MAX)
   5818       buf[i++] = c;
   5819     else if (!err) {
   5820       err = 1;
   5821       error("PostScript file `%1' is non-conforming "
   5822 	    "because length of line exceeds 255", filename);
   5823     }
   5824     c = getc(fp);
   5825   }
   5826   buf[i++] = '\n';
   5827   buf[i] = '\0';
   5828   if (c == '\r') {
   5829     c = getc(fp);
   5830     if (c != EOF && c != '\n')
   5831       ungetc(c, fp);
   5832   }
   5833   return 1;
   5834 }
   5835 
   5836 inline void assign_registers(int llx, int lly, int urx, int ury)
   5837 {
   5838   llx_reg_contents = llx;
   5839   lly_reg_contents = lly;
   5840   urx_reg_contents = urx;
   5841   ury_reg_contents = ury;
   5842 }
   5843 
   5844 void do_ps_file(FILE *fp, const char* filename)
   5845 {
   5846   bounding_box bb;
   5847   int bb_at_end = 0;
   5848   char buf[PS_LINE_MAX];
   5849   llx_reg_contents = lly_reg_contents =
   5850     urx_reg_contents = ury_reg_contents = 0;
   5851   if (!ps_get_line(buf, fp, filename)) {
   5852     error("`%1' is empty", filename);
   5853     return;
   5854   }
   5855   if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
   5856     error("`%1' is not conforming to the Document Structuring Conventions",
   5857 	  filename);
   5858     return;
   5859   }
   5860   while (ps_get_line(buf, fp, filename) != 0) {
   5861     if (buf[0] != '%' || buf[1] != '%'
   5862 	|| strncmp(buf + 2, "EndComments", 11) == 0)
   5863       break;
   5864     if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
   5865       int res = parse_bounding_box(buf + 14, &bb);
   5866       if (res == 1) {
   5867 	assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
   5868 	return;
   5869       }
   5870       else if (res == 2) {
   5871 	bb_at_end = 1;
   5872 	break;
   5873       }
   5874       else {
   5875 	error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
   5876 	      filename);
   5877 	return;
   5878       }
   5879     }
   5880   }
   5881   if (bb_at_end) {
   5882     long offset;
   5883     int last_try = 0;
   5884     /* in the trailer, the last BoundingBox comment is significant */
   5885     for (offset = 512; !last_try; offset *= 2) {
   5886       int had_trailer = 0;
   5887       int got_bb = 0;
   5888       if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
   5889 	last_try = 1;
   5890 	if (fseek(fp, 0L, 0) == -1)
   5891 	  break;
   5892       }
   5893       while (ps_get_line(buf, fp, filename) != 0) {
   5894 	if (buf[0] == '%' && buf[1] == '%') {
   5895 	  if (!had_trailer) {
   5896 	    if (strncmp(buf + 2, "Trailer", 7) == 0)
   5897 	      had_trailer = 1;
   5898 	  }
   5899 	  else {
   5900 	    if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
   5901 	      int res = parse_bounding_box(buf + 14, &bb);
   5902 	      if (res == 1)
   5903 		got_bb = 1;
   5904 	      else if (res == 2) {
   5905 		error("`(atend)' not allowed in trailer of `%1'", filename);
   5906 		return;
   5907 	      }
   5908 	      else {
   5909 		error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
   5910 		      filename);
   5911 		return;
   5912 	      }
   5913 	    }
   5914 	  }
   5915 	}
   5916       }
   5917       if (got_bb) {
   5918 	assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
   5919 	return;
   5920       }
   5921     }
   5922   }
   5923   error("%%%%BoundingBox comment not found in `%1'", filename);
   5924 }
   5925 
   5926 void ps_bbox_request()
   5927 {
   5928   symbol nm = get_long_name(1);
   5929   if (nm.is_null())
   5930     skip_line();
   5931   else {
   5932     while (!tok.newline() && !tok.eof())
   5933       tok.next();
   5934     errno = 0;
   5935     // PS files might contain non-printable characters, such as ^Z
   5936     // and CRs not followed by an LF, so open them in binary mode.
   5937     FILE *fp = include_search_path.open_file_cautious(nm.contents(),
   5938 						      0, FOPEN_RB);
   5939     if (fp) {
   5940       do_ps_file(fp, nm.contents());
   5941       fclose(fp);
   5942     }
   5943     else
   5944       error("can't open `%1': %2", nm.contents(), strerror(errno));
   5945     tok.next();
   5946   }
   5947 }
   5948 
   5949 const char *asciify(int c)
   5950 {
   5951   static char buf[3];
   5952   buf[0] = escape_char == '\0' ? '\\' : escape_char;
   5953   buf[1] = buf[2] = '\0';
   5954   switch (c) {
   5955   case ESCAPE_QUESTION:
   5956     buf[1] = '?';
   5957     break;
   5958   case ESCAPE_AMPERSAND:
   5959     buf[1] = '&';
   5960     break;
   5961   case ESCAPE_RIGHT_PARENTHESIS:
   5962     buf[1] = ')';
   5963     break;
   5964   case ESCAPE_UNDERSCORE:
   5965     buf[1] = '_';
   5966     break;
   5967   case ESCAPE_BAR:
   5968     buf[1] = '|';
   5969     break;
   5970   case ESCAPE_CIRCUMFLEX:
   5971     buf[1] = '^';
   5972     break;
   5973   case ESCAPE_LEFT_BRACE:
   5974     buf[1] = '{';
   5975     break;
   5976   case ESCAPE_RIGHT_BRACE:
   5977     buf[1] = '}';
   5978     break;
   5979   case ESCAPE_LEFT_QUOTE:
   5980     buf[1] = '`';
   5981     break;
   5982   case ESCAPE_RIGHT_QUOTE:
   5983     buf[1] = '\'';
   5984     break;
   5985   case ESCAPE_HYPHEN:
   5986     buf[1] = '-';
   5987     break;
   5988   case ESCAPE_BANG:
   5989     buf[1] = '!';
   5990     break;
   5991   case ESCAPE_c:
   5992     buf[1] = 'c';
   5993     break;
   5994   case ESCAPE_e:
   5995     buf[1] = 'e';
   5996     break;
   5997   case ESCAPE_E:
   5998     buf[1] = 'E';
   5999     break;
   6000   case ESCAPE_PERCENT:
   6001     buf[1] = '%';
   6002     break;
   6003   case ESCAPE_SPACE:
   6004     buf[1] = ' ';
   6005     break;
   6006   case ESCAPE_TILDE:
   6007     buf[1] = '~';
   6008     break;
   6009   case ESCAPE_COLON:
   6010     buf[1] = ':';
   6011     break;
   6012   case PUSH_GROFF_MODE:
   6013   case PUSH_COMP_MODE:
   6014   case POP_GROFFCOMP_MODE:
   6015     buf[0] = '\0';
   6016     break;
   6017   default:
   6018     if (invalid_input_char(c))
   6019       buf[0] = '\0';
   6020     else
   6021       buf[0] = c;
   6022     break;
   6023   }
   6024   return buf;
   6025 }
   6026 
   6027 const char *input_char_description(int c)
   6028 {
   6029   switch (c) {
   6030   case '\n':
   6031     return "a newline character";
   6032   case '\b':
   6033     return "a backspace character";
   6034   case '\001':
   6035     return "a leader character";
   6036   case '\t':
   6037     return "a tab character";
   6038   case ' ':
   6039     return "a space character";
   6040   case '\0':
   6041     return "a node";
   6042   }
   6043   static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
   6044   if (invalid_input_char(c)) {
   6045     const char *s = asciify(c);
   6046     if (*s) {
   6047       buf[0] = '`';
   6048       strcpy(buf + 1, s);
   6049       strcat(buf, "'");
   6050       return buf;
   6051     }
   6052     sprintf(buf, "magic character code %d", c);
   6053     return buf;
   6054   }
   6055   if (csprint(c)) {
   6056     buf[0] = '`';
   6057     buf[1] = c;
   6058     buf[2] = '\'';
   6059     return buf;
   6060   }
   6061   sprintf(buf, "character code %d", c);
   6062   return buf;
   6063 }
   6064 
   6065 void tag()
   6066 {
   6067   if (!tok.newline() && !tok.eof()) {
   6068     string s;
   6069     int c;
   6070     for (;;) {
   6071       c = get_copy(0);
   6072       if (c == '"') {
   6073 	c = get_copy(0);
   6074 	break;
   6075       }
   6076       if (c != ' ' && c != '\t')
   6077 	break;
   6078     }
   6079     s = "x X ";
   6080     for (; c != '\n' && c != EOF; c = get_copy(0))
   6081       s += (char)c;
   6082     s += '\n';
   6083     curenv->add_node(new tag_node(s, 0));
   6084   }
   6085   tok.next();
   6086 }
   6087 
   6088 void taga()
   6089 {
   6090   if (!tok.newline() && !tok.eof()) {
   6091     string s;
   6092     int c;
   6093     for (;;) {
   6094       c = get_copy(0);
   6095       if (c == '"') {
   6096 	c = get_copy(0);
   6097 	break;
   6098       }
   6099       if (c != ' ' && c != '\t')
   6100 	break;
   6101     }
   6102     s = "x X ";
   6103     for (; c != '\n' && c != EOF; c = get_copy(0))
   6104       s += (char)c;
   6105     s += '\n';
   6106     curenv->add_node(new tag_node(s, 1));
   6107   }
   6108   tok.next();
   6109 }
   6110 
   6111 // .tm, .tm1, and .tmc
   6112 
   6113 void do_terminal(int newline, int string_like)
   6114 {
   6115   if (!tok.newline() && !tok.eof()) {
   6116     int c;
   6117     for (;;) {
   6118       c = get_copy(0);
   6119       if (string_like && c == '"') {
   6120 	c = get_copy(0);
   6121 	break;
   6122       }
   6123       if (c != ' ' && c != '\t')
   6124 	break;
   6125     }
   6126     for (; c != '\n' && c != EOF; c = get_copy(0))
   6127       fputs(asciify(c), stderr);
   6128   }
   6129   if (newline)
   6130     fputc('\n', stderr);
   6131   fflush(stderr);
   6132   tok.next();
   6133 }
   6134 
   6135 void terminal()
   6136 {
   6137   do_terminal(1, 0);
   6138 }
   6139 
   6140 void terminal1()
   6141 {
   6142   do_terminal(1, 1);
   6143 }
   6144 
   6145 void terminal_continue()
   6146 {
   6147   do_terminal(0, 1);
   6148 }
   6149 
   6150 dictionary stream_dictionary(20);
   6151 
   6152 void do_open(int append)
   6153 {
   6154   symbol stream = get_name(1);
   6155   if (!stream.is_null()) {
   6156     symbol filename = get_long_name(1);
   6157     if (!filename.is_null()) {
   6158       errno = 0;
   6159       FILE *fp = fopen(filename.contents(), append ? "a" : "w");
   6160       if (!fp) {
   6161 	error("can't open `%1' for %2: %3",
   6162 	      filename.contents(),
   6163 	      append ? "appending" : "writing",
   6164 	      strerror(errno));
   6165 	fp = (FILE *)stream_dictionary.remove(stream);
   6166       }
   6167       else
   6168 	fp = (FILE *)stream_dictionary.lookup(stream, fp);
   6169       if (fp)
   6170 	fclose(fp);
   6171     }
   6172   }
   6173   skip_line();
   6174 }
   6175 
   6176 void open_request()
   6177 {
   6178   if (safer_flag) {
   6179     error(".open request not allowed in safer mode");
   6180     skip_line();
   6181   }
   6182   else
   6183     do_open(0);
   6184 }
   6185 
   6186 void opena_request()
   6187 {
   6188   if (safer_flag) {
   6189     error(".opena request not allowed in safer mode");
   6190     skip_line();
   6191   }
   6192   else
   6193     do_open(1);
   6194 }
   6195 
   6196 void close_request()
   6197 {
   6198   symbol stream = get_name(1);
   6199   if (!stream.is_null()) {
   6200     FILE *fp = (FILE *)stream_dictionary.remove(stream);
   6201     if (!fp)
   6202       error("no stream named `%1'", stream.contents());
   6203     else
   6204       fclose(fp);
   6205   }
   6206   skip_line();
   6207 }
   6208 
   6209 // .write and .writec
   6210 
   6211 void do_write_request(int newline)
   6212 {
   6213   symbol stream = get_name(1);
   6214   if (stream.is_null()) {
   6215     skip_line();
   6216     return;
   6217   }
   6218   FILE *fp = (FILE *)stream_dictionary.lookup(stream);
   6219   if (!fp) {
   6220     error("no stream named `%1'", stream.contents());
   6221     skip_line();
   6222     return;
   6223   }
   6224   int c;
   6225   while ((c = get_copy(0)) == ' ')
   6226     ;
   6227   if (c == '"')
   6228     c = get_copy(0);
   6229   for (; c != '\n' && c != EOF; c = get_copy(0))
   6230     fputs(asciify(c), fp);
   6231   if (newline)
   6232     fputc('\n', fp);
   6233   fflush(fp);
   6234   tok.next();
   6235 }
   6236 
   6237 void write_request()
   6238 {
   6239   do_write_request(1);
   6240 }
   6241 
   6242 void write_request_continue()
   6243 {
   6244   do_write_request(0);
   6245 }
   6246 
   6247 void write_macro_request()
   6248 {
   6249   symbol stream = get_name(1);
   6250   if (stream.is_null()) {
   6251     skip_line();
   6252     return;
   6253   }
   6254   FILE *fp = (FILE *)stream_dictionary.lookup(stream);
   6255   if (!fp) {
   6256     error("no stream named `%1'", stream.contents());
   6257     skip_line();
   6258     return;
   6259   }
   6260   symbol s = get_name(1);
   6261   if (s.is_null()) {
   6262     skip_line();
   6263     return;
   6264   }
   6265   request_or_macro *p = lookup_request(s);
   6266   macro *m = p->to_macro();
   6267   if (!m)
   6268     error("cannot write request");
   6269   else {
   6270     string_iterator iter(*m);
   6271     for (;;) {
   6272       int c = iter.get(0);
   6273       if (c == EOF)
   6274 	break;
   6275       fputs(asciify(c), fp);
   6276     }
   6277     fflush(fp);
   6278   }
   6279   skip_line();
   6280 }
   6281 
   6282 void warnscale_request()
   6283 {
   6284   if (has_arg()) {
   6285     char c = tok.ch();
   6286     if (c == 'u')
   6287       warn_scale = 1.0;
   6288     else if (c == 'i')
   6289       warn_scale = (double)units_per_inch;
   6290     else if (c == 'c')
   6291       warn_scale = (double)units_per_inch / 2.54;
   6292     else if (c == 'p')
   6293       warn_scale = (double)units_per_inch / 72.0;
   6294     else if (c == 'P')
   6295       warn_scale = (double)units_per_inch / 6.0;
   6296     else {
   6297       warning(WARN_SCALE,
   6298 	      "invalid scaling indicator `%1', using `i' instead", c);
   6299       c = 'i';
   6300     }
   6301     warn_scaling_indicator = c;
   6302   }
   6303   skip_line();
   6304 }
   6305 
   6306 void spreadwarn_request()
   6307 {
   6308   hunits n;
   6309   if (has_arg() && get_hunits(&n, 'm')) {
   6310     if (n < 0)
   6311       n = 0;
   6312     hunits em = curenv->get_size();
   6313     spread_limit = (double)n.to_units()
   6314 		   / (em.is_zero() ? hresolution : em.to_units());
   6315   }
   6316   else
   6317     spread_limit = -spread_limit - 1;	// no arg toggles on/off without
   6318 					// changing value; we mirror at
   6319 					// -0.5 to make zero a valid value
   6320   skip_line();
   6321 }
   6322 
   6323 static void init_charset_table()
   6324 {
   6325   char buf[16];
   6326   strcpy(buf, "char");
   6327   for (int i = 0; i < 256; i++) {
   6328     strcpy(buf + 4, i_to_a(i));
   6329     charset_table[i] = get_charinfo(symbol(buf));
   6330     charset_table[i]->set_ascii_code(i);
   6331     if (csalpha(i))
   6332       charset_table[i]->set_hyphenation_code(cmlower(i));
   6333   }
   6334   charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
   6335   charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
   6336   charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
   6337   charset_table['-']->set_flags(charinfo::BREAK_AFTER);
   6338   charset_table['"']->set_flags(charinfo::TRANSPARENT);
   6339   charset_table['\'']->set_flags(charinfo::TRANSPARENT);
   6340   charset_table[')']->set_flags(charinfo::TRANSPARENT);
   6341   charset_table[']']->set_flags(charinfo::TRANSPARENT);
   6342   charset_table['*']->set_flags(charinfo::TRANSPARENT);
   6343   get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
   6344   get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
   6345   get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
   6346   get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
   6347   get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
   6348   get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
   6349   get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
   6350   get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
   6351   get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
   6352   page_character = charset_table['%'];
   6353 }
   6354 
   6355 static void init_hpf_code_table()
   6356 {
   6357   for (int i = 0; i < 256; i++)
   6358     hpf_code_table[i] = i;
   6359 }
   6360 
   6361 static void do_translate(int translate_transparent, int translate_input)
   6362 {
   6363   tok.skip();
   6364   while (!tok.newline() && !tok.eof()) {
   6365     if (tok.space()) {
   6366       // This is a really bizarre troff feature.
   6367       tok.next();
   6368       translate_space_to_dummy = tok.dummy();
   6369       if (tok.newline() || tok.eof())
   6370 	break;
   6371       tok.next();
   6372       continue;
   6373     }
   6374     charinfo *ci1 = tok.get_char(1);
   6375     if (ci1 == 0)
   6376       break;
   6377     tok.next();
   6378     if (tok.newline() || tok.eof()) {
   6379       ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
   6380 				   translate_transparent);
   6381       break;
   6382     }
   6383     if (tok.space())
   6384       ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
   6385 				   translate_transparent);
   6386     else if (tok.stretchable_space())
   6387       ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
   6388 				   translate_transparent);
   6389     else if (tok.dummy())
   6390       ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
   6391 				   translate_transparent);
   6392     else if (tok.hyphen_indicator())
   6393       ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
   6394 				   translate_transparent);
   6395     else {
   6396       charinfo *ci2 = tok.get_char(1);
   6397       if (ci2 == 0)
   6398 	break;
   6399       if (ci1 == ci2)
   6400 	ci1->set_translation(0, translate_transparent, translate_input);
   6401       else
   6402 	ci1->set_translation(ci2, translate_transparent, translate_input);
   6403     }
   6404     tok.next();
   6405   }
   6406   skip_line();
   6407 }
   6408 
   6409 void translate()
   6410 {
   6411   do_translate(1, 0);
   6412 }
   6413 
   6414 void translate_no_transparent()
   6415 {
   6416   do_translate(0, 0);
   6417 }
   6418 
   6419 void translate_input()
   6420 {
   6421   do_translate(1, 1);
   6422 }
   6423 
   6424 void char_flags()
   6425 {
   6426   int flags;
   6427   if (get_integer(&flags))
   6428     while (has_arg()) {
   6429       charinfo *ci = tok.get_char(1);
   6430       if (ci) {
   6431 	charinfo *tem = ci->get_translation();
   6432 	if (tem)
   6433 	  ci = tem;
   6434 	ci->set_flags(flags);
   6435       }
   6436       tok.next();
   6437     }
   6438   skip_line();
   6439 }
   6440 
   6441 void hyphenation_code()
   6442 {
   6443   tok.skip();
   6444   while (!tok.newline() && !tok.eof()) {
   6445     charinfo *ci = tok.get_char(1);
   6446     if (ci == 0)
   6447       break;
   6448     tok.next();
   6449     tok.skip();
   6450     unsigned char c = tok.ch();
   6451     if (c == 0) {
   6452       error("hyphenation code must be ordinary character");
   6453       break;
   6454     }
   6455     if (csdigit(c)) {
   6456       error("hyphenation code cannot be digit");
   6457       break;
   6458     }
   6459     ci->set_hyphenation_code(c);
   6460     if (ci->get_translation()
   6461 	&& ci->get_translation()->get_translation_input())
   6462       ci->get_translation()->set_hyphenation_code(c);
   6463     tok.next();
   6464     tok.skip();
   6465   }
   6466   skip_line();
   6467 }
   6468 
   6469 void hyphenation_patterns_file_code()
   6470 {
   6471   tok.skip();
   6472   while (!tok.newline() && !tok.eof()) {
   6473     int n1, n2;
   6474     if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
   6475       if (!has_arg()) {
   6476 	error("missing output hyphenation code");
   6477 	break;
   6478       }
   6479       if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
   6480 	hpf_code_table[n1] = n2;
   6481 	tok.skip();
   6482       }
   6483       else {
   6484 	error("output hyphenation code must be integer in the range 0..255");
   6485 	break;
   6486       }
   6487     }
   6488     else {
   6489       error("input hyphenation code must be integer in the range 0..255");
   6490       break;
   6491     }
   6492   }
   6493   skip_line();
   6494 }
   6495 
   6496 charinfo *token::get_char(int required)
   6497 {
   6498   if (type == TOKEN_CHAR)
   6499     return charset_table[c];
   6500   if (type == TOKEN_SPECIAL)
   6501     return get_charinfo(nm);
   6502   if (type == TOKEN_NUMBERED_CHAR)
   6503     return get_charinfo_by_number(val);
   6504   if (type == TOKEN_ESCAPE) {
   6505     if (escape_char != 0)
   6506       return charset_table[escape_char];
   6507     else {
   6508       error("`\\e' used while no current escape character");
   6509       return 0;
   6510     }
   6511   }
   6512   if (required) {
   6513     if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
   6514       warning(WARN_MISSING, "missing normal or special character");
   6515     else
   6516       error("normal or special character expected (got %1)", description());
   6517   }
   6518   return 0;
   6519 }
   6520 
   6521 charinfo *get_optional_char()
   6522 {
   6523   while (tok.space())
   6524     tok.next();
   6525   charinfo *ci = tok.get_char();
   6526   if (!ci)
   6527     check_missing_character();
   6528   else
   6529     tok.next();
   6530   return ci;
   6531 }
   6532 
   6533 void check_missing_character()
   6534 {
   6535   if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
   6536     error("normal or special character expected (got %1): "
   6537 	  "treated as missing",
   6538 	  tok.description());
   6539 }
   6540 
   6541 // this is for \Z
   6542 
   6543 int token::add_to_node_list(node **pp)
   6544 {
   6545   hunits w;
   6546   int s;
   6547   node *n = 0;
   6548   switch (type) {
   6549   case TOKEN_CHAR:
   6550     *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
   6551     break;
   6552   case TOKEN_DUMMY:
   6553     n = new dummy_node;
   6554     break;
   6555   case TOKEN_ESCAPE:
   6556     if (escape_char != 0)
   6557       *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
   6558     break;
   6559   case TOKEN_HYPHEN_INDICATOR:
   6560     *pp = (*pp)->add_discretionary_hyphen();
   6561     break;
   6562   case TOKEN_ITALIC_CORRECTION:
   6563     *pp = (*pp)->add_italic_correction(&w);
   6564     break;
   6565   case TOKEN_LEFT_BRACE:
   6566     break;
   6567   case TOKEN_MARK_INPUT:
   6568     set_number_reg(nm, curenv->get_input_line_position().to_units());
   6569     break;
   6570   case TOKEN_NODE:
   6571     n = nd;
   6572     nd = 0;
   6573     break;
   6574   case TOKEN_NUMBERED_CHAR:
   6575     *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
   6576     break;
   6577   case TOKEN_RIGHT_BRACE:
   6578     break;
   6579   case TOKEN_SPACE:
   6580     n = new hmotion_node(curenv->get_space_width(),
   6581 			 curenv->get_fill_color());
   6582     break;
   6583   case TOKEN_SPECIAL:
   6584     *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
   6585     break;
   6586   case TOKEN_STRETCHABLE_SPACE:
   6587     n = new unbreakable_space_node(curenv->get_space_width(),
   6588 				   curenv->get_fill_color());
   6589     break;
   6590   case TOKEN_UNSTRETCHABLE_SPACE:
   6591     n = new space_char_hmotion_node(curenv->get_space_width(),
   6592 				    curenv->get_fill_color());
   6593     break;
   6594   case TOKEN_TRANSPARENT_DUMMY:
   6595     n = new transparent_dummy_node;
   6596     break;
   6597   case TOKEN_ZERO_WIDTH_BREAK:
   6598     n = new space_node(H0, curenv->get_fill_color());
   6599     n->freeze_space();
   6600     n->is_escape_colon();
   6601     break;
   6602   default:
   6603     return 0;
   6604   }
   6605   if (n) {
   6606     n->next = *pp;
   6607     *pp = n;
   6608   }
   6609   return 1;
   6610 }
   6611 
   6612 void token::process()
   6613 {
   6614   if (possibly_handle_first_page_transition())
   6615     return;
   6616   switch (type) {
   6617   case TOKEN_BACKSPACE:
   6618     curenv->add_node(new hmotion_node(-curenv->get_space_width(),
   6619 				      curenv->get_fill_color()));
   6620     break;
   6621   case TOKEN_CHAR:
   6622     curenv->add_char(charset_table[c]);
   6623     break;
   6624   case TOKEN_DUMMY:
   6625     curenv->add_node(new dummy_node);
   6626     break;
   6627   case TOKEN_EMPTY:
   6628     assert(0);
   6629     break;
   6630   case TOKEN_EOF:
   6631     assert(0);
   6632     break;
   6633   case TOKEN_ESCAPE:
   6634     if (escape_char != 0)
   6635       curenv->add_char(charset_table[escape_char]);
   6636     break;
   6637   case TOKEN_BEGIN_TRAP:
   6638   case TOKEN_END_TRAP:
   6639   case TOKEN_PAGE_EJECTOR:
   6640     // these are all handled in process_input_stack()
   6641     break;
   6642   case TOKEN_HYPHEN_INDICATOR:
   6643     curenv->add_hyphen_indicator();
   6644     break;
   6645   case TOKEN_INTERRUPT:
   6646     curenv->interrupt();
   6647     break;
   6648   case TOKEN_ITALIC_CORRECTION:
   6649     curenv->add_italic_correction();
   6650     break;
   6651   case TOKEN_LEADER:
   6652     curenv->handle_tab(1);
   6653     break;
   6654   case TOKEN_LEFT_BRACE:
   6655     break;
   6656   case TOKEN_MARK_INPUT:
   6657     set_number_reg(nm, curenv->get_input_line_position().to_units());
   6658     break;
   6659   case TOKEN_NEWLINE:
   6660     curenv->newline();
   6661     break;
   6662   case TOKEN_NODE:
   6663     curenv->add_node(nd);
   6664     nd = 0;
   6665     break;
   6666   case TOKEN_NUMBERED_CHAR:
   6667     curenv->add_char(get_charinfo_by_number(val));
   6668     break;
   6669   case TOKEN_REQUEST:
   6670     // handled in process_input_stack()
   6671     break;
   6672   case TOKEN_RIGHT_BRACE:
   6673     break;
   6674   case TOKEN_SPACE:
   6675     curenv->space();
   6676     break;
   6677   case TOKEN_SPECIAL:
   6678     curenv->add_char(get_charinfo(nm));
   6679     break;
   6680   case TOKEN_SPREAD:
   6681     curenv->spread();
   6682     break;
   6683   case TOKEN_STRETCHABLE_SPACE:
   6684     curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
   6685 						curenv->get_fill_color()));
   6686     break;
   6687   case TOKEN_UNSTRETCHABLE_SPACE:
   6688     curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
   6689 						 curenv->get_fill_color()));
   6690     break;
   6691   case TOKEN_TAB:
   6692     curenv->handle_tab(0);
   6693     break;
   6694   case TOKEN_TRANSPARENT:
   6695     break;
   6696   case TOKEN_TRANSPARENT_DUMMY:
   6697     curenv->add_node(new transparent_dummy_node);
   6698     break;
   6699   case TOKEN_ZERO_WIDTH_BREAK:
   6700     {
   6701       node *tmp = new space_node(H0, curenv->get_fill_color());
   6702       tmp->freeze_space();
   6703       tmp->is_escape_colon();
   6704       curenv->add_node(tmp);
   6705       break;
   6706     }
   6707   default:
   6708     assert(0);
   6709   }
   6710 }
   6711 
   6712 class nargs_reg : public reg {
   6713 public:
   6714   const char *get_string();
   6715 };
   6716 
   6717 const char *nargs_reg::get_string()
   6718 {
   6719   return i_to_a(input_stack::nargs());
   6720 }
   6721 
   6722 class lineno_reg : public reg {
   6723 public:
   6724   const char *get_string();
   6725 };
   6726 
   6727 const char *lineno_reg::get_string()
   6728 {
   6729   int line;
   6730   const char *file;
   6731   if (!input_stack::get_location(0, &file, &line))
   6732     line = 0;
   6733   return i_to_a(line);
   6734 }
   6735 
   6736 class writable_lineno_reg : public general_reg {
   6737 public:
   6738   writable_lineno_reg();
   6739   void set_value(units);
   6740   int get_value(units *);
   6741 };
   6742 
   6743 writable_lineno_reg::writable_lineno_reg()
   6744 {
   6745 }
   6746 
   6747 int writable_lineno_reg::get_value(units *res)
   6748 {
   6749   int line;
   6750   const char *file;
   6751   if (!input_stack::get_location(0, &file, &line))
   6752     return 0;
   6753   *res = line;
   6754   return 1;
   6755 }
   6756 
   6757 void writable_lineno_reg::set_value(units n)
   6758 {
   6759   input_stack::set_location(0, n);
   6760 }
   6761 
   6762 class filename_reg : public reg {
   6763 public:
   6764   const char *get_string();
   6765 };
   6766 
   6767 const char *filename_reg::get_string()
   6768 {
   6769   int line;
   6770   const char *file;
   6771   if (input_stack::get_location(0, &file, &line))
   6772     return file;
   6773   else
   6774     return 0;
   6775 }
   6776 
   6777 class constant_reg : public reg {
   6778   const char *s;
   6779 public:
   6780   constant_reg(const char *);
   6781   const char *get_string();
   6782 };
   6783 
   6784 constant_reg::constant_reg(const char *p) : s(p)
   6785 {
   6786 }
   6787 
   6788 const char *constant_reg::get_string()
   6789 {
   6790   return s;
   6791 }
   6792 
   6793 constant_int_reg::constant_int_reg(int *q) : p(q)
   6794 {
   6795 }
   6796 
   6797 const char *constant_int_reg::get_string()
   6798 {
   6799   return i_to_a(*p);
   6800 }
   6801 
   6802 void abort_request()
   6803 {
   6804   int c;
   6805   if (tok.eof())
   6806     c = EOF;
   6807   else if (tok.newline())
   6808     c = '\n';
   6809   else {
   6810     while ((c = get_copy(0)) == ' ')
   6811       ;
   6812   }
   6813   if (c == EOF || c == '\n')
   6814     fputs("User Abort.", stderr);
   6815   else {
   6816     for (; c != '\n' && c != EOF; c = get_copy(0))
   6817       fputs(asciify(c), stderr);
   6818   }
   6819   fputc('\n', stderr);
   6820   cleanup_and_exit(1);
   6821 }
   6822 
   6823 char *read_string()
   6824 {
   6825   int len = 256;
   6826   char *s = new char[len];
   6827   int c;
   6828   while ((c = get_copy(0)) == ' ')
   6829     ;
   6830   int i = 0;
   6831   while (c != '\n' && c != EOF) {
   6832     if (!invalid_input_char(c)) {
   6833       if (i + 2 > len) {
   6834 	char *tem = s;
   6835 	s = new char[len*2];
   6836 	memcpy(s, tem, len);
   6837 	len *= 2;
   6838 	a_delete tem;
   6839       }
   6840       s[i++] = c;
   6841     }
   6842     c = get_copy(0);
   6843   }
   6844   s[i] = '\0';
   6845   tok.next();
   6846   if (i == 0) {
   6847     a_delete s;
   6848     return 0;
   6849   }
   6850   return s;
   6851 }
   6852 
   6853 void pipe_output()
   6854 {
   6855   if (safer_flag) {
   6856     error(".pi request not allowed in safer mode");
   6857     skip_line();
   6858   }
   6859   else {
   6860 #ifdef POPEN_MISSING
   6861     error("pipes not available on this system");
   6862     skip_line();
   6863 #else /* not POPEN_MISSING */
   6864     if (the_output) {
   6865       error("can't pipe: output already started");
   6866       skip_line();
   6867     }
   6868     else {
   6869       char *pc;
   6870       if ((pc = read_string()) == 0)
   6871 	error("can't pipe to empty command");
   6872       if (pipe_command) {
   6873 	char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
   6874 	strcpy(s, pipe_command);
   6875 	strcat(s, "|");
   6876 	strcat(s, pc);
   6877 	a_delete pipe_command;
   6878 	a_delete pc;
   6879 	pipe_command = s;
   6880       }
   6881       else
   6882         pipe_command = pc;
   6883     }
   6884 #endif /* not POPEN_MISSING */
   6885   }
   6886 }
   6887 
   6888 static int system_status;
   6889 
   6890 void system_request()
   6891 {
   6892   if (safer_flag) {
   6893     error(".sy request not allowed in safer mode");
   6894     skip_line();
   6895   }
   6896   else {
   6897     char *command = read_string();
   6898     if (!command)
   6899       error("empty command");
   6900     else {
   6901       system_status = system(command);
   6902       a_delete command;
   6903     }
   6904   }
   6905 }
   6906 
   6907 void copy_file()
   6908 {
   6909   if (curdiv == topdiv && topdiv->before_first_page) {
   6910     handle_initial_request(COPY_FILE_REQUEST);
   6911     return;
   6912   }
   6913   symbol filename = get_long_name(1);
   6914   while (!tok.newline() && !tok.eof())
   6915     tok.next();
   6916   if (break_flag)
   6917     curenv->do_break();
   6918   if (!filename.is_null())
   6919     curdiv->copy_file(filename.contents());
   6920   tok.next();
   6921 }
   6922 
   6923 #ifdef COLUMN
   6924 
   6925 void vjustify()
   6926 {
   6927   if (curdiv == topdiv && topdiv->before_first_page) {
   6928     handle_initial_request(VJUSTIFY_REQUEST);
   6929     return;
   6930   }
   6931   symbol type = get_long_name(1);
   6932   if (!type.is_null())
   6933     curdiv->vjustify(type);
   6934   skip_line();
   6935 }
   6936 
   6937 #endif /* COLUMN */
   6938 
   6939 void transparent_file()
   6940 {
   6941   if (curdiv == topdiv && topdiv->before_first_page) {
   6942     handle_initial_request(TRANSPARENT_FILE_REQUEST);
   6943     return;
   6944   }
   6945   symbol filename = get_long_name(1);
   6946   while (!tok.newline() && !tok.eof())
   6947     tok.next();
   6948   if (break_flag)
   6949     curenv->do_break();
   6950   if (!filename.is_null()) {
   6951     errno = 0;
   6952     FILE *fp = include_search_path.open_file_cautious(filename.contents());
   6953     if (!fp)
   6954       error("can't open `%1': %2", filename.contents(), strerror(errno));
   6955     else {
   6956       int bol = 1;
   6957       for (;;) {
   6958 	int c = getc(fp);
   6959 	if (c == EOF)
   6960 	  break;
   6961 	if (invalid_input_char(c))
   6962 	  warning(WARN_INPUT, "invalid input character code %1", int(c));
   6963 	else {
   6964 	  curdiv->transparent_output(c);
   6965 	  bol = c == '\n';
   6966 	}
   6967       }
   6968       if (!bol)
   6969 	curdiv->transparent_output('\n');
   6970       fclose(fp);
   6971     }
   6972   }
   6973   tok.next();
   6974 }
   6975 
   6976 class page_range {
   6977   int first;
   6978   int last;
   6979 public:
   6980   page_range *next;
   6981   page_range(int, int, page_range *);
   6982   int contains(int n);
   6983 };
   6984 
   6985 page_range::page_range(int i, int j, page_range *p)
   6986 : first(i), last(j), next(p)
   6987 {
   6988 }
   6989 
   6990 int page_range::contains(int n)
   6991 {
   6992   return n >= first && (last <= 0 || n <= last);
   6993 }
   6994 
   6995 page_range *output_page_list = 0;
   6996 
   6997 int in_output_page_list(int n)
   6998 {
   6999   if (!output_page_list)
   7000     return 1;
   7001   for (page_range *p = output_page_list; p; p = p->next)
   7002     if (p->contains(n))
   7003       return 1;
   7004   return 0;
   7005 }
   7006 
   7007 static void parse_output_page_list(char *p)
   7008 {
   7009   for (;;) {
   7010     int i;
   7011     if (*p == '-')
   7012       i = 1;
   7013     else if (csdigit(*p)) {
   7014       i = 0;
   7015       do
   7016 	i = i*10 + *p++ - '0';
   7017       while (csdigit(*p));
   7018     }
   7019     else
   7020       break;
   7021     int j;
   7022     if (*p == '-') {
   7023       p++;
   7024       j = 0;
   7025       if (csdigit(*p)) {
   7026 	do
   7027 	  j = j*10 + *p++ - '0';
   7028 	while (csdigit(*p));
   7029       }
   7030     }
   7031     else
   7032       j = i;
   7033     if (j == 0)
   7034       last_page_number = -1;
   7035     else if (last_page_number >= 0 && j > last_page_number)
   7036       last_page_number = j;
   7037     output_page_list = new page_range(i, j, output_page_list);
   7038     if (*p != ',')
   7039       break;
   7040     ++p;
   7041   }
   7042   if (*p != '\0') {
   7043     error("bad output page list");
   7044     output_page_list = 0;
   7045   }
   7046 }
   7047 
   7048 static FILE *open_mac_file(const char *mac, char **path)
   7049 {
   7050   // Try first FOOBAR.tmac, then tmac.FOOBAR
   7051   char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
   7052   strcpy(s1, mac);
   7053   strcat(s1, MACRO_POSTFIX);
   7054   FILE *fp = mac_path->open_file(s1, path);
   7055   a_delete s1;
   7056   if (!fp) {
   7057     char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
   7058     strcpy(s2, MACRO_PREFIX);
   7059     strcat(s2, mac);
   7060     fp = mac_path->open_file(s2, path);
   7061     a_delete s2;
   7062   }
   7063   return fp;
   7064 }
   7065 
   7066 static void process_macro_file(const char *mac)
   7067 {
   7068   char *path;
   7069   FILE *fp = open_mac_file(mac, &path);
   7070   if (!fp)
   7071     fatal("can't find macro file %1", mac);
   7072   const char *s = symbol(path).contents();
   7073   a_delete path;
   7074   input_stack::push(new file_iterator(fp, s));
   7075   tok.next();
   7076   process_input_stack();
   7077 }
   7078 
   7079 static void process_startup_file(const char *filename)
   7080 {
   7081   char *path;
   7082   search_path *orig_mac_path = mac_path;
   7083   mac_path = &config_macro_path;
   7084   FILE *fp = mac_path->open_file(filename, &path);
   7085   if (fp) {
   7086     input_stack::push(new file_iterator(fp, symbol(path).contents()));
   7087     a_delete path;
   7088     tok.next();
   7089     process_input_stack();
   7090   }
   7091   mac_path = orig_mac_path;
   7092 }
   7093 
   7094 void macro_source()
   7095 {
   7096   symbol nm = get_long_name(1);
   7097   if (nm.is_null())
   7098     skip_line();
   7099   else {
   7100     while (!tok.newline() && !tok.eof())
   7101       tok.next();
   7102     char *path;
   7103     FILE *fp = mac_path->open_file(nm.contents(), &path);
   7104     // .mso doesn't (and cannot) go through open_mac_file, so we
   7105     // need to do it here manually: If we have tmac.FOOBAR, try
   7106     // FOOBAR.tmac and vice versa
   7107     if (!fp) {
   7108       const char *fn = nm.contents();
   7109       if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
   7110 	char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
   7111 	strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
   7112 	strcat(s, MACRO_POSTFIX);
   7113 	fp = mac_path->open_file(s, &path);
   7114 	a_delete s;
   7115       }
   7116       if (!fp) {
   7117 	if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
   7118 			MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
   7119 	  char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
   7120 	  strcpy(s, MACRO_PREFIX);
   7121 	  strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
   7122 	  fp = mac_path->open_file(s, &path);
   7123 	  a_delete s;
   7124 	}
   7125       }
   7126     }
   7127     if (fp) {
   7128       input_stack::push(new file_iterator(fp, symbol(path).contents()));
   7129       a_delete path;
   7130     }
   7131     else
   7132       error("can't find macro file `%1'", nm.contents());
   7133     tok.next();
   7134   }
   7135 }
   7136 
   7137 static void process_input_file(const char *name)
   7138 {
   7139   FILE *fp;
   7140   if (strcmp(name, "-") == 0) {
   7141     clearerr(stdin);
   7142     fp = stdin;
   7143   }
   7144   else {
   7145     errno = 0;
   7146     fp = include_search_path.open_file_cautious(name);
   7147     if (!fp)
   7148       fatal("can't open `%1': %2", name, strerror(errno));
   7149   }
   7150   input_stack::push(new file_iterator(fp, name));
   7151   tok.next();
   7152   process_input_stack();
   7153 }
   7154 
   7155 // make sure the_input is empty before calling this
   7156 
   7157 static int evaluate_expression(const char *expr, units *res)
   7158 {
   7159   input_stack::push(make_temp_iterator(expr));
   7160   tok.next();
   7161   int success = get_number(res, 'u');
   7162   while (input_stack::get(0) != EOF)
   7163     ;
   7164   return success;
   7165 }
   7166 
   7167 static void do_register_assignment(const char *s)
   7168 {
   7169   const char *p = strchr(s, '=');
   7170   if (!p) {
   7171     char buf[2];
   7172     buf[0] = s[0];
   7173     buf[1] = 0;
   7174     units n;
   7175     if (evaluate_expression(s + 1, &n))
   7176       set_number_reg(buf, n);
   7177   }
   7178   else {
   7179     char *buf = new char[p - s + 1];
   7180     memcpy(buf, s, p - s);
   7181     buf[p - s] = 0;
   7182     units n;
   7183     if (evaluate_expression(p + 1, &n))
   7184       set_number_reg(buf, n);
   7185     a_delete buf;
   7186   }
   7187 }
   7188 
   7189 static void set_string(const char *name, const char *value)
   7190 {
   7191   macro *m = new macro;
   7192   for (const char *p = value; *p; p++)
   7193     if (!invalid_input_char((unsigned char)*p))
   7194       m->append(*p);
   7195   request_dictionary.define(name, m);
   7196 }
   7197 
   7198 static void do_string_assignment(const char *s)
   7199 {
   7200   const char *p = strchr(s, '=');
   7201   if (!p) {
   7202     char buf[2];
   7203     buf[0] = s[0];
   7204     buf[1] = 0;
   7205     set_string(buf, s + 1);
   7206   }
   7207   else {
   7208     char *buf = new char[p - s + 1];
   7209     memcpy(buf, s, p - s);
   7210     buf[p - s] = 0;
   7211     set_string(buf, p + 1);
   7212     a_delete buf;
   7213   }
   7214 }
   7215 
   7216 struct string_list {
   7217   const char *s;
   7218   string_list *next;
   7219   string_list(const char *ss) : s(ss), next(0) {}
   7220 };
   7221 
   7222 #if 0
   7223 static void prepend_string(const char *s, string_list **p)
   7224 {
   7225   string_list *l = new string_list(s);
   7226   l->next = *p;
   7227   *p = l;
   7228 }
   7229 #endif
   7230 
   7231 static void add_string(const char *s, string_list **p)
   7232 {
   7233   while (*p)
   7234     p = &((*p)->next);
   7235   *p = new string_list(s);
   7236 }
   7237 
   7238 void usage(FILE *stream, const char *prog)
   7239 {
   7240   fprintf(stream,
   7241 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
   7242 "       -rcn -Tname -Fdir -Idir -Mdir [files...]\n",
   7243 	  prog);
   7244 }
   7245 
   7246 static
   7247 #ifdef LONG_FOR_TIME_T
   7248 long
   7249 #else /* not LONG_FOR_TIME_T */
   7250 time_t
   7251 #endif /* not LONG_FOR_TIME_T */
   7252 timestamp;
   7253 
   7254 int main(int argc, char **argv)
   7255 {
   7256   program_name = argv[0];
   7257   static char stderr_buf[BUFSIZ];
   7258   setbuf(stderr, stderr_buf);
   7259   int c;
   7260   string_list *macros = 0;
   7261   string_list *register_assignments = 0;
   7262   string_list *string_assignments = 0;
   7263   int iflag = 0;
   7264   int tflag = 0;
   7265   int fflag = 0;
   7266   int nflag = 0;
   7267   int no_rc = 0;		// don't process troffrc and troffrc-end
   7268   int next_page_number = 0;	// pacify compiler
   7269   opterr = 0;
   7270   hresolution = vresolution = 1;
   7271   // restore $PATH if called from groff
   7272   char* groff_path = getenv("GROFF_PATH__");
   7273   if (groff_path) {
   7274     string e = "PATH";
   7275     e += '=';
   7276     if (*groff_path)
   7277       e += groff_path;
   7278     e += '\0';
   7279     if (putenv(strsave(e.contents())))
   7280       fatal("putenv failed");
   7281   }
   7282   static const struct option long_options[] = {
   7283     { "help", no_argument, 0, CHAR_MAX + 1 },
   7284     { "timestamp", required_argument, 0, 'Y' },
   7285     { "version", no_argument, 0, 'v' },
   7286     { 0, 0, 0, 0 }
   7287   };
   7288 #if defined(DEBUGGING)
   7289 #define DEBUG_OPTION "D"
   7290 #endif
   7291   while ((c = getopt_long(argc, argv,
   7292 			  "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RUY:"
   7293 			  DEBUG_OPTION, long_options, 0))
   7294 	 != EOF)
   7295     switch(c) {
   7296     case 'v':
   7297       {
   7298 	printf("GNU troff (groff) version %s\n", Version_string);
   7299 	exit(0);
   7300 	break;
   7301       }
   7302     case 'I':
   7303       // Search path for .psbb files
   7304       // and most other non-system input files.
   7305       include_search_path.command_line_dir(optarg);
   7306       break;
   7307     case 'T':
   7308       device = optarg;
   7309       tflag = 1;
   7310       is_html = (strcmp(device, "html") == 0);
   7311       break;
   7312     case 'C':
   7313       compatible_flag = 1;
   7314       // fall through
   7315     case 'c':
   7316       color_flag = 0;
   7317       break;
   7318     case 'M':
   7319       macro_path.command_line_dir(optarg);
   7320       safer_macro_path.command_line_dir(optarg);
   7321       config_macro_path.command_line_dir(optarg);
   7322       break;
   7323     case 'F':
   7324       font::command_line_font_dir(optarg);
   7325       break;
   7326     case 'm':
   7327       add_string(optarg, &macros);
   7328       break;
   7329     case 'E':
   7330       inhibit_errors = 1;
   7331       break;
   7332     case 'R':
   7333       no_rc = 1;
   7334       break;
   7335     case 'w':
   7336       enable_warning(optarg);
   7337       break;
   7338     case 'W':
   7339       disable_warning(optarg);
   7340       break;
   7341     case 'i':
   7342       iflag = 1;
   7343       break;
   7344     case 'b':
   7345       backtrace_flag = 1;
   7346       break;
   7347     case 'a':
   7348       ascii_output_flag = 1;
   7349       break;
   7350     case 'z':
   7351       suppress_output_flag = 1;
   7352       break;
   7353     case 'n':
   7354       if (sscanf(optarg, "%d", &next_page_number) == 1)
   7355 	nflag++;
   7356       else
   7357 	error("bad page number");
   7358       break;
   7359     case 'o':
   7360       parse_output_page_list(optarg);
   7361       break;
   7362     case 'd':
   7363       if (*optarg == '\0')
   7364 	error("`-d' requires non-empty argument");
   7365       else
   7366 	add_string(optarg, &string_assignments);
   7367       break;
   7368     case 'r':
   7369       if (*optarg == '\0')
   7370 	error("`-r' requires non-empty argument");
   7371       else
   7372 	add_string(optarg, &register_assignments);
   7373       break;
   7374     case 'f':
   7375       default_family = symbol(optarg);
   7376       fflag = 1;
   7377       break;
   7378     case 'q':
   7379     case 's':
   7380     case 't':
   7381       // silently ignore these
   7382       break;
   7383     case 'U':
   7384       safer_flag = 0;	// unsafe behaviour
   7385       break;
   7386 #if defined(DEBUGGING)
   7387     case 'D':
   7388       debug_state = 1;
   7389       break;
   7390 #endif
   7391     case CHAR_MAX + 1: // --help
   7392       usage(stdout, argv[0]);
   7393       exit(0);
   7394       break;
   7395     case 'Y': // --timestamp
   7396       timestamp = strtoul(optarg, NULL, 0);
   7397       break;
   7398     case '?':
   7399       usage(stderr, argv[0]);
   7400       exit(1);
   7401       break;		// never reached
   7402     default:
   7403       assert(0);
   7404     }
   7405   if (!safer_flag)
   7406     mac_path = &macro_path;
   7407   set_string(".T", device);
   7408   init_charset_table();
   7409   init_hpf_code_table();
   7410   if (!font::load_desc())
   7411     fatal("sorry, I can't continue");
   7412   units_per_inch = font::res;
   7413   hresolution = font::hor;
   7414   vresolution = font::vert;
   7415   sizescale = font::sizescale;
   7416   tcommand_flag = font::tcommand;
   7417   warn_scale = (double)units_per_inch;
   7418   warn_scaling_indicator = 'i';
   7419   if (!fflag && font::family != 0 && *font::family != '\0')
   7420     default_family = symbol(font::family);
   7421   font_size::init_size_table(font::sizes);
   7422   int i;
   7423   int j = 1;
   7424   if (font::style_table) {
   7425     for (i = 0; font::style_table[i]; i++)
   7426       mount_style(j++, symbol(font::style_table[i]));
   7427   }
   7428   for (i = 0; font::font_name_table[i]; i++, j++)
   7429     // In the DESC file a font name of 0 (zero) means leave this
   7430     // position empty.
   7431     if (strcmp(font::font_name_table[i], "0") != 0)
   7432       mount_font(j, symbol(font::font_name_table[i]));
   7433   curdiv = topdiv = new top_level_diversion;
   7434   if (nflag)
   7435     topdiv->set_next_page_number(next_page_number);
   7436   init_input_requests();
   7437   init_env_requests();
   7438   init_div_requests();
   7439 #ifdef COLUMN
   7440   init_column_requests();
   7441 #endif /* COLUMN */
   7442   init_node_requests();
   7443   number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
   7444   init_registers();
   7445   init_reg_requests();
   7446   init_hyphen_requests();
   7447   init_environments();
   7448   while (string_assignments) {
   7449     do_string_assignment(string_assignments->s);
   7450     string_list *tem = string_assignments;
   7451     string_assignments = string_assignments->next;
   7452     delete tem;
   7453   }
   7454   while (register_assignments) {
   7455     do_register_assignment(register_assignments->s);
   7456     string_list *tem = register_assignments;
   7457     register_assignments = register_assignments->next;
   7458     delete tem;
   7459   }
   7460   if (!no_rc)
   7461     process_startup_file(INITIAL_STARTUP_FILE);
   7462   while (macros) {
   7463     process_macro_file(macros->s);
   7464     string_list *tem = macros;
   7465     macros = macros->next;
   7466     delete tem;
   7467   }
   7468   if (!no_rc)
   7469     process_startup_file(FINAL_STARTUP_FILE);
   7470   for (i = optind; i < argc; i++)
   7471     process_input_file(argv[i]);
   7472   if (optind >= argc || iflag)
   7473     process_input_file("-");
   7474   exit_troff();
   7475   return 0;			// not reached
   7476 }
   7477 
   7478 void warn_request()
   7479 {
   7480   int n;
   7481   if (has_arg() && get_integer(&n)) {
   7482     if (n & ~WARN_TOTAL) {
   7483       warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
   7484       n &= WARN_TOTAL;
   7485     }
   7486     warning_mask = n;
   7487   }
   7488   else
   7489     warning_mask = WARN_TOTAL;
   7490   skip_line();
   7491 }
   7492 
   7493 static void init_registers()
   7494 {
   7495 #ifdef LONG_FOR_TIME_T
   7496   long
   7497 #else /* not LONG_FOR_TIME_T */
   7498   time_t
   7499 #endif /* not LONG_FOR_TIME_T */
   7500     t = timestamp ? timestamp : time(0);
   7501   // Use struct here to work around misfeature in old versions of g++.
   7502   struct tm *tt = localtime(&t);
   7503   set_number_reg("seconds", int(tt->tm_sec));
   7504   set_number_reg("minutes", int(tt->tm_min));
   7505   set_number_reg("hours", int(tt->tm_hour));
   7506   set_number_reg("dw", int(tt->tm_wday + 1));
   7507   set_number_reg("dy", int(tt->tm_mday));
   7508   set_number_reg("mo", int(tt->tm_mon + 1));
   7509   set_number_reg("year", int(1900 + tt->tm_year));
   7510   set_number_reg("yr", int(tt->tm_year));
   7511   set_number_reg("$$", getpid());
   7512   number_reg_dictionary.define(".A",
   7513 			       new constant_reg(ascii_output_flag
   7514 						? "1"
   7515 						: "0"));
   7516 }
   7517 
   7518 /*
   7519  *  registers associated with \O
   7520  */
   7521 
   7522 static int output_reg_minx_contents = -1;
   7523 static int output_reg_miny_contents = -1;
   7524 static int output_reg_maxx_contents = -1;
   7525 static int output_reg_maxy_contents = -1;
   7526 
   7527 void check_output_limits(int x, int y)
   7528 {
   7529   if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
   7530     output_reg_minx_contents = x;
   7531   if (x > output_reg_maxx_contents)
   7532     output_reg_maxx_contents = x;
   7533   if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
   7534     output_reg_miny_contents = y;
   7535   if (y > output_reg_maxy_contents)
   7536     output_reg_maxy_contents = y;
   7537 }
   7538 
   7539 void reset_output_registers()
   7540 {
   7541   output_reg_minx_contents = -1;
   7542   output_reg_miny_contents = -1;
   7543   output_reg_maxx_contents = -1;
   7544   output_reg_maxy_contents = -1;
   7545 }
   7546 
   7547 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
   7548 {
   7549   *minx = output_reg_minx_contents;
   7550   *miny = output_reg_miny_contents;
   7551   *maxx = output_reg_maxx_contents;
   7552   *maxy = output_reg_maxy_contents;
   7553 }
   7554 
   7555 void init_input_requests()
   7556 {
   7557   init_request("ab", abort_request);
   7558   init_request("als", alias_macro);
   7559   init_request("am", append_macro);
   7560   init_request("am1", append_nocomp_macro);
   7561   init_request("ami", append_indirect_macro);
   7562   init_request("ami1", append_indirect_nocomp_macro);
   7563   init_request("as", append_string);
   7564   init_request("as1", append_nocomp_string);
   7565   init_request("asciify", asciify_macro);
   7566   init_request("backtrace", backtrace_request);
   7567   init_request("blm", blank_line_macro);
   7568   init_request("break", while_break_request);
   7569   init_request("cf", copy_file);
   7570   init_request("cflags", char_flags);
   7571   init_request("char", define_character);
   7572   init_request("chop", chop_macro);
   7573   init_request("close", close_request);
   7574   init_request("color", activate_color);
   7575   init_request("composite", composite_request);
   7576   init_request("continue", while_continue_request);
   7577   init_request("cp", compatible);
   7578   init_request("de", define_macro);
   7579   init_request("de1", define_nocomp_macro);
   7580   init_request("defcolor", define_color);
   7581   init_request("dei", define_indirect_macro);
   7582   init_request("dei1", define_indirect_nocomp_macro);
   7583   init_request("do", do_request);
   7584   init_request("ds", define_string);
   7585   init_request("ds1", define_nocomp_string);
   7586   init_request("ec", set_escape_char);
   7587   init_request("ecr", restore_escape_char);
   7588   init_request("ecs", save_escape_char);
   7589   init_request("el", else_request);
   7590   init_request("em", end_macro);
   7591   init_request("eo", escape_off);
   7592   init_request("ex", exit_request);
   7593   init_request("fchar", define_fallback_character);
   7594 #ifdef WIDOW_CONTROL
   7595   init_request("fpl", flush_pending_lines);
   7596 #endif /* WIDOW_CONTROL */
   7597   init_request("hcode", hyphenation_code);
   7598   init_request("hpfcode", hyphenation_patterns_file_code);
   7599   init_request("ie", if_else_request);
   7600   init_request("if", if_request);
   7601   init_request("ig", ignore);
   7602   init_request("length", length_request);
   7603   init_request("lf", line_file);
   7604   init_request("mso", macro_source);
   7605   init_request("nop", nop_request);
   7606   init_request("nroff", nroff_request);
   7607   init_request("nx", next_file);
   7608   init_request("open", open_request);
   7609   init_request("opena", opena_request);
   7610   init_request("output", output_request);
   7611   init_request("pc", set_page_character);
   7612   init_request("pi", pipe_output);
   7613   init_request("pm", print_macros);
   7614   init_request("psbb", ps_bbox_request);
   7615 #ifndef POPEN_MISSING
   7616   init_request("pso", pipe_source);
   7617 #endif /* not POPEN_MISSING */
   7618   init_request("rchar", remove_character);
   7619   init_request("rd", read_request);
   7620   init_request("return", return_macro_request);
   7621   init_request("rm", remove_macro);
   7622   init_request("rn", rename_macro);
   7623   init_request("schar", define_special_character);
   7624   init_request("shift", shift);
   7625   init_request("so", source);
   7626   init_request("spreadwarn", spreadwarn_request);
   7627   init_request("substring", substring_request);
   7628   init_request("sy", system_request);
   7629   init_request("tag", tag);
   7630   init_request("taga", taga);
   7631   init_request("tm", terminal);
   7632   init_request("tm1", terminal1);
   7633   init_request("tmc", terminal_continue);
   7634   init_request("tr", translate);
   7635   init_request("trf", transparent_file);
   7636   init_request("trin", translate_input);
   7637   init_request("trnt", translate_no_transparent);
   7638   init_request("troff", troff_request);
   7639   init_request("unformat", unformat_macro);
   7640 #ifdef COLUMN
   7641   init_request("vj", vjustify);
   7642 #endif /* COLUMN */
   7643   init_request("warn", warn_request);
   7644   init_request("warnscale", warnscale_request);
   7645   init_request("while", while_request);
   7646   init_request("write", write_request);
   7647   init_request("writec", write_request_continue);
   7648   init_request("writem", write_macro_request);
   7649   number_reg_dictionary.define(".$", new nargs_reg);
   7650   number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
   7651   number_reg_dictionary.define(".c", new lineno_reg);
   7652   number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
   7653   number_reg_dictionary.define(".F", new filename_reg);
   7654   number_reg_dictionary.define(".g", new constant_reg("1"));
   7655   number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
   7656   number_reg_dictionary.define(".R", new constant_reg("10000"));
   7657   number_reg_dictionary.define(".U", new constant_int_reg(&safer_flag));
   7658   number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
   7659   number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
   7660   extern const char *major_version;
   7661   number_reg_dictionary.define(".x", new constant_reg(major_version));
   7662   extern const char *revision;
   7663   number_reg_dictionary.define(".Y", new constant_reg(revision));
   7664   extern const char *minor_version;
   7665   number_reg_dictionary.define(".y", new constant_reg(minor_version));
   7666   number_reg_dictionary.define("c.", new writable_lineno_reg);
   7667   number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
   7668   number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
   7669   number_reg_dictionary.define("opmaxx",
   7670 			       new variable_reg(&output_reg_maxx_contents));
   7671   number_reg_dictionary.define("opmaxy",
   7672 			       new variable_reg(&output_reg_maxy_contents));
   7673   number_reg_dictionary.define("opminx",
   7674 			       new variable_reg(&output_reg_minx_contents));
   7675   number_reg_dictionary.define("opminy",
   7676 			       new variable_reg(&output_reg_miny_contents));
   7677   number_reg_dictionary.define("slimit",
   7678 			       new variable_reg(&input_stack::limit));
   7679   number_reg_dictionary.define("systat", new variable_reg(&system_status));
   7680   number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
   7681   number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
   7682 }
   7683 
   7684 object_dictionary request_dictionary(501);
   7685 
   7686 void init_request(const char *s, REQUEST_FUNCP f)
   7687 {
   7688   request_dictionary.define(s, new request(f));
   7689 }
   7690 
   7691 static request_or_macro *lookup_request(symbol nm)
   7692 {
   7693   assert(!nm.is_null());
   7694   request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
   7695   if (p == 0) {
   7696     warning(WARN_MAC, "macro `%1' not defined", nm.contents());
   7697     p = new macro;
   7698     request_dictionary.define(nm, p);
   7699   }
   7700   return p;
   7701 }
   7702 
   7703 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
   7704 {
   7705   // Don't interpret character definitions in compatible mode.
   7706   int old_compatible_flag = compatible_flag;
   7707   compatible_flag = 0;
   7708   int old_escape_char = escape_char;
   7709   escape_char = '\\';
   7710   macro *mac = ci->set_macro(0);
   7711   assert(mac != 0);
   7712   environment *oldenv = curenv;
   7713   environment env(envp);
   7714   curenv = &env;
   7715   curenv->set_composite();
   7716   token old_tok = tok;
   7717   input_stack::add_boundary();
   7718   string_iterator *si =
   7719     new string_iterator(*mac, "composite character", ci->nm);
   7720   input_stack::push(si);
   7721   // we don't use process_input_stack, because we don't want to recognise
   7722   // requests
   7723   for (;;) {
   7724     tok.next();
   7725     if (tok.eof())
   7726       break;
   7727     if (tok.newline()) {
   7728       error("composite character mustn't contain newline");
   7729       while (!tok.eof())
   7730 	tok.next();
   7731       break;
   7732     }
   7733     else
   7734       tok.process();
   7735   }
   7736   node *n = curenv->extract_output_line();
   7737   input_stack::remove_boundary();
   7738   ci->set_macro(mac);
   7739   tok = old_tok;
   7740   curenv = oldenv;
   7741   compatible_flag = old_compatible_flag;
   7742   escape_char = old_escape_char;
   7743   have_input = 0;
   7744   return n;
   7745 }
   7746 
   7747 static node *read_draw_node()
   7748 {
   7749   token start;
   7750   start.next();
   7751   if (!start.delimiter(1)){
   7752     do {
   7753       tok.next();
   7754     } while (tok != start && !tok.newline() && !tok.eof());
   7755   }
   7756   else {
   7757     tok.next();
   7758     if (tok == start)
   7759       error("missing argument");
   7760     else {
   7761       unsigned char type = tok.ch();
   7762       if (type == 'F') {
   7763 	read_color_draw_node(start);
   7764 	return 0;
   7765       }
   7766       tok.next();
   7767       int maxpoints = 10;
   7768       hvpair *point = new hvpair[maxpoints];
   7769       int npoints = 0;
   7770       int no_last_v = 0;
   7771       int err = 0;
   7772       int i;
   7773       for (i = 0; tok != start; i++) {
   7774 	if (i == maxpoints) {
   7775 	  hvpair *oldpoint = point;
   7776 	  point = new hvpair[maxpoints*2];
   7777 	  for (int j = 0; j < maxpoints; j++)
   7778 	    point[j] = oldpoint[j];
   7779 	  maxpoints *= 2;
   7780 	  a_delete oldpoint;
   7781 	}
   7782 	if (!get_hunits(&point[i].h,
   7783 			type == 'f' || type == 't' ? 'u' : 'm')) {
   7784 	  err = 1;
   7785 	  break;
   7786 	}
   7787 	++npoints;
   7788 	tok.skip();
   7789 	point[i].v = V0;
   7790 	if (tok == start) {
   7791 	  no_last_v = 1;
   7792 	  break;
   7793 	}
   7794 	if (!get_vunits(&point[i].v, 'v')) {
   7795 	  err = 1;
   7796 	  break;
   7797 	}
   7798 	tok.skip();
   7799       }
   7800       while (tok != start && !tok.newline() && !tok.eof())
   7801 	tok.next();
   7802       if (!err) {
   7803 	switch (type) {
   7804 	case 'l':
   7805 	  if (npoints != 1 || no_last_v) {
   7806 	    error("two arguments needed for line");
   7807 	    npoints = 1;
   7808 	  }
   7809 	  break;
   7810 	case 'c':
   7811 	  if (npoints != 1 || !no_last_v) {
   7812 	    error("one argument needed for circle");
   7813 	    npoints = 1;
   7814 	    point[0].v = V0;
   7815 	  }
   7816 	  break;
   7817 	case 'e':
   7818 	  if (npoints != 1 || no_last_v) {
   7819 	    error("two arguments needed for ellipse");
   7820 	    npoints = 1;
   7821 	  }
   7822 	  break;
   7823 	case 'a':
   7824 	  if (npoints != 2 || no_last_v) {
   7825 	    error("four arguments needed for arc");
   7826 	    npoints = 2;
   7827 	  }
   7828 	  break;
   7829 	case '~':
   7830 	  if (no_last_v)
   7831 	    error("even number of arguments needed for spline");
   7832 	  break;
   7833 	case 'f':
   7834 	  if (npoints != 1 || !no_last_v) {
   7835 	    error("one argument needed for gray shade");
   7836 	    npoints = 1;
   7837 	    point[0].v = V0;
   7838 	  }
   7839 	default:
   7840 	  // silently pass it through
   7841 	  break;
   7842 	}
   7843 	draw_node *dn = new draw_node(type, point, npoints,
   7844 				      curenv->get_font_size(),
   7845 				      curenv->get_glyph_color(),
   7846 				      curenv->get_fill_color());
   7847 	a_delete point;
   7848 	return dn;
   7849       }
   7850       else {
   7851 	a_delete point;
   7852       }
   7853     }
   7854   }
   7855   return 0;
   7856 }
   7857 
   7858 static void read_color_draw_node(token &start)
   7859 {
   7860   tok.next();
   7861   if (tok == start) {
   7862     error("missing color scheme");
   7863     return;
   7864   }
   7865   unsigned char scheme = tok.ch();
   7866   tok.next();
   7867   color *col = 0;
   7868   char end = start.ch();
   7869   switch (scheme) {
   7870   case 'c':
   7871     col = read_cmy(end);
   7872     break;
   7873   case 'd':
   7874     col = &default_color;
   7875     break;
   7876   case 'g':
   7877     col = read_gray(end);
   7878     break;
   7879   case 'k':
   7880     col = read_cmyk(end);
   7881     break;
   7882   case 'r':
   7883     col = read_rgb(end);
   7884     break;
   7885   }
   7886   if (col)
   7887     curenv->set_fill_color(col);
   7888   while (tok != start) {
   7889     if (tok.newline() || tok.eof()) {
   7890       warning(WARN_DELIM, "missing closing delimiter");
   7891       input_stack::push(make_temp_iterator("\n"));
   7892       break;
   7893     }
   7894     tok.next();
   7895   }
   7896   have_input = 1;
   7897 }
   7898 
   7899 static struct {
   7900   const char *name;
   7901   int mask;
   7902 } warning_table[] = {
   7903   { "char", WARN_CHAR },
   7904   { "range", WARN_RANGE },
   7905   { "break", WARN_BREAK },
   7906   { "delim", WARN_DELIM },
   7907   { "el", WARN_EL },
   7908   { "scale", WARN_SCALE },
   7909   { "number", WARN_NUMBER },
   7910   { "syntax", WARN_SYNTAX },
   7911   { "tab", WARN_TAB },
   7912   { "right-brace", WARN_RIGHT_BRACE },
   7913   { "missing", WARN_MISSING },
   7914   { "input", WARN_INPUT },
   7915   { "escape", WARN_ESCAPE },
   7916   { "space", WARN_SPACE },
   7917   { "font", WARN_FONT },
   7918   { "di", WARN_DI },
   7919   { "mac", WARN_MAC },
   7920   { "reg", WARN_REG },
   7921   { "ig", WARN_IG },
   7922   { "color", WARN_COLOR },
   7923   { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
   7924   { "w", WARN_TOTAL },
   7925   { "default", DEFAULT_WARNING_MASK },
   7926 };
   7927 
   7928 static int lookup_warning(const char *name)
   7929 {
   7930   for (unsigned int i = 0;
   7931        i < sizeof(warning_table)/sizeof(warning_table[0]);
   7932        i++)
   7933     if (strcmp(name, warning_table[i].name) == 0)
   7934       return warning_table[i].mask;
   7935   return 0;
   7936 }
   7937 
   7938 static void enable_warning(const char *name)
   7939 {
   7940   int mask = lookup_warning(name);
   7941   if (mask)
   7942     warning_mask |= mask;
   7943   else
   7944     error("unknown warning `%1'", name);
   7945 }
   7946 
   7947 static void disable_warning(const char *name)
   7948 {
   7949   int mask = lookup_warning(name);
   7950   if (mask)
   7951     warning_mask &= ~mask;
   7952   else
   7953     error("unknown warning `%1'", name);
   7954 }
   7955 
   7956 static void copy_mode_error(const char *format,
   7957 			    const errarg &arg1,
   7958 			    const errarg &arg2,
   7959 			    const errarg &arg3)
   7960 {
   7961   if (ignoring) {
   7962     static const char prefix[] = "(in ignored input) ";
   7963     char *s = new char[sizeof(prefix) + strlen(format)];
   7964     strcpy(s, prefix);
   7965     strcat(s, format);
   7966     warning(WARN_IG, s, arg1, arg2, arg3);
   7967     a_delete s;
   7968   }
   7969   else
   7970     error(format, arg1, arg2, arg3);
   7971 }
   7972 
   7973 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
   7974 
   7975 static void do_error(error_type type,
   7976 		     const char *format,
   7977 		     const errarg &arg1,
   7978 		     const errarg &arg2,
   7979 		     const errarg &arg3)
   7980 {
   7981   const char *filename;
   7982   int lineno;
   7983   if (inhibit_errors && type < FATAL)
   7984     return;
   7985   if (backtrace_flag)
   7986     input_stack::backtrace();
   7987   if (!get_file_line(&filename, &lineno))
   7988     filename = 0;
   7989   if (filename)
   7990     errprint("%1:%2: ", filename, lineno);
   7991   else if (program_name)
   7992     fprintf(stderr, "%s: ", program_name);
   7993   switch (type) {
   7994   case FATAL:
   7995     fputs("fatal error: ", stderr);
   7996     break;
   7997   case ERROR:
   7998     break;
   7999   case WARNING:
   8000     fputs("warning: ", stderr);
   8001     break;
   8002   case OUTPUT_WARNING:
   8003     double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
   8004     fprintf(stderr, "warning [p %d, %.1f%c",
   8005 	    topdiv->get_page_number(), fromtop, warn_scaling_indicator);
   8006     if (topdiv != curdiv) {
   8007       double fromtop1 = curdiv->get_vertical_position().to_units()
   8008 			/ warn_scale;
   8009       fprintf(stderr, ", div `%s', %.1f%c",
   8010 	      curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
   8011     }
   8012     fprintf(stderr, "]: ");
   8013     break;
   8014   }
   8015   errprint(format, arg1, arg2, arg3);
   8016   fputc('\n', stderr);
   8017   fflush(stderr);
   8018   if (type == FATAL)
   8019     cleanup_and_exit(1);
   8020 }
   8021 
   8022 int warning(warning_type t,
   8023 	    const char *format,
   8024 	    const errarg &arg1,
   8025 	    const errarg &arg2,
   8026 	    const errarg &arg3)
   8027 {
   8028   if ((t & warning_mask) != 0) {
   8029     do_error(WARNING, format, arg1, arg2, arg3);
   8030     return 1;
   8031   }
   8032   else
   8033     return 0;
   8034 }
   8035 
   8036 int output_warning(warning_type t,
   8037 		   const char *format,
   8038 		   const errarg &arg1,
   8039 		   const errarg &arg2,
   8040 		   const errarg &arg3)
   8041 {
   8042   if ((t & warning_mask) != 0) {
   8043     do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
   8044     return 1;
   8045   }
   8046   else
   8047     return 0;
   8048 }
   8049 
   8050 void error(const char *format,
   8051 	   const errarg &arg1,
   8052 	   const errarg &arg2,
   8053 	   const errarg &arg3)
   8054 {
   8055   do_error(ERROR, format, arg1, arg2, arg3);
   8056 }
   8057 
   8058 void fatal(const char *format,
   8059 	   const errarg &arg1,
   8060 	   const errarg &arg2,
   8061 	   const errarg &arg3)
   8062 {
   8063   do_error(FATAL, format, arg1, arg2, arg3);
   8064 }
   8065 
   8066 void fatal_with_file_and_line(const char *filename, int lineno,
   8067 			      const char *format,
   8068 			      const errarg &arg1,
   8069 			      const errarg &arg2,
   8070 			      const errarg &arg3)
   8071 {
   8072   fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
   8073   errprint(format, arg1, arg2, arg3);
   8074   fputc('\n', stderr);
   8075   fflush(stderr);
   8076   cleanup_and_exit(1);
   8077 }
   8078 
   8079 void error_with_file_and_line(const char *filename, int lineno,
   8080 			      const char *format,
   8081 			      const errarg &arg1,
   8082 			      const errarg &arg2,
   8083 			      const errarg &arg3)
   8084 {
   8085   fprintf(stderr, "%s:%d: error: ", filename, lineno);
   8086   errprint(format, arg1, arg2, arg3);
   8087   fputc('\n', stderr);
   8088   fflush(stderr);
   8089 }
   8090 
   8091 dictionary charinfo_dictionary(501);
   8092 
   8093 charinfo *get_charinfo(symbol nm)
   8094 {
   8095   void *p = charinfo_dictionary.lookup(nm);
   8096   if (p != 0)
   8097     return (charinfo *)p;
   8098   charinfo *cp = new charinfo(nm);
   8099   (void)charinfo_dictionary.lookup(nm, cp);
   8100   return cp;
   8101 }
   8102 
   8103 int charinfo::next_index = 0;
   8104 
   8105 charinfo::charinfo(symbol s)
   8106 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
   8107   hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
   8108   not_found(0), transparent_translate(1), translate_input(0),
   8109   mode(CHAR_NORMAL), nm(s)
   8110 {
   8111   index = next_index++;
   8112 }
   8113 
   8114 void charinfo::set_hyphenation_code(unsigned char c)
   8115 {
   8116   hyphenation_code = c;
   8117 }
   8118 
   8119 void charinfo::set_translation(charinfo *ci, int tt, int ti)
   8120 {
   8121   translation = ci;
   8122   if (ci && ti) {
   8123     if (hyphenation_code != 0)
   8124       ci->set_hyphenation_code(hyphenation_code);
   8125     if (asciify_code != 0)
   8126       ci->set_asciify_code(asciify_code);
   8127     else if (ascii_code != 0)
   8128       ci->set_asciify_code(ascii_code);
   8129     ci->set_translation_input();
   8130   }
   8131   special_translation = TRANSLATE_NONE;
   8132   transparent_translate = tt;
   8133 }
   8134 
   8135 void charinfo::set_special_translation(int c, int tt)
   8136 {
   8137   special_translation = c;
   8138   translation = 0;
   8139   transparent_translate = tt;
   8140 }
   8141 
   8142 void charinfo::set_ascii_code(unsigned char c)
   8143 {
   8144   ascii_code = c;
   8145 }
   8146 
   8147 void charinfo::set_asciify_code(unsigned char c)
   8148 {
   8149   asciify_code = c;
   8150 }
   8151 
   8152 macro *charinfo::set_macro(macro *m)
   8153 {
   8154   macro *tem = mac;
   8155   mac = m;
   8156   return tem;
   8157 }
   8158 
   8159 macro *charinfo::setx_macro(macro *m, char_mode cm)
   8160 {
   8161   macro *tem = mac;
   8162   mac = m;
   8163   mode = cm;
   8164   return tem;
   8165 }
   8166 
   8167 void charinfo::set_number(int n)
   8168 {
   8169   number = n;
   8170   flags |= NUMBERED;
   8171 }
   8172 
   8173 int charinfo::get_number()
   8174 {
   8175   assert(flags & NUMBERED);
   8176   return number;
   8177 }
   8178 
   8179 symbol UNNAMED_SYMBOL("---");
   8180 
   8181 // For numbered characters not between 0 and 255, we make a symbol out
   8182 // of the number and store them in this dictionary.
   8183 
   8184 dictionary numbered_charinfo_dictionary(11);
   8185 
   8186 charinfo *get_charinfo_by_number(int n)
   8187 {
   8188   static charinfo *number_table[256];
   8189 
   8190   if (n >= 0 && n < 256) {
   8191     charinfo *ci = number_table[n];
   8192     if (!ci) {
   8193       ci = new charinfo(UNNAMED_SYMBOL);
   8194       ci->set_number(n);
   8195       number_table[n] = ci;
   8196     }
   8197     return ci;
   8198   }
   8199   else {
   8200     symbol ns(i_to_a(n));
   8201     charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
   8202     if (!ci) {
   8203       ci = new charinfo(UNNAMED_SYMBOL);
   8204       ci->set_number(n);
   8205       (void)numbered_charinfo_dictionary.lookup(ns, ci);
   8206     }
   8207     return ci;
   8208   }
   8209 }
   8210 
   8211 int font::name_to_index(const char *nm)
   8212 {
   8213   charinfo *ci;
   8214   if (nm[1] == 0)
   8215     ci = charset_table[nm[0] & 0xff];
   8216   else if (nm[0] == '\\' && nm[2] == 0)
   8217     ci = get_charinfo(symbol(nm + 1));
   8218   else
   8219     ci = get_charinfo(symbol(nm));
   8220   if (ci == 0)
   8221     return -1;
   8222   else
   8223     return ci->get_index();
   8224 }
   8225 
   8226 int font::number_to_index(int n)
   8227 {
   8228   return get_charinfo_by_number(n)->get_index();
   8229 }
   8230