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