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