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