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