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