Adding a new option -P and new environment variable GROPS_PROLOGUE
[s-roff.git] / src / roff / troff / input.cc
blob835f526e40e140e51fcaa8103a736ed7039d2c0f
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING. If not, write to the Free Software
19 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21 #include "troff.h"
22 #include "symbol.h"
23 #include "dictionary.h"
24 #include "hvunits.h"
25 #include "env.h"
26 #include "request.h"
27 #include "node.h"
28 #include "reg.h"
29 #include "token.h"
30 #include "div.h"
31 #include "charinfo.h"
32 #include "font.h"
33 #include "searchpath.h"
34 #include "macropath.h"
35 #include "defs.h"
37 // Needed for getpid().
38 #include "posix.h"
40 #include "nonposix.h"
42 #ifdef ISATTY_MISSING
43 #undef isatty
44 #define isatty(n) (1)
45 #else /* not ISATTY_MISSING */
46 #ifndef isatty
47 extern "C" {
48 int isatty(int);
50 #endif /* not isatty */
51 #endif /* not ISATTY_MISSING */
53 #define USAGE_EXIT_CODE 1
54 #define MACRO_PREFIX "tmac."
55 #define INITIAL_STARTUP_FILE "troffrc"
56 #define FINAL_STARTUP_FILE "troffrc-end"
57 #define DEFAULT_INPUT_STACK_LIMIT 1000
59 #ifndef DEFAULT_WARNING_MASK
60 // warnings that are enabled by default
61 #define DEFAULT_WARNING_MASK \
62 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
63 #endif
65 // initial size of buffer for reading names; expanded as necessary
66 #define ABUF_SIZE 16
68 #ifdef COLUMN
69 void init_column_requests();
70 #endif /* COLUMN */
72 static node *read_draw_node();
73 void handle_first_page_transition();
74 static void push_token(const token &);
75 void copy_file();
76 #ifdef COLUMN
77 void vjustify();
78 #endif /* COLUMN */
79 void transparent();
80 void transparent_file();
82 const char *program_name = 0;
83 token tok;
84 int break_flag = 0;
85 static int backtrace_flag = 0;
86 #ifndef POPEN_MISSING
87 char *pipe_command = 0;
88 #endif
89 charinfo *charset_table[256];
91 static int warning_mask = DEFAULT_WARNING_MASK;
92 static int inhibit_errors = 0;
93 static int ignoring = 0;
95 static void enable_warning(const char *);
96 static void disable_warning(const char *);
98 static int escape_char = '\\';
99 static symbol end_macro_name;
100 static symbol blank_line_macro_name;
101 static int compatible_flag = 0;
102 int ascii_output_flag = 0;
103 int suppress_output_flag = 0;
104 int is_html2 = 0;
106 int tcommand_flag = 0;
108 static int get_copy(node**, int = 0);
109 static void copy_mode_error(const char *,
110 const errarg & = empty_errarg,
111 const errarg & = empty_errarg,
112 const errarg & = empty_errarg);
114 static symbol read_escape_name();
115 static void interpolate_string(symbol);
116 static void interpolate_macro(symbol);
117 static void interpolate_number_format(symbol);
118 static void interpolate_environment_variable(symbol);
120 static void interpolate_arg(symbol);
121 static request_or_macro *lookup_request(symbol);
122 static int get_delim_number(units *, int);
123 static int get_delim_number(units *, int, units);
124 static int get_line_arg(units *res, int si, charinfo **cp);
125 static int read_size(int *);
126 static symbol get_delim_name();
127 static void init_registers();
128 static void trapping_blank_line();
130 struct input_iterator;
131 input_iterator *make_temp_iterator(const char *);
132 const char *input_char_description(int);
134 #ifndef IS_EBCDIC_HOST
136 const int ESCAPE_QUESTION = 015;
137 const int BEGIN_TRAP = 016;
138 const int END_TRAP = 017;
139 const int PAGE_EJECTOR = 020;
140 const int ESCAPE_NEWLINE = 021;
141 const int ESCAPE_AMPERSAND = 022;
142 const int ESCAPE_UNDERSCORE = 023;
143 const int ESCAPE_BAR = 024;
144 const int ESCAPE_CIRCUMFLEX = 025;
145 const int ESCAPE_LEFT_BRACE = 026;
146 const int ESCAPE_RIGHT_BRACE = 027;
147 const int ESCAPE_LEFT_QUOTE = 030;
148 const int ESCAPE_RIGHT_QUOTE = 031;
149 const int ESCAPE_HYPHEN = 032;
150 const int ESCAPE_BANG = 033;
151 const int ESCAPE_c = 034;
152 const int ESCAPE_e = 035;
153 const int ESCAPE_PERCENT = 036;
154 const int ESCAPE_SPACE = 037;
156 const int TITLE_REQUEST = 0200;
157 const int COPY_FILE_REQUEST = 0201;
158 const int TRANSPARENT_FILE_REQUEST = 0202;
159 #ifdef COLUMN
160 const int VJUSTIFY_REQUEST = 0203;
161 #endif /* COLUMN */
162 const int ESCAPE_E = 0204;
163 const int LAST_PAGE_EJECTOR = 0205;
164 const int ESCAPE_RIGHT_PARENTHESIS = 0206;
166 #else /* IS_EBCDIC_HOST */
168 const int ESCAPE_QUESTION = 010;
169 const int BEGIN_TRAP = 011;
170 const int END_TRAP = 013;
171 const int PAGE_EJECTOR = 015;
172 const int ESCAPE_NEWLINE = 016;
173 const int ESCAPE_AMPERSAND = 017;
174 const int ESCAPE_UNDERSCORE = 020;
175 const int ESCAPE_BAR = 021;
176 const int ESCAPE_CIRCUMFLEX = 022;
177 const int ESCAPE_LEFT_BRACE = 023;
178 const int ESCAPE_RIGHT_BRACE = 024;
179 const int ESCAPE_LEFT_QUOTE = 027;
180 const int ESCAPE_RIGHT_QUOTE = 030;
181 const int ESCAPE_HYPHEN = 031;
182 const int ESCAPE_BANG = 032;
183 const int ESCAPE_c = 033;
184 const int ESCAPE_e = 034;
185 const int ESCAPE_PERCENT = 035;
186 const int ESCAPE_SPACE = 036;
188 const int TITLE_REQUEST = 060;
189 const int COPY_FILE_REQUEST = 061;
190 const int TRANSPARENT_FILE_REQUEST = 062;
191 #ifdef COLUMN
192 const int VJUSTIFY_REQUEST = 063;
193 #endif /* COLUMN */
194 const int ESCAPE_E = 064;
195 const int LAST_PAGE_EJECTOR = 065;
196 const int ESCAPE_RIGHT_PARENTHESIS = 066;
198 #endif /* IS_EBCDIC_HOST */
201 void set_escape_char()
203 if (has_arg()) {
204 if (tok.ch() == 0) {
205 error("bad escape character");
206 escape_char = '\\';
208 else
209 escape_char = tok.ch();
211 else
212 escape_char = '\\';
213 skip_line();
216 void escape_off()
218 escape_char = 0;
219 skip_line();
222 class input_iterator {
223 public:
224 input_iterator();
225 virtual ~input_iterator();
226 int get(node **);
227 friend class input_stack;
228 protected:
229 const unsigned char *ptr;
230 const unsigned char *eptr;
231 input_iterator *next;
232 private:
233 virtual int fill(node **);
234 virtual int peek();
235 virtual int has_args() { return 0; }
236 virtual int nargs() { return 0; }
237 virtual input_iterator *get_arg(int) { return NULL; }
238 virtual int get_location(int, const char **, int *)
239 { return 0; }
240 virtual void backtrace() {}
241 virtual int set_location(const char *, int)
242 { return 0; }
243 virtual int next_file(FILE *, const char *) { return 0; }
244 virtual void shift(int) {}
245 virtual int is_boundary();
246 virtual int internal_level() { return 0; }
247 virtual int is_file() { return 0; }
250 input_iterator::input_iterator()
251 : ptr(0), eptr(0)
255 input_iterator::~input_iterator()
259 int input_iterator::fill(node **)
261 return EOF;
264 int input_iterator::peek()
266 return EOF;
269 int input_iterator::is_boundary()
271 return 0;
274 inline int input_iterator::get(node **p)
276 return ptr < eptr ? *ptr++ : fill(p);
280 class input_boundary : public input_iterator {
281 public:
282 int is_boundary() { return 1; }
285 class file_iterator : public input_iterator {
286 FILE *fp;
287 int lineno;
288 const char *filename;
289 int popened;
290 int newline_flag;
291 enum { BUF_SIZE = 512 };
292 unsigned char buf[BUF_SIZE];
293 void close();
294 public:
295 file_iterator(FILE *, const char *, int = 0);
296 ~file_iterator();
297 int fill(node **);
298 int peek();
299 int get_location(int, const char **, int *);
300 void backtrace();
301 int set_location(const char *, int);
302 int next_file(FILE *, const char *);
303 int is_file();
306 file_iterator::file_iterator(FILE *f, const char *fn, int po)
307 : fp(f), lineno(1), filename(fn), popened(po), newline_flag(0)
309 if ((font::use_charnames_in_special) && (fn != 0)) {
310 if (!the_output)
311 init_output();
312 the_output->put_filename(fn);
316 file_iterator::~file_iterator()
318 close();
321 void file_iterator::close()
323 if (fp == stdin)
324 clearerr(stdin);
325 #ifndef POPEN_MISSING
326 else if (popened)
327 pclose(fp);
328 #endif /* not POPEN_MISSING */
329 else
330 fclose(fp);
333 int file_iterator::is_file()
335 return 1;
338 int file_iterator::next_file(FILE *f, const char *s)
340 close();
341 filename = s;
342 fp = f;
343 lineno = 1;
344 newline_flag = 0;
345 popened = 0;
346 ptr = 0;
347 eptr = 0;
348 return 1;
351 int file_iterator::fill(node **)
353 if (newline_flag)
354 lineno++;
355 newline_flag = 0;
356 unsigned char *p = buf;
357 ptr = p;
358 unsigned char *e = p + BUF_SIZE;
359 while (p < e) {
360 int c = getc(fp);
361 if (c == EOF)
362 break;
363 if (illegal_input_char(c))
364 warning(WARN_INPUT, "illegal input character code %1", int(c));
365 else {
366 *p++ = c;
367 if (c == '\n') {
368 newline_flag = 1;
369 break;
373 if (p > buf) {
374 eptr = p;
375 return *ptr++;
377 else {
378 eptr = p;
379 return EOF;
383 int file_iterator::peek()
385 int c = getc(fp);
386 while (illegal_input_char(c)) {
387 warning(WARN_INPUT, "illegal input character code %1", int(c));
388 c = getc(fp);
390 if (c != EOF)
391 ungetc(c, fp);
392 return c;
395 int file_iterator::get_location(int /*allow_macro*/,
396 const char **filenamep, int *linenop)
398 *linenop = lineno;
399 if (filename != 0 && strcmp(filename, "-") == 0)
400 *filenamep = "<standard input>";
401 else
402 *filenamep = filename;
403 return 1;
406 void file_iterator::backtrace()
408 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
409 popened ? "process" : "file");
412 int file_iterator::set_location(const char *f, int ln)
414 if (f) {
415 filename = f;
416 if (!the_output)
417 init_output();
418 the_output->put_filename(f);
420 lineno = ln;
421 return 1;
424 input_iterator nil_iterator;
426 class input_stack {
427 public:
428 static int get(node **);
429 static int peek();
430 static void push(input_iterator *);
431 static input_iterator *get_arg(int);
432 static int nargs();
433 static int get_location(int, const char **, int *);
434 static int set_location(const char *, int);
435 static void backtrace();
436 static void backtrace_all();
437 static void next_file(FILE *, const char *);
438 static void end_file();
439 static void shift(int n);
440 static void add_boundary();
441 static void remove_boundary();
442 static int get_level();
443 static void clear();
445 static int limit;
446 private:
447 static input_iterator *top;
448 static int level;
450 static int finish_get(node **);
451 static int finish_peek();
454 input_iterator *input_stack::top = &nil_iterator;
455 int input_stack::level = 0;
456 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
458 inline int input_stack::get_level()
460 return level + top->internal_level();
463 inline int input_stack::get(node **np)
465 return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
468 int input_stack::finish_get(node **np)
470 for (;;) {
471 int c = top->fill(np);
472 if (c != EOF || top->is_boundary())
473 return c;
474 if (top == &nil_iterator)
475 break;
476 input_iterator *tem = top;
477 top = top->next;
478 level--;
479 delete tem;
480 if (top->ptr < top->eptr)
481 return *top->ptr++;
483 assert(level == 0);
484 return EOF;
487 inline int input_stack::peek()
489 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
492 int input_stack::finish_peek()
494 for (;;) {
495 int c = top->peek();
496 if (c != EOF || top->is_boundary())
497 return c;
498 if (top == &nil_iterator)
499 break;
500 input_iterator *tem = top;
501 top = top->next;
502 level--;
503 delete tem;
504 if (top->ptr < top->eptr)
505 return *top->ptr;
507 assert(level == 0);
508 return EOF;
511 void input_stack::add_boundary()
513 push(new input_boundary);
516 void input_stack::remove_boundary()
518 assert(top->is_boundary());
519 input_iterator *temp = top->next;
520 delete top;
521 top = temp;
522 level--;
525 void input_stack::push(input_iterator *in)
527 if (in == 0)
528 return;
529 if (++level > limit && limit > 0)
530 fatal("input stack limit exceeded (probable infinite loop)");
531 in->next = top;
532 top = in;
535 input_iterator *input_stack::get_arg(int i)
537 input_iterator *p;
538 for (p = top; p != NULL; p = p->next)
539 if (p->has_args())
540 return p->get_arg(i);
541 return 0;
544 void input_stack::shift(int n)
546 for (input_iterator *p = top; p; p = p->next)
547 if (p->has_args()) {
548 p->shift(n);
549 return;
553 int input_stack::nargs()
555 for (input_iterator *p =top; p != 0; p = p->next)
556 if (p->has_args())
557 return p->nargs();
558 return 0;
561 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
563 for (input_iterator *p = top; p; p = p->next)
564 if (p->get_location(allow_macro, filenamep, linenop))
565 return 1;
566 return 0;
569 void input_stack::backtrace()
571 const char *f;
572 int n;
573 // only backtrace down to (not including) the topmost file
574 for (input_iterator *p = top;
575 p && !p->get_location(0, &f, &n);
576 p = p->next)
577 p->backtrace();
580 void input_stack::backtrace_all()
582 for (input_iterator *p = top; p; p = p->next)
583 p->backtrace();
586 int input_stack::set_location(const char *filename, int lineno)
588 for (input_iterator *p = top; p; p = p->next)
589 if (p->set_location(filename, lineno))
590 return 1;
591 return 0;
594 void input_stack::next_file(FILE *fp, const char *s)
596 input_iterator **pp;
597 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
598 if ((*pp)->next_file(fp, s))
599 return;
600 if (++level > limit && limit > 0)
601 fatal("input stack limit exceeded");
602 *pp = new file_iterator(fp, s);
603 (*pp)->next = &nil_iterator;
606 void input_stack::end_file()
608 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
609 if ((*pp)->is_file()) {
610 input_iterator *tem = *pp;
611 *pp = (*pp)->next;
612 delete tem;
613 level--;
614 return;
618 void input_stack::clear()
620 int nboundaries = 0;
621 while (top != &nil_iterator) {
622 if (top->is_boundary())
623 nboundaries++;
624 input_iterator *tem = top;
625 top = top->next;
626 level--;
627 delete tem;
629 // Keep while_request happy.
630 for (; nboundaries > 0; --nboundaries)
631 add_boundary();
634 void backtrace_request()
636 input_stack::backtrace_all();
637 fflush(stderr);
638 skip_line();
641 void next_file()
643 symbol nm = get_long_name(0);
644 while (!tok.newline() && !tok.eof())
645 tok.next();
646 if (nm.is_null())
647 input_stack::end_file();
648 else {
649 errno = 0;
650 FILE *fp = fopen(nm.contents(), "r");
651 if (!fp)
652 error("can't open `%1': %2", nm.contents(), strerror(errno));
653 else
654 input_stack::next_file(fp, nm.contents());
656 tok.next();
659 void shift()
661 int n;
662 if (!has_arg() || !get_integer(&n))
663 n = 1;
664 input_stack::shift(n);
665 skip_line();
668 static int get_char_for_escape_name()
670 int c = get_copy(NULL);
671 switch (c) {
672 case EOF:
673 copy_mode_error("end of input in escape name");
674 return '\0';
675 default:
676 if (!illegal_input_char(c))
677 break;
678 // fall through
679 case '\n':
680 if (c == '\n')
681 input_stack::push(make_temp_iterator("\n"));
682 case ' ':
683 case '\t':
684 case '\001':
685 case '\b':
686 copy_mode_error("%1 is not allowed in an escape name",
687 input_char_description(c));
688 return '\0';
690 return c;
693 static symbol read_two_char_escape_name()
695 char buf[3];
696 buf[0] = get_char_for_escape_name();
697 if (buf[0] != '\0') {
698 buf[1] = get_char_for_escape_name();
699 if (buf[1] == '\0')
700 buf[0] = 0;
701 else
702 buf[2] = 0;
704 return symbol(buf);
707 static symbol read_long_escape_name()
709 int start_level = input_stack::get_level();
710 char abuf[ABUF_SIZE];
711 char *buf = abuf;
712 int buf_size = ABUF_SIZE;
713 int i = 0;
714 for (;;) {
715 int c = get_char_for_escape_name();
716 if (c == 0) {
717 if (buf != abuf)
718 a_delete buf;
719 return NULL_SYMBOL;
721 if (i + 2 > buf_size) {
722 if (buf == abuf) {
723 buf = new char [ABUF_SIZE*2];
724 memcpy(buf, abuf, buf_size);
725 buf_size = ABUF_SIZE*2;
727 else {
728 char *old_buf = buf;
729 buf = new char[buf_size*2];
730 memcpy(buf, old_buf, buf_size);
731 buf_size *= 2;
732 a_delete old_buf;
735 if (c == ']' && input_stack::get_level() == start_level)
736 break;
737 buf[i++] = c;
739 buf[i] = 0;
740 if (buf == abuf) {
741 if (i == 0) {
742 copy_mode_error("empty escape name");
743 return NULL_SYMBOL;
745 return symbol(abuf);
747 else {
748 symbol s(buf);
749 a_delete buf;
750 return s;
754 static symbol read_escape_name()
756 int c = get_char_for_escape_name();
757 if (c == 0)
758 return NULL_SYMBOL;
759 if (c == '(')
760 return read_two_char_escape_name();
761 if (c == '[' && !compatible_flag)
762 return read_long_escape_name();
763 char buf[2];
764 buf[0] = c;
765 buf[1] = '\0';
766 return symbol(buf);
769 static symbol read_increment_and_escape_name(int *incp)
771 int c = get_char_for_escape_name();
772 switch (c) {
773 case 0:
774 *incp = 0;
775 return NULL_SYMBOL;
776 case '(':
777 *incp = 0;
778 return read_two_char_escape_name();
779 case '+':
780 *incp = 1;
781 return read_escape_name();
782 case '-':
783 *incp = -1;
784 return read_escape_name();
785 case '[':
786 if (!compatible_flag) {
787 *incp = 0;
788 return read_long_escape_name();
790 break;
792 *incp = 0;
793 char buf[2];
794 buf[0] = c;
795 buf[1] = '\0';
796 return symbol(buf);
799 static int get_copy(node **nd, int defining)
801 for (;;) {
802 int c = input_stack::get(nd);
803 if (c == ESCAPE_NEWLINE) {
804 if (defining)
805 return c;
806 do {
807 c = input_stack::get(nd);
808 } while (c == ESCAPE_NEWLINE);
810 if (c != escape_char || escape_char <= 0)
811 return c;
812 c = input_stack::peek();
813 switch(c) {
814 case 0:
815 return escape_char;
816 case '"':
817 (void)input_stack::get(NULL);
818 while ((c = input_stack::get(NULL)) != '\n' && c != EOF)
820 return c;
821 case '#': // Like \" but newline is ignored.
822 (void)input_stack::get(NULL);
823 while ((c = input_stack::get(NULL)) != '\n')
824 if (c == EOF)
825 return EOF;
826 break;
827 case '$':
829 (void)input_stack::get(NULL);
830 symbol s = read_escape_name();
831 if (!s.is_null())
832 interpolate_arg(s);
833 break;
835 case '*':
837 (void)input_stack::get(NULL);
838 symbol s = read_escape_name();
839 if (!s.is_null())
840 interpolate_string(s);
841 break;
843 case 'a':
844 (void)input_stack::get(NULL);
845 return '\001';
846 case 'e':
847 (void)input_stack::get(NULL);
848 return ESCAPE_e;
849 case 'E':
850 (void)input_stack::get(NULL);
851 return ESCAPE_E;
852 case 'n':
854 (void)input_stack::get(NULL);
855 int inc;
856 symbol s = read_increment_and_escape_name(&inc);
857 if (!s.is_null())
858 interpolate_number_reg(s, inc);
859 break;
861 case 'g':
863 (void)input_stack::get(NULL);
864 symbol s = read_escape_name();
865 if (!s.is_null())
866 interpolate_number_format(s);
867 break;
869 case 't':
870 (void)input_stack::get(NULL);
871 return '\t';
872 case 'V':
874 (void)input_stack::get(NULL);
875 symbol s = read_escape_name();
876 if (!s.is_null())
877 interpolate_environment_variable(s);
878 break;
880 case '\n':
881 (void)input_stack::get(NULL);
882 if (defining)
883 return ESCAPE_NEWLINE;
884 break;
885 case ' ':
886 (void)input_stack::get(NULL);
887 return ESCAPE_SPACE;
888 case '|':
889 (void)input_stack::get(NULL);
890 return ESCAPE_BAR;
891 case '^':
892 (void)input_stack::get(NULL);
893 return ESCAPE_CIRCUMFLEX;
894 case '{':
895 (void)input_stack::get(NULL);
896 return ESCAPE_LEFT_BRACE;
897 case '}':
898 (void)input_stack::get(NULL);
899 return ESCAPE_RIGHT_BRACE;
900 case '`':
901 (void)input_stack::get(NULL);
902 return ESCAPE_LEFT_QUOTE;
903 case '\'':
904 (void)input_stack::get(NULL);
905 return ESCAPE_RIGHT_QUOTE;
906 case '-':
907 (void)input_stack::get(NULL);
908 return ESCAPE_HYPHEN;
909 case '_':
910 (void)input_stack::get(NULL);
911 return ESCAPE_UNDERSCORE;
912 case 'c':
913 (void)input_stack::get(NULL);
914 return ESCAPE_c;
915 case '!':
916 (void)input_stack::get(NULL);
917 return ESCAPE_BANG;
918 case '?':
919 (void)input_stack::get(NULL);
920 return ESCAPE_QUESTION;
921 case '&':
922 (void)input_stack::get(NULL);
923 return ESCAPE_AMPERSAND;
924 case ')':
925 (void)input_stack::get(NULL);
926 return ESCAPE_RIGHT_PARENTHESIS;
927 case '.':
928 (void)input_stack::get(NULL);
929 return c;
930 case '%':
931 (void)input_stack::get(NULL);
932 return ESCAPE_PERCENT;
933 default:
934 if (c == escape_char) {
935 (void)input_stack::get(NULL);
936 return c;
938 else
939 return escape_char;
944 class non_interpreted_char_node : public node {
945 unsigned char c;
946 public:
947 non_interpreted_char_node(unsigned char);
948 node *copy();
949 int interpret(macro *);
950 int same(node *);
951 const char *type();
954 int non_interpreted_char_node::same(node *nd)
956 return c == ((non_interpreted_char_node *)nd)->c;
959 const char *non_interpreted_char_node::type()
961 return "non_interpreted_char_node";
964 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
966 assert(n != 0);
969 node *non_interpreted_char_node::copy()
971 return new non_interpreted_char_node(c);
974 int non_interpreted_char_node::interpret(macro *mac)
976 mac->append(c);
977 return 1;
980 static void do_width();
981 static node *do_non_interpreted();
982 static node *do_special();
983 static void do_register();
985 static node *do_overstrike()
987 token start;
988 overstrike_node *on = new overstrike_node;
989 start.next();
990 tok.next();
991 while (tok != start) {
992 if (tok.newline() || tok.eof()) {
993 warning(WARN_DELIM, "missing closing delimiter");
994 break;
996 charinfo *ci = tok.get_char(1);
997 if (ci) {
998 node *n = curenv->make_char_node(ci);
999 if (n)
1000 on->overstrike(n);
1002 tok.next();
1004 return on;
1007 static node *do_bracket()
1009 token start;
1010 bracket_node *bn = new bracket_node;
1011 start.next();
1012 tok.next();
1013 while (tok != start) {
1014 if (tok.eof()) {
1015 warning(WARN_DELIM, "missing closing delimiter");
1016 break;
1018 if (tok.newline()) {
1019 warning(WARN_DELIM, "missing closing delimiter");
1020 input_stack::push(make_temp_iterator("\n"));
1021 break;
1023 charinfo *ci = tok.get_char(1);
1024 if (ci) {
1025 node *n = curenv->make_char_node(ci);
1026 if (n)
1027 bn->bracket(n);
1029 tok.next();
1031 return bn;
1034 static int do_name_test()
1036 token start;
1037 start.next();
1038 int start_level = input_stack::get_level();
1039 int bad_char = 0;
1040 int some_char = 0;
1041 for (;;) {
1042 tok.next();
1043 if (tok.newline() || tok.eof()) {
1044 warning(WARN_DELIM, "missing closing delimiter");
1045 break;
1047 if (tok == start
1048 && (compatible_flag || input_stack::get_level() == start_level))
1049 break;
1050 if (!tok.ch())
1051 bad_char = 1;
1052 some_char = 1;
1054 return some_char && !bad_char;
1057 #if 0
1058 static node *do_zero_width()
1060 token start;
1061 start.next();
1062 int start_level = input_stack::get_level();
1063 environment env(curenv);
1064 environment *oldenv = curenv;
1065 curenv = &env;
1066 for (;;) {
1067 tok.next();
1068 if (tok.newline() || tok.eof()) {
1069 error("missing closing delimiter");
1070 break;
1072 if (tok == start
1073 && (compatible_flag || input_stack::get_level() == start_level))
1074 break;
1075 tok.process();
1077 curenv = oldenv;
1078 node *rev = env.extract_output_line();
1079 node *n = 0;
1080 while (rev) {
1081 node *tem = rev;
1082 rev = rev->next;
1083 tem->next = n;
1084 n = tem;
1086 return new zero_width_node(n);
1089 #else
1091 // It's undesirable for \Z to change environments, because then
1092 // \n(.w won't work as expected.
1094 static node *do_zero_width()
1096 node *rev = new dummy_node;
1097 token start;
1098 start.next();
1099 int start_level = input_stack::get_level();
1100 for (;;) {
1101 tok.next();
1102 if (tok.newline() || tok.eof()) {
1103 warning(WARN_DELIM, "missing closing delimiter");
1104 break;
1106 if (tok == start
1107 && (compatible_flag || input_stack::get_level() == start_level))
1108 break;
1109 if (!tok.add_to_node_list(&rev))
1110 error("illegal token in argument to \\Z");
1112 node *n = 0;
1113 while (rev) {
1114 node *tem = rev;
1115 rev = rev->next;
1116 tem->next = n;
1117 n = tem;
1119 return new zero_width_node(n);
1122 #endif
1124 token_node *node::get_token_node()
1126 return 0;
1129 class token_node : public node {
1130 public:
1131 token tk;
1132 token_node(const token &t);
1133 node *copy();
1134 token_node *get_token_node();
1135 int same(node *);
1136 const char *type();
1139 token_node::token_node(const token &t) : tk(t)
1143 node *token_node::copy()
1145 return new token_node(tk);
1148 token_node *token_node::get_token_node()
1150 return this;
1153 int token_node::same(node *nd)
1155 return tk == ((token_node *)nd)->tk;
1158 const char *token_node::type()
1160 return "token_node";
1163 token::token() : nd(0), type(TOKEN_EMPTY)
1167 token::~token()
1169 delete nd;
1172 token::token(const token &t)
1173 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1175 // Use two statements to work around bug in SGI C++.
1176 node *tem = t.nd;
1177 nd = tem ? tem->copy() : 0;
1180 void token::operator=(const token &t)
1182 delete nd;
1183 nm = t.nm;
1184 // Use two statements to work around bug in SGI C++.
1185 node *tem = t.nd;
1186 nd = tem ? tem->copy() : 0;
1187 c = t.c;
1188 val = t.val;
1189 dim = t.dim;
1190 type = t.type;
1193 void token::skip()
1195 while (space())
1196 next();
1199 int has_arg()
1201 while (tok.space())
1202 tok.next();
1203 return !tok.newline();
1206 void token::make_space()
1208 type = TOKEN_SPACE;
1211 void token::make_newline()
1213 type = TOKEN_NEWLINE;
1216 void token::next()
1218 if (nd) {
1219 delete nd;
1220 nd = 0;
1222 units x;
1223 for (;;) {
1224 node *n;
1225 int cc = input_stack::get(&n);
1226 if (cc != escape_char || escape_char == 0) {
1227 handle_normal_char:
1228 switch(cc) {
1229 case EOF:
1230 type = TOKEN_EOF;
1231 return;
1232 case TRANSPARENT_FILE_REQUEST:
1233 case TITLE_REQUEST:
1234 case COPY_FILE_REQUEST:
1235 #ifdef COLUMN
1236 case VJUSTIFY_REQUEST:
1237 #endif /* COLUMN */
1238 type = TOKEN_REQUEST;
1239 c = cc;
1240 return;
1241 case BEGIN_TRAP:
1242 type = TOKEN_BEGIN_TRAP;
1243 return;
1244 case END_TRAP:
1245 type = TOKEN_END_TRAP;
1246 return;
1247 case LAST_PAGE_EJECTOR:
1248 seen_last_page_ejector = 1;
1249 // fall through
1250 case PAGE_EJECTOR:
1251 type = TOKEN_PAGE_EJECTOR;
1252 return;
1253 case ESCAPE_PERCENT:
1254 ESCAPE_PERCENT:
1255 type = TOKEN_HYPHEN_INDICATOR;
1256 return;
1257 case ESCAPE_SPACE:
1258 ESCAPE_SPACE:
1259 type = TOKEN_NODE;
1260 nd = new space_char_hmotion_node(curenv->get_space_width());
1261 return;
1262 case ESCAPE_e:
1263 ESCAPE_e:
1264 type = TOKEN_ESCAPE;
1265 return;
1266 case ESCAPE_E:
1267 goto handle_escape_char;
1268 case ESCAPE_BAR:
1269 ESCAPE_BAR:
1270 type = TOKEN_NODE;
1271 nd = new hmotion_node(curenv->get_narrow_space_width());
1272 return;
1273 case ESCAPE_CIRCUMFLEX:
1274 ESCAPE_CIRCUMFLEX:
1275 type = TOKEN_NODE;
1276 nd = new hmotion_node(curenv->get_half_narrow_space_width());
1277 return;
1278 case ESCAPE_NEWLINE:
1279 break;
1280 case ESCAPE_LEFT_BRACE:
1281 ESCAPE_LEFT_BRACE:
1282 type = TOKEN_LEFT_BRACE;
1283 return;
1284 case ESCAPE_RIGHT_BRACE:
1285 ESCAPE_RIGHT_BRACE:
1286 type = TOKEN_RIGHT_BRACE;
1287 return;
1288 case ESCAPE_LEFT_QUOTE:
1289 ESCAPE_LEFT_QUOTE:
1290 type = TOKEN_SPECIAL;
1291 nm = symbol("ga");
1292 return;
1293 case ESCAPE_RIGHT_QUOTE:
1294 ESCAPE_RIGHT_QUOTE:
1295 type = TOKEN_SPECIAL;
1296 nm = symbol("aa");
1297 return;
1298 case ESCAPE_HYPHEN:
1299 ESCAPE_HYPHEN:
1300 type = TOKEN_SPECIAL;
1301 nm = symbol("-");
1302 return;
1303 case ESCAPE_UNDERSCORE:
1304 ESCAPE_UNDERSCORE:
1305 type = TOKEN_SPECIAL;
1306 nm = symbol("ul");
1307 return;
1308 case ESCAPE_c:
1309 ESCAPE_c:
1310 type = TOKEN_INTERRUPT;
1311 return;
1312 case ESCAPE_BANG:
1313 ESCAPE_BANG:
1314 type = TOKEN_TRANSPARENT;
1315 return;
1316 case ESCAPE_QUESTION:
1317 ESCAPE_QUESTION:
1318 nd = do_non_interpreted();
1319 if (nd) {
1320 type = TOKEN_NODE;
1321 return;
1323 break;
1324 case ESCAPE_AMPERSAND:
1325 ESCAPE_AMPERSAND:
1326 type = TOKEN_DUMMY;
1327 return;
1328 case ESCAPE_RIGHT_PARENTHESIS:
1329 ESCAPE_RIGHT_PARENTHESIS:
1330 type = TOKEN_NODE;
1331 nd = new transparent_dummy_node;
1332 return;
1333 case '\b':
1334 type = TOKEN_BACKSPACE;
1335 return;
1336 case ' ':
1337 type = TOKEN_SPACE;
1338 return;
1339 case '\t':
1340 type = TOKEN_TAB;
1341 return;
1342 case '\n':
1343 type = TOKEN_NEWLINE;
1344 return;
1345 case '\001':
1346 type = TOKEN_LEADER;
1347 return;
1348 case 0:
1350 assert(n != 0);
1351 token_node *tn = n->get_token_node();
1352 if (tn) {
1353 *this = tn->tk;
1354 delete tn;
1356 else {
1357 nd = n;
1358 type = TOKEN_NODE;
1361 return;
1362 default:
1363 type = TOKEN_CHAR;
1364 c = cc;
1365 return;
1368 else {
1369 handle_escape_char:
1370 cc = input_stack::get(NULL);
1371 switch(cc) {
1372 case '(':
1373 nm = read_two_char_escape_name();
1374 type = TOKEN_SPECIAL;
1375 return;
1376 case EOF:
1377 type = TOKEN_EOF;
1378 error("end of input after escape character");
1379 return;
1380 case '`':
1381 goto ESCAPE_LEFT_QUOTE;
1382 case '\'':
1383 goto ESCAPE_RIGHT_QUOTE;
1384 case '-':
1385 goto ESCAPE_HYPHEN;
1386 case '_':
1387 goto ESCAPE_UNDERSCORE;
1388 case '%':
1389 goto ESCAPE_PERCENT;
1390 case ' ':
1391 goto ESCAPE_SPACE;
1392 case '0':
1393 nd = new hmotion_node(curenv->get_digit_width());
1394 type = TOKEN_NODE;
1395 return;
1396 case '|':
1397 goto ESCAPE_BAR;
1398 case '^':
1399 goto ESCAPE_CIRCUMFLEX;
1400 case '/':
1401 type = TOKEN_ITALIC_CORRECTION;
1402 return;
1403 case ',':
1404 type = TOKEN_NODE;
1405 nd = new left_italic_corrected_node;
1406 return;
1407 case '&':
1408 goto ESCAPE_AMPERSAND;
1409 case ')':
1410 goto ESCAPE_RIGHT_PARENTHESIS;
1411 case '!':
1412 goto ESCAPE_BANG;
1413 case '?':
1414 goto ESCAPE_QUESTION;
1415 case '~':
1416 nd = new unbreakable_space_node(curenv->get_space_width());
1417 type = TOKEN_NODE;
1418 return;
1419 case '"':
1420 while ((cc = input_stack::get(NULL)) != '\n' && cc != EOF)
1422 if (cc == '\n')
1423 type = TOKEN_NEWLINE;
1424 else
1425 type = TOKEN_EOF;
1426 return;
1427 case '#': // Like \" but newline is ignored.
1428 while ((cc = input_stack::get(NULL)) != '\n')
1429 if (cc == EOF) {
1430 type = TOKEN_EOF;
1431 return;
1433 break;
1434 case '$':
1436 symbol nm = read_escape_name();
1437 if (!nm.is_null())
1438 interpolate_arg(nm);
1439 break;
1441 case '*':
1443 symbol nm = read_escape_name();
1444 if (!nm.is_null())
1445 interpolate_string(nm);
1446 break;
1448 case 'a':
1449 nd = new non_interpreted_char_node('\001');
1450 type = TOKEN_NODE;
1451 return;
1452 case 'A':
1453 c = '0' + do_name_test();
1454 type = TOKEN_CHAR;
1455 return;
1456 case 'b':
1457 nd = do_bracket();
1458 type = TOKEN_NODE;
1459 return;
1460 case 'c':
1461 goto ESCAPE_c;
1462 case 'C':
1463 nm = get_delim_name();
1464 if (nm.is_null())
1465 break;
1466 type = TOKEN_SPECIAL;
1467 return;
1468 case 'd':
1469 type = TOKEN_NODE;
1470 nd = new vmotion_node(curenv->get_size()/2);
1471 return;
1472 case 'D':
1473 nd = read_draw_node();
1474 if (!nd)
1475 break;
1476 type = TOKEN_NODE;
1477 return;
1478 case 'e':
1479 goto ESCAPE_e;
1480 case 'E':
1481 goto handle_escape_char;
1482 case 'f':
1484 symbol s = read_escape_name();
1485 if (s.is_null())
1486 break;
1487 const char *p;
1488 for (p = s.contents(); *p != '\0'; p++)
1489 if (!csdigit(*p))
1490 break;
1491 if (*p)
1492 curenv->set_font(s);
1493 else
1494 curenv->set_font(atoi(s.contents()));
1495 break;
1497 case 'g':
1499 symbol s = read_escape_name();
1500 if (!s.is_null())
1501 interpolate_number_format(s);
1502 break;
1504 case 'h':
1505 if (!get_delim_number(&x, 'm'))
1506 break;
1507 type = TOKEN_NODE;
1508 nd = new hmotion_node(x);
1509 return;
1510 case 'H':
1511 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1512 curenv->set_char_height(x);
1513 break;
1514 case 'k':
1515 nm = read_escape_name();
1516 if (nm.is_null())
1517 break;
1518 type = TOKEN_MARK_INPUT;
1519 return;
1520 case 'l':
1521 case 'L':
1523 charinfo *s = 0;
1524 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1525 break;
1526 if (s == 0)
1527 s = get_charinfo(cc == 'l' ? "ru" : "br");
1528 type = TOKEN_NODE;
1529 node *n = curenv->make_char_node(s);
1530 if (cc == 'l')
1531 nd = new hline_node(x, n);
1532 else
1533 nd = new vline_node(x, n);
1534 return;
1536 case 'n':
1538 int inc;
1539 symbol nm = read_increment_and_escape_name(&inc);
1540 if (!nm.is_null())
1541 interpolate_number_reg(nm, inc);
1542 break;
1544 case 'N':
1545 if (!get_delim_number(&val, 0))
1546 break;
1547 type = TOKEN_NUMBERED_CHAR;
1548 return;
1549 case 'o':
1550 nd = do_overstrike();
1551 type = TOKEN_NODE;
1552 return;
1553 case 'p':
1554 type = TOKEN_SPREAD;
1555 return;
1556 case 'r':
1557 type = TOKEN_NODE;
1558 nd = new vmotion_node(-curenv->get_size());
1559 return;
1560 case 'R':
1561 do_register();
1562 break;
1563 case 's':
1564 if (read_size(&x))
1565 curenv->set_size(x);
1566 break;
1567 case 'S':
1568 if (get_delim_number(&x, 0))
1569 curenv->set_char_slant(x);
1570 break;
1571 case 't':
1572 type = TOKEN_NODE;
1573 nd = new non_interpreted_char_node('\t');
1574 return;
1575 case 'u':
1576 type = TOKEN_NODE;
1577 nd = new vmotion_node(-curenv->get_size()/2);
1578 return;
1579 case 'v':
1580 if (!get_delim_number(&x, 'v'))
1581 break;
1582 type = TOKEN_NODE;
1583 nd = new vmotion_node(x);
1584 return;
1585 case 'V':
1587 symbol nm = read_escape_name();
1588 if (!nm.is_null())
1589 interpolate_environment_variable(nm);
1590 break;
1592 case 'w':
1593 do_width();
1594 break;
1595 case 'x':
1596 if (!get_delim_number(&x, 'v'))
1597 break;
1598 type = TOKEN_NODE;
1599 nd = new extra_size_node(x);
1600 return;
1601 case 'X':
1602 nd = do_special();
1603 if (!nd)
1604 break;
1605 type = TOKEN_NODE;
1606 return;
1607 case 'Y':
1609 symbol s = read_escape_name();
1610 if (s.is_null())
1611 break;
1612 request_or_macro *p = lookup_request(s);
1613 macro *m = p->to_macro();
1614 if (!m) {
1615 error("can't transparently throughput a request");
1616 break;
1618 nd = new special_node(*m);
1619 type = TOKEN_NODE;
1620 return;
1622 case 'z':
1624 next();
1625 if (type == TOKEN_NODE)
1626 nd = new zero_width_node(nd);
1627 else {
1628 charinfo *ci = get_char(1);
1629 if (ci == 0)
1630 break;
1631 node *gn = curenv->make_char_node(ci);
1632 if (gn == 0)
1633 break;
1634 nd = new zero_width_node(gn);
1635 type = TOKEN_NODE;
1637 return;
1639 case 'Z':
1640 nd = do_zero_width();
1641 if (nd == 0)
1642 break;
1643 type = TOKEN_NODE;
1644 return;
1645 case '{':
1646 goto ESCAPE_LEFT_BRACE;
1647 case '}':
1648 goto ESCAPE_RIGHT_BRACE;
1649 case '\n':
1650 break;
1651 case '[':
1652 if (!compatible_flag) {
1653 nm = read_long_escape_name();
1654 if (nm.is_null())
1655 break;
1656 type = TOKEN_SPECIAL;
1657 return;
1659 goto handle_normal_char;
1660 default:
1661 if (cc != escape_char && cc != '.')
1662 warning(WARN_ESCAPE, "escape character ignored before %1",
1663 input_char_description(cc));
1664 goto handle_normal_char;
1670 int token::operator==(const token &t)
1672 if (type != t.type)
1673 return 0;
1674 switch(type) {
1675 case TOKEN_CHAR:
1676 return c == t.c;
1677 case TOKEN_SPECIAL:
1678 return nm == t.nm;
1679 case TOKEN_NUMBERED_CHAR:
1680 return val == t.val;
1681 default:
1682 return 1;
1686 int token::operator!=(const token &t)
1688 return !(*this == t);
1691 // is token a suitable delimiter (like ')?
1693 int token::delimiter(int err)
1695 switch(type) {
1696 case TOKEN_CHAR:
1697 switch(c) {
1698 case '0':
1699 case '1':
1700 case '2':
1701 case '3':
1702 case '4':
1703 case '5':
1704 case '6':
1705 case '7':
1706 case '8':
1707 case '9':
1708 case '+':
1709 case '-':
1710 case '/':
1711 case '*':
1712 case '%':
1713 case '<':
1714 case '>':
1715 case '=':
1716 case '&':
1717 case ':':
1718 case '(':
1719 case ')':
1720 case '.':
1721 if (err)
1722 error("cannot use character `%1' as a starting delimiter", char(c));
1723 return 0;
1724 default:
1725 return 1;
1727 case TOKEN_NODE:
1728 case TOKEN_SPACE:
1729 case TOKEN_TAB:
1730 case TOKEN_NEWLINE:
1731 if (err)
1732 error("cannot use %1 as a starting delimiter", description());
1733 return 0;
1734 default:
1735 return 1;
1739 const char *token::description()
1741 static char buf[4];
1742 switch (type) {
1743 case TOKEN_BACKSPACE:
1744 return "a backspace character";
1745 case TOKEN_CHAR:
1746 buf[0] = '`';
1747 buf[1] = c;
1748 buf[2] = '\'';
1749 buf[3] = '\0';
1750 return buf;
1751 case TOKEN_DUMMY:
1752 return "`\\&'";
1753 case TOKEN_ESCAPE:
1754 return "`\\e'";
1755 case TOKEN_HYPHEN_INDICATOR:
1756 return "`\\%'";
1757 case TOKEN_INTERRUPT:
1758 return "`\\c'";
1759 case TOKEN_ITALIC_CORRECTION:
1760 return "`\\/'";
1761 case TOKEN_LEADER:
1762 return "a leader character";
1763 case TOKEN_LEFT_BRACE:
1764 return "`\\{'";
1765 case TOKEN_MARK_INPUT:
1766 return "`\\k'";
1767 case TOKEN_NEWLINE:
1768 return "newline";
1769 case TOKEN_NODE:
1770 return "a node";
1771 case TOKEN_NUMBERED_CHAR:
1772 return "`\\N'";
1773 case TOKEN_RIGHT_BRACE:
1774 return "`\\}'";
1775 case TOKEN_SPACE:
1776 return "a space";
1777 case TOKEN_SPECIAL:
1778 return "a special character";
1779 case TOKEN_SPREAD:
1780 return "`\\p'";
1781 case TOKEN_TAB:
1782 return "a tab character";
1783 case TOKEN_TRANSPARENT:
1784 return "`\\!'";
1785 case TOKEN_EOF:
1786 return "end of input";
1787 default:
1788 break;
1790 return "a magic token";
1793 void skip_line()
1795 while (!tok.newline())
1796 if (tok.eof())
1797 return;
1798 else
1799 tok.next();
1800 tok.next();
1803 void compatible()
1805 int n;
1806 if (has_arg() && get_integer(&n))
1807 compatible_flag = n != 0;
1808 else
1809 compatible_flag = 1;
1810 skip_line();
1813 static void empty_name_warning(int required)
1815 if (tok.newline() || tok.eof()) {
1816 if (required)
1817 warning(WARN_MISSING, "missing name");
1819 else if (tok.right_brace() || tok.tab()) {
1820 const char *start = tok.description();
1821 do {
1822 tok.next();
1823 } while (tok.space() || tok.right_brace() || tok.tab());
1824 if (!tok.newline() && !tok.eof())
1825 error("%1 is not allowed before an argument", start);
1826 else if (required)
1827 warning(WARN_MISSING, "missing name");
1829 else if (required)
1830 error("name expected (got %1)", tok.description());
1831 else
1832 error("name expected (got %1): treated as missing", tok.description());
1835 static void non_empty_name_warning()
1837 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
1838 && !tok.right_brace()
1839 // We don't want to give a warning for .el\{
1840 && !tok.left_brace())
1841 error("%1 is not allowed in a name", tok.description());
1844 symbol get_name(int required)
1846 if (compatible_flag) {
1847 char buf[3];
1848 tok.skip();
1849 if ((buf[0] = tok.ch()) != 0) {
1850 tok.next();
1851 if ((buf[1] = tok.ch()) != 0) {
1852 buf[2] = 0;
1853 tok.make_space();
1855 else
1856 non_empty_name_warning();
1857 return symbol(buf);
1859 else {
1860 empty_name_warning(required);
1861 return NULL_SYMBOL;
1864 else
1865 return get_long_name(required);
1868 symbol get_long_name(int required)
1870 while (tok.space())
1871 tok.next();
1872 char abuf[ABUF_SIZE];
1873 char *buf = abuf;
1874 int buf_size = ABUF_SIZE;
1875 int i = 0;
1876 for (;;) {
1877 if (i + 1 > buf_size) {
1878 if (buf == abuf) {
1879 buf = new char [ABUF_SIZE*2];
1880 memcpy(buf, abuf, buf_size);
1881 buf_size = ABUF_SIZE*2;
1883 else {
1884 char *old_buf = buf;
1885 buf = new char[buf_size*2];
1886 memcpy(buf, old_buf, buf_size);
1887 buf_size *= 2;
1888 a_delete old_buf;
1891 if ((buf[i] = tok.ch()) == 0)
1892 break;
1893 i++;
1894 tok.next();
1896 if (i == 0) {
1897 empty_name_warning(required);
1898 return NULL_SYMBOL;
1900 non_empty_name_warning();
1901 if (buf == abuf)
1902 return symbol(buf);
1903 else {
1904 symbol s(buf);
1905 a_delete buf;
1906 return s;
1910 void exit_troff()
1912 exit_started = 1;
1913 topdiv->set_last_page();
1914 if (!end_macro_name.is_null()) {
1915 spring_trap(end_macro_name);
1916 tok.next();
1917 process_input_stack();
1919 curenv->final_break();
1920 tok.next();
1921 process_input_stack();
1922 end_diversions();
1923 done_end_macro = 1;
1924 topdiv->set_ejecting();
1925 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
1926 input_stack::push(make_temp_iterator((char *)buf));
1927 topdiv->space(topdiv->get_page_length(), 1);
1928 tok.next();
1929 process_input_stack();
1930 seen_last_page_ejector = 1; // should be set already
1931 topdiv->set_ejecting();
1932 push_page_ejector();
1933 topdiv->space(topdiv->get_page_length(), 1);
1934 tok.next();
1935 process_input_stack();
1936 // This will only happen if a trap-invoked macro starts a diversion,
1937 // or if vertical position traps have been disabled.
1938 cleanup_and_exit(0);
1941 // This implements .ex. The input stack must be cleared before calling
1942 // exit_troff().
1944 void exit_request()
1946 input_stack::clear();
1947 if (exit_started)
1948 tok.next();
1949 else
1950 exit_troff();
1953 void end_macro()
1955 end_macro_name = get_name();
1956 skip_line();
1959 void blank_line_macro()
1961 blank_line_macro_name = get_name();
1962 skip_line();
1965 static void trapping_blank_line()
1967 if (!blank_line_macro_name.is_null())
1968 spring_trap(blank_line_macro_name);
1969 else
1970 blank_line();
1973 void do_request()
1975 int saved_compatible_flag = compatible_flag;
1976 compatible_flag = 0;
1977 symbol nm = get_name();
1978 if (nm.is_null())
1979 skip_line();
1980 else
1981 interpolate_macro(nm);
1982 compatible_flag = saved_compatible_flag;
1985 inline int possibly_handle_first_page_transition()
1987 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
1988 handle_first_page_transition();
1989 return 1;
1991 else
1992 return 0;
1995 static int transparent_translate(int cc)
1997 if (!illegal_input_char(cc)) {
1998 charinfo *ci = charset_table[cc];
1999 switch (ci->get_special_translation(1)) {
2000 case charinfo::TRANSLATE_SPACE:
2001 return ' ';
2002 case charinfo::TRANSLATE_DUMMY:
2003 return ESCAPE_AMPERSAND;
2004 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2005 return ESCAPE_PERCENT;
2007 // This is really ugly.
2008 ci = ci->get_translation(1);
2009 if (ci) {
2010 int c = ci->get_ascii_code();
2011 if (c != '\0')
2012 return c;
2013 error("can't translate %1 to special character `%2'"
2014 " in transparent throughput",
2015 input_char_description(cc),
2016 ci->nm.contents());
2019 return cc;
2022 class int_stack {
2023 struct int_stack_element {
2024 int n;
2025 int_stack_element *next;
2026 } *top;
2027 public:
2028 int_stack();
2029 ~int_stack();
2030 void push(int);
2031 int is_empty();
2032 int pop();
2035 int_stack::int_stack()
2037 top = 0;
2040 int_stack::~int_stack()
2042 while (top != 0) {
2043 int_stack_element *temp = top;
2044 top = top->next;
2045 delete temp;
2050 int int_stack::is_empty()
2052 return top == 0;
2055 void int_stack::push(int n)
2057 int_stack_element *p = new int_stack_element;
2058 p->next = top;
2059 p->n = n;
2060 top = p;
2064 int int_stack::pop()
2066 assert(top != 0);
2067 int_stack_element *p = top;
2068 top = top->next;
2069 int n = p->n;
2070 delete p;
2071 return n;
2074 int node::reread(int *)
2076 return 0;
2079 int diverted_space_node::reread(int *bolp)
2081 if (curenv->get_fill())
2082 trapping_blank_line();
2083 else
2084 curdiv->space(n);
2085 *bolp = 1;
2086 return 1;
2089 int diverted_copy_file_node::reread(int *bolp)
2091 curdiv->copy_file(filename.contents());
2092 *bolp = 1;
2093 return 1;
2096 void process_input_stack()
2098 int_stack trap_bol_stack;
2099 int bol = 1;
2100 for (;;) {
2101 int suppress_next = 0;
2102 switch (tok.type) {
2103 case token::TOKEN_CHAR:
2105 unsigned char ch = tok.c;
2106 if (bol &&
2107 (ch == curenv->control_char
2108 || ch == curenv->no_break_control_char)) {
2109 break_flag = ch == curenv->control_char;
2110 // skip tabs as well as spaces here
2111 do {
2112 tok.next();
2113 } while (tok.white_space());
2114 symbol nm = get_name();
2115 if (nm.is_null())
2116 skip_line();
2117 else
2118 interpolate_macro(nm);
2119 suppress_next = 1;
2121 else {
2122 if (possibly_handle_first_page_transition())
2124 else {
2125 for (;;) {
2126 curenv->add_char(charset_table[ch]);
2127 tok.next();
2128 if (tok.type != token::TOKEN_CHAR)
2129 break;
2130 ch = tok.c;
2132 suppress_next = 1;
2133 bol = 0;
2136 break;
2138 case token::TOKEN_TRANSPARENT:
2140 if (bol) {
2141 if (possibly_handle_first_page_transition())
2143 else {
2144 int cc;
2145 do {
2146 node *n;
2147 cc = get_copy(&n);
2148 if (cc != EOF)
2149 if (cc != '\0')
2150 curdiv->transparent_output(transparent_translate(cc));
2151 else
2152 curdiv->transparent_output(n);
2153 } while (cc != '\n' && cc != EOF);
2154 if (cc == EOF)
2155 curdiv->transparent_output('\n');
2158 break;
2160 case token::TOKEN_NEWLINE:
2162 if (bol && !curenv->get_prev_line_interrupted())
2163 trapping_blank_line();
2164 else {
2165 curenv->newline();
2166 bol = 1;
2168 break;
2170 case token::TOKEN_REQUEST:
2172 int request_code = tok.c;
2173 tok.next();
2174 switch (request_code) {
2175 case TITLE_REQUEST:
2176 title();
2177 break;
2178 case COPY_FILE_REQUEST:
2179 copy_file();
2180 break;
2181 case TRANSPARENT_FILE_REQUEST:
2182 transparent_file();
2183 break;
2184 #ifdef COLUMN
2185 case VJUSTIFY_REQUEST:
2186 vjustify();
2187 break;
2188 #endif /* COLUMN */
2189 default:
2190 assert(0);
2191 break;
2193 suppress_next = 1;
2194 break;
2196 case token::TOKEN_SPACE:
2198 if (possibly_handle_first_page_transition())
2200 else if (bol && !curenv->get_prev_line_interrupted()) {
2201 int nspaces = 0;
2202 do {
2203 nspaces += tok.nspaces();
2204 tok.next();
2205 } while (tok.space());
2206 if (tok.newline())
2207 trapping_blank_line();
2208 else {
2209 push_token(tok);
2210 curenv->do_break();
2211 curenv->add_node(new hmotion_node(curenv->get_space_width()*nspaces));
2212 bol = 0;
2215 else {
2216 curenv->space();
2217 bol = 0;
2219 break;
2221 case token::TOKEN_EOF:
2222 return;
2223 case token::TOKEN_NODE:
2225 if (possibly_handle_first_page_transition())
2227 else if (tok.nd->reread(&bol)) {
2228 delete tok.nd;
2229 tok.nd = 0;
2231 else {
2232 curenv->add_node(tok.nd);
2233 tok.nd = 0;
2234 bol = 0;
2236 break;
2238 case token::TOKEN_PAGE_EJECTOR:
2240 continue_page_eject();
2241 // I think we just want to preserve bol.
2242 // bol = 1;
2243 break;
2245 case token::TOKEN_BEGIN_TRAP:
2247 trap_bol_stack.push(bol);
2248 bol = 1;
2249 break;
2251 case token::TOKEN_END_TRAP:
2253 if (trap_bol_stack.is_empty())
2254 error("spurious end trap token detected!");
2255 else
2256 bol = trap_bol_stack.pop();
2258 /* I'm not totally happy about this. But I can't think of any other
2259 way to do it. Doing an output_pending_lines() whenever a
2260 TOKEN_END_TRAP is detected doesn't work: for example,
2262 .wh -1i x
2263 .de x
2266 .wh -.5i y
2267 .de y
2268 .tl ''-%-''
2271 .ll .5i
2272 .sp |\n(.pu-1i-.5v
2273 a\%very\%very\%long\%word
2275 will print all but the first lines from the word immediately
2276 after the footer, rather than on the next page. */
2278 if (trap_bol_stack.is_empty())
2279 curenv->output_pending_lines();
2280 break;
2282 default:
2284 bol = 0;
2285 tok.process();
2286 break;
2289 if (!suppress_next)
2290 tok.next();
2291 trap_sprung_flag = 0;
2295 #ifdef WIDOW_CONTROL
2297 void flush_pending_lines()
2299 while (!tok.newline() && !tok.eof())
2300 tok.next();
2301 curenv->output_pending_lines();
2302 tok.next();
2305 #endif /* WIDOW_CONTROL */
2307 request_or_macro::request_or_macro()
2311 macro *request_or_macro::to_macro()
2313 return 0;
2316 request::request(REQUEST_FUNCP pp) : p(pp)
2320 void request::invoke(symbol)
2322 (*p)();
2325 struct char_block {
2326 enum { SIZE = 128 };
2327 unsigned char s[SIZE];
2328 char_block *next;
2329 char_block();
2332 char_block::char_block()
2333 : next(0)
2337 class char_list {
2338 public:
2339 char_list();
2340 ~char_list();
2341 void append(unsigned char);
2342 int length();
2343 private:
2344 unsigned char *ptr;
2345 int len;
2346 char_block *head;
2347 char_block *tail;
2348 friend class macro_header;
2349 friend class string_iterator;
2352 char_list::char_list()
2353 : ptr(0), len(0), head(0), tail(0)
2357 char_list::~char_list()
2359 while (head != 0) {
2360 char_block *tem = head;
2361 head = head->next;
2362 delete tem;
2366 int char_list::length()
2368 return len;
2371 void char_list::append(unsigned char c)
2373 if (tail == 0) {
2374 head = tail = new char_block;
2375 ptr = tail->s;
2377 else {
2378 if (ptr >= tail->s + char_block::SIZE) {
2379 tail->next = new char_block;
2380 tail = tail->next;
2381 ptr = tail->s;
2384 *ptr++ = c;
2385 len++;
2388 class node_list {
2389 node *head;
2390 node *tail;
2391 public:
2392 node_list();
2393 ~node_list();
2394 void append(node *);
2395 int length();
2396 node *extract();
2398 friend class macro_header;
2399 friend class string_iterator;
2402 void node_list::append(node *n)
2404 if (head == 0) {
2405 n->next = 0;
2406 head = tail = n;
2408 else {
2409 n->next = 0;
2410 tail = tail->next = n;
2414 int node_list::length()
2416 int total = 0;
2417 for (node *n = head; n != 0; n = n->next)
2418 ++total;
2419 return total;
2422 node_list::node_list()
2424 head = tail = 0;
2427 node *node_list::extract()
2429 node *temp = head;
2430 head = tail = 0;
2431 return temp;
2435 node_list::~node_list()
2437 delete_node_list(head);
2440 struct macro_header {
2441 public:
2442 int count;
2443 char_list cl;
2444 node_list nl;
2445 macro_header() { count = 1; }
2446 macro_header *copy(int);
2450 macro::~macro()
2452 if (p != 0 && --(p->count) <= 0)
2453 delete p;
2456 macro::macro()
2458 if (!input_stack::get_location(1, &filename, &lineno)) {
2459 filename = 0;
2460 lineno = 0;
2462 length = 0;
2463 p = 0;
2466 macro::macro(const macro &m)
2467 : p(m.p), filename(m.filename), lineno(m.lineno), length(m.length)
2469 if (p != 0)
2470 p->count++;
2473 macro &macro::operator=(const macro &m)
2475 // don't assign object
2476 if (m.p != 0)
2477 m.p->count++;
2478 if (p != 0 && --(p->count) <= 0)
2479 delete p;
2480 p = m.p;
2481 filename = m.filename;
2482 lineno = m.lineno;
2483 length = m.length;
2484 return *this;
2487 void macro::append(unsigned char c)
2489 assert(c != 0);
2490 if (p == 0)
2491 p = new macro_header;
2492 if (p->cl.length() != length) {
2493 macro_header *tem = p->copy(length);
2494 if (--(p->count) <= 0)
2495 delete p;
2496 p = tem;
2498 p->cl.append(c);
2499 ++length;
2502 void macro::append(node *n)
2504 assert(n != 0);
2505 if (p == 0)
2506 p = new macro_header;
2507 if (p->cl.length() != length) {
2508 macro_header *tem = p->copy(length);
2509 if (--(p->count) <= 0)
2510 delete p;
2511 p = tem;
2513 p->cl.append(0);
2514 p->nl.append(n);
2515 ++length;
2518 void macro::print_size()
2520 errprint("%1", length);
2523 // make a copy of the first n bytes
2525 macro_header *macro_header::copy(int n)
2527 macro_header *p = new macro_header;
2528 char_block *bp = cl.head;
2529 unsigned char *ptr = bp->s;
2530 node *nd = nl.head;
2531 while (--n >= 0) {
2532 if (ptr >= bp->s + char_block::SIZE) {
2533 bp = bp->next;
2534 ptr = bp->s;
2536 int c = *ptr++;
2537 p->cl.append(c);
2538 if (c == 0) {
2539 p->nl.append(nd->copy());
2540 nd = nd->next;
2543 return p;
2546 void print_macros()
2548 object_dictionary_iterator iter(request_dictionary);
2549 request_or_macro *rm;
2550 symbol s;
2551 while (iter.get(&s, (object **)&rm)) {
2552 assert(!s.is_null());
2553 macro *m = rm->to_macro();
2554 if (m) {
2555 errprint("%1\t", s.contents());
2556 m->print_size();
2557 errprint("\n");
2560 fflush(stderr);
2561 skip_line();
2564 class string_iterator : public input_iterator {
2565 macro mac;
2566 const char *how_invoked;
2567 int newline_flag;
2568 int lineno;
2569 char_block *bp;
2570 int count; // of characters remaining
2571 node *nd;
2572 protected:
2573 symbol nm;
2574 string_iterator();
2575 public:
2576 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
2577 int fill(node **);
2578 int peek();
2579 int get_location(int, const char **, int *);
2580 void backtrace();
2583 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
2584 : mac(m), how_invoked(p), newline_flag(0), lineno(1), nm(s)
2586 count = mac.length;
2587 if (count != 0) {
2588 bp = mac.p->cl.head;
2589 nd = mac.p->nl.head;
2590 ptr = eptr = bp->s;
2592 else {
2593 bp = 0;
2594 nd = 0;
2595 ptr = eptr = 0;
2599 string_iterator::string_iterator()
2601 bp = 0;
2602 nd = 0;
2603 ptr = eptr = 0;
2604 newline_flag = 0;
2605 how_invoked = 0;
2606 lineno = 1;
2607 count = 0;
2610 int string_iterator::fill(node **np)
2612 if (newline_flag)
2613 lineno++;
2614 newline_flag = 0;
2615 if (count <= 0)
2616 return EOF;
2617 const unsigned char *p = eptr;
2618 if (p >= bp->s + char_block::SIZE) {
2619 bp = bp->next;
2620 p = bp->s;
2622 if (*p == '\0') {
2623 if (np)
2624 *np = nd->copy();
2625 nd = nd->next;
2626 eptr = ptr = p + 1;
2627 count--;
2628 return 0;
2630 const unsigned char *e = bp->s + char_block::SIZE;
2631 if (e - p > count)
2632 e = p + count;
2633 ptr = p;
2634 while (p < e) {
2635 unsigned char c = *p;
2636 if (c == '\n' || c == ESCAPE_NEWLINE) {
2637 newline_flag = 1;
2638 p++;
2639 break;
2641 if (c == '\0')
2642 break;
2643 p++;
2645 eptr = p;
2646 count -= p - ptr;
2647 return *ptr++;
2650 int string_iterator::peek()
2652 if (count <= 0)
2653 return EOF;
2654 const unsigned char *p = eptr;
2655 if (count <= 0)
2656 return EOF;
2657 if (p >= bp->s + char_block::SIZE) {
2658 p = bp->next->s;
2660 return *p;
2663 int string_iterator::get_location(int allow_macro,
2664 const char **filep, int *linep)
2666 if (!allow_macro)
2667 return 0;
2668 if (mac.filename == 0)
2669 return 0;
2670 *filep = mac.filename;
2671 *linep = mac.lineno + lineno - 1;
2672 return 1;
2675 void string_iterator::backtrace()
2677 if (mac.filename) {
2678 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
2679 if (how_invoked) {
2680 if (!nm.is_null())
2681 errprint(": %1 `%2'\n", how_invoked, nm.contents());
2682 else
2683 errprint(": %1\n", how_invoked);
2685 else
2686 errprint("\n");
2690 class temp_iterator : public input_iterator {
2691 unsigned char *base;
2692 temp_iterator(const char *, int len);
2693 public:
2694 ~temp_iterator();
2695 friend input_iterator *make_temp_iterator(const char *);
2698 #ifdef __GNUG__
2699 inline
2700 #endif
2701 temp_iterator::temp_iterator(const char *s, int len)
2703 base = new unsigned char[len];
2704 memcpy(base, s, len);
2705 ptr = base;
2706 eptr = base + len;
2709 temp_iterator::~temp_iterator()
2711 a_delete base;
2714 class small_temp_iterator : public input_iterator {
2715 private:
2716 small_temp_iterator(const char *, int);
2717 ~small_temp_iterator();
2718 enum { BLOCK = 16 };
2719 static small_temp_iterator *free_list;
2720 void *operator new(size_t);
2721 void operator delete(void *);
2722 enum { SIZE = 12 };
2723 unsigned char buf[SIZE];
2724 friend input_iterator *make_temp_iterator(const char *);
2727 small_temp_iterator *small_temp_iterator::free_list = 0;
2729 void *small_temp_iterator::operator new(size_t n)
2731 assert(n == sizeof(small_temp_iterator));
2732 if (!free_list) {
2733 free_list = (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
2734 for (int i = 0; i < BLOCK - 1; i++)
2735 free_list[i].next = free_list + i + 1;
2736 free_list[BLOCK-1].next = 0;
2738 small_temp_iterator *p = free_list;
2739 free_list = (small_temp_iterator *)(free_list->next);
2740 p->next = 0;
2741 return p;
2744 #ifdef __GNUG__
2745 inline
2746 #endif
2747 void small_temp_iterator::operator delete(void *p)
2749 if (p) {
2750 ((small_temp_iterator *)p)->next = free_list;
2751 free_list = (small_temp_iterator *)p;
2755 small_temp_iterator::~small_temp_iterator()
2760 #ifdef __GNUG__
2761 inline
2762 #endif
2763 small_temp_iterator::small_temp_iterator(const char *s, int len)
2765 for (int i = 0; i < len; i++)
2766 buf[i] = s[i];
2767 ptr = buf;
2768 eptr = buf + len;
2771 input_iterator *make_temp_iterator(const char *s)
2773 if (s == 0)
2774 return new small_temp_iterator(s, 0);
2775 else {
2776 int n = strlen(s);
2777 if (n <= small_temp_iterator::SIZE)
2778 return new small_temp_iterator(s, n);
2779 else
2780 return new temp_iterator(s, n);
2784 // this is used when macros are interpolated using the .macro_name notation
2786 struct arg_list {
2787 macro mac;
2788 arg_list *next;
2789 arg_list(const macro &);
2790 ~arg_list();
2793 arg_list::arg_list(const macro &m) : mac(m), next(0)
2797 arg_list::~arg_list()
2801 class macro_iterator : public string_iterator {
2802 arg_list *args;
2803 int argc;
2804 public:
2805 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
2806 macro_iterator();
2807 ~macro_iterator();
2808 int has_args() { return 1; }
2809 input_iterator *get_arg(int i);
2810 int nargs() { return argc; }
2811 void add_arg(const macro &m);
2812 void shift(int n);
2815 input_iterator *macro_iterator::get_arg(int i)
2817 if (i == 0)
2818 return make_temp_iterator(nm.contents());
2819 if (i > 0 && i <= argc) {
2820 arg_list *p = args;
2821 for (int j = 1; j < i; j++) {
2822 assert(p != 0);
2823 p = p->next;
2825 return new string_iterator(p->mac);
2827 else
2828 return 0;
2831 void macro_iterator::add_arg(const macro &m)
2833 arg_list **p;
2834 for (p = &args; *p; p = &((*p)->next))
2836 *p = new arg_list(m);
2837 ++argc;
2840 void macro_iterator::shift(int n)
2842 while (n > 0 && argc > 0) {
2843 arg_list *tem = args;
2844 args = args->next;
2845 delete tem;
2846 --argc;
2847 --n;
2851 // This gets used by eg .if '\?xxx\?''.
2853 int operator==(const macro &m1, const macro &m2)
2855 if (m1.length != m2.length)
2856 return 0;
2857 string_iterator iter1(m1);
2858 string_iterator iter2(m2);
2859 int n = m1.length;
2860 while (--n >= 0) {
2861 node *nd1 = 0;
2862 int c1 = iter1.get(&nd1);
2863 assert(c1 != EOF);
2864 node *nd2 = 0;
2865 int c2 = iter2.get(&nd2);
2866 assert(c2 != EOF);
2867 if (c1 != c2) {
2868 if (c1 == 0)
2869 delete nd1;
2870 else if (c2 == 0)
2871 delete nd2;
2872 return 0;
2874 if (c1 == 0) {
2875 assert(nd1 != 0);
2876 assert(nd2 != 0);
2877 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
2878 delete nd1;
2879 delete nd2;
2880 if (!are_same)
2881 return 0;
2884 return 1;
2887 static void interpolate_macro(symbol nm)
2889 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
2890 if (p == 0) {
2891 int warned = 0;
2892 const char *s = nm.contents();
2893 if (strlen(s) > 2) {
2894 request_or_macro *r;
2895 char buf[3];
2896 buf[0] = s[0];
2897 buf[1] = s[1];
2898 buf[2] = '\0';
2899 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
2900 if (r) {
2901 macro *m = r->to_macro();
2902 if (!m || !m->empty())
2903 warned = warning(WARN_SPACE,
2904 "`%1' not defined (probable missing space after `%2')",
2905 nm.contents(), buf);
2908 if (!warned) {
2909 warning(WARN_MAC, "`%1' not defined", nm.contents());
2910 p = new macro;
2911 request_dictionary.define(nm, p);
2914 if (p)
2915 p->invoke(nm);
2916 else {
2917 skip_line();
2918 return;
2922 static void decode_args(macro_iterator *mi)
2924 if (!tok.newline() && !tok.eof()) {
2925 node *n;
2926 int c = get_copy(&n);
2927 for (;;) {
2928 while (c == ' ')
2929 c = get_copy(&n);
2930 if (c == '\n' || c == EOF)
2931 break;
2932 macro arg;
2933 int quote_input_level = 0;
2934 int done_tab_warning = 0;
2935 if (c == '\"') {
2936 quote_input_level = input_stack::get_level();
2937 c = get_copy(&n);
2939 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
2940 if (quote_input_level > 0 && c == '\"'
2941 && (compatible_flag
2942 || input_stack::get_level() == quote_input_level)) {
2943 c = get_copy(&n);
2944 if (c == '"') {
2945 arg.append(c);
2946 c = get_copy(&n);
2948 else
2949 break;
2951 else {
2952 if (c == 0)
2953 arg.append(n);
2954 else {
2955 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
2956 warning(WARN_TAB, "tab character in unquoted macro argument");
2957 done_tab_warning = 1;
2959 arg.append(c);
2961 c = get_copy(&n);
2964 mi->add_arg(arg);
2969 void macro::invoke(symbol nm)
2971 macro_iterator *mi = new macro_iterator(nm, *this);
2972 decode_args(mi);
2973 input_stack::push(mi);
2974 tok.next();
2977 macro *macro::to_macro()
2979 return this;
2982 int macro::empty()
2984 return length == 0;
2987 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
2988 : string_iterator(m, how_invoked, s), args(0), argc(0)
2992 macro_iterator::macro_iterator() : args(0), argc(0)
2996 macro_iterator::~macro_iterator()
2998 while (args != 0) {
2999 arg_list *tem = args;
3000 args = args->next;
3001 delete tem;
3005 int trap_sprung_flag = 0;
3006 int postpone_traps_flag = 0;
3007 symbol postponed_trap;
3009 void spring_trap(symbol nm)
3011 assert(!nm.is_null());
3012 trap_sprung_flag = 1;
3013 if (postpone_traps_flag) {
3014 postponed_trap = nm;
3015 return;
3017 static char buf[2] = { BEGIN_TRAP, 0 };
3018 static char buf2[2] = { END_TRAP, '\0' };
3019 input_stack::push(make_temp_iterator(buf2));
3020 request_or_macro *p = lookup_request(nm);
3021 macro *m = p->to_macro();
3022 if (m)
3023 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
3024 else
3025 error("you can't invoke a request with a trap");
3026 input_stack::push(make_temp_iterator(buf));
3029 void postpone_traps()
3031 postpone_traps_flag = 1;
3034 int unpostpone_traps()
3036 postpone_traps_flag = 0;
3037 if (!postponed_trap.is_null()) {
3038 spring_trap(postponed_trap);
3039 postponed_trap = NULL_SYMBOL;
3040 return 1;
3042 else
3043 return 0;
3046 void read_request()
3048 macro_iterator *mi = new macro_iterator;
3049 int reading_from_terminal = isatty(fileno(stdin));
3050 int had_prompt = 0;
3051 if (!tok.newline() && !tok.eof()) {
3052 int c = get_copy(NULL);
3053 while (c == ' ')
3054 c = get_copy(NULL);
3055 while (c != EOF && c != '\n' && c != ' ') {
3056 if (!illegal_input_char(c)) {
3057 if (reading_from_terminal)
3058 fputc(c, stderr);
3059 had_prompt = 1;
3061 c = get_copy(NULL);
3063 if (c == ' ') {
3064 tok.make_space();
3065 decode_args(mi);
3068 if (reading_from_terminal) {
3069 fputc(had_prompt ? ':' : '\a', stderr);
3070 fflush(stderr);
3072 input_stack::push(mi);
3073 macro mac;
3074 int nl = 0;
3075 int c;
3076 while ((c = getchar()) != EOF) {
3077 if (illegal_input_char(c))
3078 warning(WARN_INPUT, "illegal input character code %1", int(c));
3079 else {
3080 if (c == '\n') {
3081 if (nl)
3082 break;
3083 else
3084 nl = 1;
3086 else
3087 nl = 0;
3088 mac.append(c);
3091 if (reading_from_terminal)
3092 clearerr(stdin);
3093 input_stack::push(new string_iterator(mac));
3094 tok.next();
3097 void do_define_string(int append)
3099 symbol nm;
3100 node *n;
3101 int c;
3102 nm = get_name(1);
3103 if (nm.is_null()) {
3104 skip_line();
3105 return;
3107 if (tok.newline())
3108 c = '\n';
3109 else if (tok.tab())
3110 c = '\t';
3111 else if (!tok.space()) {
3112 error("bad string definition");
3113 skip_line();
3114 return;
3116 else
3117 c = get_copy(&n);
3118 while (c == ' ')
3119 c = get_copy(&n);
3120 if (c == '"')
3121 c = get_copy(&n);
3122 macro mac;
3123 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3124 macro *mm = rm ? rm->to_macro() : 0;
3125 if (append && mm)
3126 mac = *mm;
3127 while (c != '\n' && c != EOF) {
3128 if (c == 0)
3129 mac.append(n);
3130 else
3131 mac.append((unsigned char)c);
3132 c = get_copy(&n);
3134 if (!mm) {
3135 mm = new macro;
3136 request_dictionary.define(nm, mm);
3138 *mm = mac;
3139 tok.next();
3142 void define_string()
3144 do_define_string(0);
3147 void append_string()
3149 do_define_string(1);
3152 void define_character()
3154 node *n;
3155 int c;
3156 tok.skip();
3157 charinfo *ci = tok.get_char(1);
3158 if (ci == 0) {
3159 skip_line();
3160 return;
3162 tok.next();
3163 if (tok.newline())
3164 c = '\n';
3165 else if (tok.tab())
3166 c = '\t';
3167 else if (!tok.space()) {
3168 error("bad character definition");
3169 skip_line();
3170 return;
3172 else
3173 c = get_copy(&n);
3174 while (c == ' ' || c == '\t')
3175 c = get_copy(&n);
3176 if (c == '"')
3177 c = get_copy(&n);
3178 macro *m = new macro;
3179 while (c != '\n' && c != EOF) {
3180 if (c == 0)
3181 m->append(n);
3182 else
3183 m->append((unsigned char)c);
3184 c = get_copy(&n);
3186 m = ci->set_macro(m);
3187 if (m)
3188 delete m;
3189 tok.next();
3193 static void remove_character()
3195 tok.skip();
3196 while (!tok.newline() && !tok.eof()) {
3197 if (!tok.space() && !tok.tab()) {
3198 charinfo *ci = tok.get_char(1);
3199 if (!ci)
3200 break;
3201 macro *m = ci->set_macro(0);
3202 if (m)
3203 delete m;
3205 tok.next();
3207 skip_line();
3210 static void interpolate_string(symbol nm)
3212 request_or_macro *p = lookup_request(nm);
3213 macro *m = p->to_macro();
3214 if (!m)
3215 error("you can only invoke a string using \\*");
3216 else {
3217 string_iterator *si = new string_iterator(*m, "string", nm);
3218 input_stack::push(si);
3222 /* This class is used for the implementation of \$@. It is used for
3223 each of the closing double quotes. It artificially increases the
3224 input level by 2, so that the closing double quote will appear to have
3225 the same input level as the opening quote. */
3227 class end_quote_iterator : public input_iterator {
3228 unsigned char buf[1];
3229 public:
3230 end_quote_iterator();
3231 ~end_quote_iterator() { }
3232 int internal_level() { return 2; }
3235 end_quote_iterator::end_quote_iterator()
3237 buf[0] = '"';
3238 ptr = buf;
3239 eptr = buf + 1;
3242 static void interpolate_arg(symbol nm)
3244 const char *s = nm.contents();
3245 if (!s || *s == '\0')
3246 copy_mode_error("missing argument name");
3247 else if (s[1] == 0 && csdigit(s[0]))
3248 input_stack::push(input_stack::get_arg(s[0] - '0'));
3249 else if (s[0] == '*' && s[1] == '\0') {
3250 for (int i = input_stack::nargs(); i > 0; i--) {
3251 input_stack::push(input_stack::get_arg(i));
3252 if (i != 1)
3253 input_stack::push(make_temp_iterator(" "));
3256 else if (s[0] == '@' && s[1] == '\0') {
3257 for (int i = input_stack::nargs(); i > 0; i--) {
3258 input_stack::push(new end_quote_iterator);
3259 input_stack::push(input_stack::get_arg(i));
3260 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
3263 else {
3264 const char *p;
3265 for (p = s; *p && csdigit(*p); p++)
3267 if (*p)
3268 copy_mode_error("bad argument name `%1'", s);
3269 else
3270 input_stack::push(input_stack::get_arg(atoi(s)));
3274 void handle_first_page_transition()
3276 push_token(tok);
3277 topdiv->begin_page();
3280 // We push back a token by wrapping it up in a token_node, and
3281 // wrapping that up in a string_iterator.
3283 static void push_token(const token &t)
3285 macro m;
3286 m.append(new token_node(t));
3287 input_stack::push(new string_iterator(m));
3290 void push_page_ejector()
3292 static char buf[2] = { PAGE_EJECTOR, '\0' };
3293 input_stack::push(make_temp_iterator(buf));
3296 void handle_initial_request(unsigned char code)
3298 char buf[2];
3299 buf[0] = code;
3300 buf[1] = '\0';
3301 macro mac;
3302 mac.append(new token_node(tok));
3303 input_stack::push(new string_iterator(mac));
3304 input_stack::push(make_temp_iterator(buf));
3305 topdiv->begin_page();
3306 tok.next();
3309 void handle_initial_title()
3311 handle_initial_request(TITLE_REQUEST);
3314 // this should be local to define_macro, but cfront 1.2 doesn't support that
3315 static symbol dot_symbol(".");
3317 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3319 void do_define_macro(define_mode mode, int indirect)
3321 symbol nm, term;
3322 if (indirect) {
3323 symbol temp1 = get_name(1);
3324 if (temp1.is_null()) {
3325 skip_line();
3326 return;
3328 symbol temp2 = get_name();
3329 input_stack::push(make_temp_iterator("\n"));
3330 if (!temp2.is_null()) {
3331 interpolate_string(temp2);
3332 input_stack::push(make_temp_iterator(" "));
3334 interpolate_string(temp1);
3335 input_stack::push(make_temp_iterator(" "));
3336 tok.next();
3338 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3339 nm = get_name(1);
3340 if (nm.is_null()) {
3341 skip_line();
3342 return;
3345 term = get_name(); // the request that terminates the definition
3346 if (term.is_null())
3347 term = dot_symbol;
3348 while (!tok.newline() && !tok.eof())
3349 tok.next();
3350 const char *start_filename;
3351 int start_lineno;
3352 int have_start_location = input_stack::get_location(0, &start_filename,
3353 &start_lineno);
3354 node *n;
3355 // doing this here makes the line numbers come out right
3356 int c = get_copy(&n, 1);
3357 macro mac;
3358 macro *mm = 0;
3359 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3360 request_or_macro *rm =
3361 (request_or_macro *)request_dictionary.lookup(nm);
3362 if (rm)
3363 mm = rm->to_macro();
3364 if (mm && mode == DEFINE_APPEND)
3365 mac = *mm;
3367 int bol = 1;
3368 for (;;) {
3369 while (c == ESCAPE_NEWLINE) {
3370 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
3371 mac.append(c);
3372 c = get_copy(&n, 1);
3374 if (bol && c == '.') {
3375 const char *s = term.contents();
3376 int d;
3377 // see if it matches term
3378 int i;
3379 for (i = 0; s[i] != 0; i++) {
3380 d = get_copy(&n);
3381 if ((unsigned char)s[i] != d)
3382 break;
3384 if (s[i] == 0
3385 && ((i == 2 && compatible_flag)
3386 || (d = get_copy(&n)) == ' '
3387 || d == '\n')) { // we found it
3388 if (d == '\n')
3389 tok.make_newline();
3390 else
3391 tok.make_space();
3392 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3393 if (!mm) {
3394 mm = new macro;
3395 request_dictionary.define(nm, mm);
3397 *mm = mac;
3399 if (term != dot_symbol) {
3400 ignoring = 0;
3401 interpolate_macro(term);
3403 else
3404 skip_line();
3405 return;
3407 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3408 mac.append(c);
3409 for (int j = 0; j < i; j++)
3410 mac.append(s[j]);
3412 c = d;
3414 if (c == EOF) {
3415 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3416 if (have_start_location)
3417 error_with_file_and_line(start_filename, start_lineno,
3418 "end of file while defining macro `%1'",
3419 nm.contents());
3420 else
3421 error("end of file while defining macro `%1'", nm.contents());
3423 else {
3424 if (have_start_location)
3425 error_with_file_and_line(start_filename, start_lineno,
3426 "end of file while ignoring input lines");
3427 else
3428 error("end of file while ignoring input lines");
3430 tok.next();
3431 return;
3433 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3434 if (c == 0)
3435 mac.append(n);
3436 else
3437 mac.append(c);
3439 bol = (c == '\n');
3440 c = get_copy(&n, 1);
3444 void define_macro()
3446 do_define_macro(DEFINE_NORMAL, 0);
3449 void define_indirect_macro()
3451 do_define_macro(DEFINE_NORMAL, 1);
3454 void append_macro()
3456 do_define_macro(DEFINE_APPEND, 0);
3459 void ignore()
3461 ignoring = 1;
3462 do_define_macro(DEFINE_IGNORE, 0);
3463 ignoring = 0;
3466 void remove_macro()
3468 for (;;) {
3469 symbol s = get_name();
3470 if (s.is_null())
3471 break;
3472 request_dictionary.remove(s);
3474 skip_line();
3477 void rename_macro()
3479 symbol s1 = get_name(1);
3480 if (!s1.is_null()) {
3481 symbol s2 = get_name(1);
3482 if (!s2.is_null())
3483 request_dictionary.rename(s1, s2);
3485 skip_line();
3488 void alias_macro()
3490 symbol s1 = get_name(1);
3491 if (!s1.is_null()) {
3492 symbol s2 = get_name(1);
3493 if (!s2.is_null()) {
3494 if (!request_dictionary.alias(s1, s2))
3495 warning(WARN_MAC, "`%1' not defined", s2.contents());
3498 skip_line();
3501 void chop_macro()
3503 symbol s = get_name(1);
3504 if (!s.is_null()) {
3505 request_or_macro *p = lookup_request(s);
3506 macro *m = p->to_macro();
3507 if (!m)
3508 error("cannot chop request");
3509 else if (m->length == 0)
3510 error("cannot chop empty macro");
3511 else
3512 m->length -= 1;
3514 skip_line();
3517 void substring_macro()
3519 int start;
3520 symbol s = get_name(1);
3521 if (!s.is_null() && get_integer(&start)) {
3522 request_or_macro *p = lookup_request(s);
3523 macro *m = p->to_macro();
3524 if (!m)
3525 error("cannot substring request");
3526 else {
3527 if (start <= 0)
3528 start += m->length;
3529 else
3530 start--;
3531 int end = 0;
3532 if (!has_arg() || get_integer(&end)) {
3533 if (end <= 0)
3534 end += m->length;
3535 else
3536 end--;
3537 if (start > end) {
3538 int tem = start;
3539 start = end;
3540 end = tem;
3542 if (start >= m->length || start == end) {
3543 m->length = 0;
3544 if (m->p) {
3545 if (--(m->p->count) <= 0)
3546 delete m->p;
3547 m->p = 0;
3550 else if (start == 0)
3551 m->length = end;
3552 else {
3553 string_iterator iter(*m);
3554 int i;
3555 for (i = 0; i < start; i++)
3556 if (iter.get(0) == EOF)
3557 break;
3558 macro mac;
3559 for (; i < end; i++) {
3560 node *nd;
3561 int c = iter.get(&nd);
3562 if (c == EOF)
3563 break;
3564 if (c == 0)
3565 mac.append(nd);
3566 else
3567 mac.append((unsigned char)c);
3569 *m = mac;
3574 skip_line();
3577 void length_macro()
3579 symbol ret;
3580 ret = get_name(1);
3581 if (ret.is_null()) {
3582 skip_line();
3583 return;
3585 int c;
3586 node *n;
3587 if (tok.newline())
3588 c = '\n';
3589 else if (tok.tab())
3590 c = '\t';
3591 else if (!tok.space()) {
3592 error("bad string definition");
3593 skip_line();
3594 return;
3596 else
3597 c = get_copy(&n);
3598 while (c == ' ')
3599 c = get_copy(&n);
3600 if (c == '"')
3601 c = get_copy(&n);
3602 int len = 0;
3603 while (c != '\n' && c != EOF) {
3604 ++len;
3605 c = get_copy(&n);
3607 tok.next();
3608 reg *r = (reg*)number_reg_dictionary.lookup(ret);
3609 if (r)
3610 r->set_value(len);
3611 else
3612 set_number_reg(ret, len);
3615 void asciify_macro()
3617 symbol s = get_name(1);
3618 if (!s.is_null()) {
3619 request_or_macro *p = lookup_request(s);
3620 macro *m = p->to_macro();
3621 if (!m)
3622 error("cannot asciify request");
3623 else {
3624 macro am;
3625 string_iterator iter(*m);
3626 for (;;) {
3627 node *nd;
3628 int c = iter.get(&nd);
3629 if (c == EOF)
3630 break;
3631 if (c != 0)
3632 am.append(c);
3633 else
3634 nd->asciify(&am);
3636 *m = am;
3639 skip_line();
3642 static void interpolate_environment_variable(symbol nm)
3644 const char *s = getenv(nm.contents());
3645 if (s && *s)
3646 input_stack::push(make_temp_iterator(s));
3649 void interpolate_number_reg(symbol nm, int inc)
3651 reg *r = lookup_number_reg(nm);
3652 if (inc < 0)
3653 r->decrement();
3654 else if (inc > 0)
3655 r->increment();
3656 input_stack::push(make_temp_iterator(r->get_string()));
3659 static void interpolate_number_format(symbol nm)
3661 reg *r = (reg *)number_reg_dictionary.lookup(nm);
3662 if (r)
3663 input_stack::push(make_temp_iterator(r->get_format()));
3666 static int get_delim_number(units *n, int si, int prev_value)
3668 token start;
3669 start.next();
3670 if (start.delimiter(1)) {
3671 tok.next();
3672 if (get_number(n, si, prev_value)) {
3673 if (start != tok)
3674 warning(WARN_DELIM, "closing delimiter does not match");
3675 return 1;
3678 return 0;
3681 static int get_delim_number(units *n, int si)
3683 token start;
3684 start.next();
3685 if (start.delimiter(1)) {
3686 tok.next();
3687 if (get_number(n, si)) {
3688 if (start != tok)
3689 warning(WARN_DELIM, "closing delimiter does not match");
3690 return 1;
3693 return 0;
3696 static int get_line_arg(units *n, int si, charinfo **cp)
3698 token start;
3699 start.next();
3700 if (start.delimiter(1)) {
3701 tok.next();
3702 if (get_number(n, si)) {
3703 if (tok.dummy())
3704 tok.next();
3705 if (start != tok) {
3706 *cp = tok.get_char(1);
3707 tok.next();
3709 if (start != tok)
3710 warning(WARN_DELIM, "closing delimiter does not match");
3711 return 1;
3714 return 0;
3717 static int read_size(int *x)
3719 tok.next();
3720 int c = tok.ch();
3721 int inc = 0;
3722 if (c == '-') {
3723 inc = -1;
3724 tok.next();
3725 c = tok.ch();
3727 else if (c == '+') {
3728 inc = 1;
3729 tok.next();
3730 c = tok.ch();
3732 int val;
3733 int bad = 0;
3734 if (c == '(') {
3735 tok.next();
3736 c = tok.ch();
3737 if (!inc) {
3738 // allow an increment either before or after the left parenthesis
3739 if (c == '-') {
3740 inc = -1;
3741 tok.next();
3742 c = tok.ch();
3744 else if (c == '+') {
3745 inc = 1;
3746 tok.next();
3747 c = tok.ch();
3750 if (!csdigit(c))
3751 bad = 1;
3752 else {
3753 val = c - '0';
3754 tok.next();
3755 c = tok.ch();
3756 if (!csdigit(c))
3757 bad = 1;
3758 else {
3759 val = val*10 + (c - '0');
3760 val *= sizescale;
3764 else if (csdigit(c)) {
3765 val = c - '0';
3766 if (!inc && c != '0' && c < '4') {
3767 tok.next();
3768 c = tok.ch();
3769 if (!csdigit(c))
3770 bad = 1;
3771 else
3772 val = val*10 + (c - '0');
3774 val *= sizescale;
3776 else if (!tok.delimiter(1))
3777 return 0;
3778 else {
3779 token start(tok);
3780 tok.next();
3781 if (!(inc
3782 ? get_number(&val, 'z')
3783 : get_number(&val, 'z', curenv->get_requested_point_size())))
3784 return 0;
3785 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
3786 if (start.ch() == '[')
3787 error("missing `]'");
3788 else
3789 error("missing closing delimiter");
3790 return 0;
3793 if (!bad) {
3794 switch (inc) {
3795 case 0:
3796 *x = val;
3797 break;
3798 case 1:
3799 *x = curenv->get_requested_point_size() + val;
3800 break;
3801 case -1:
3802 *x = curenv->get_requested_point_size() - val;
3803 break;
3804 default:
3805 assert(0);
3807 return 1;
3809 else {
3810 error("bad digit in point size");
3811 return 0;
3815 static symbol get_delim_name()
3817 token start;
3818 start.next();
3819 if (start.eof()) {
3820 error("end of input at start of delimited name");
3821 return NULL_SYMBOL;
3823 if (start.newline()) {
3824 error("can't delimit name with a newline");
3825 return NULL_SYMBOL;
3827 int start_level = input_stack::get_level();
3828 char abuf[ABUF_SIZE];
3829 char *buf = abuf;
3830 int buf_size = ABUF_SIZE;
3831 int i = 0;
3832 for (;;) {
3833 if (i + 1 > buf_size) {
3834 if (buf == abuf) {
3835 buf = new char [ABUF_SIZE*2];
3836 memcpy(buf, abuf, buf_size);
3837 buf_size = ABUF_SIZE*2;
3839 else {
3840 char *old_buf = buf;
3841 buf = new char[buf_size*2];
3842 memcpy(buf, old_buf, buf_size);
3843 buf_size *= 2;
3844 a_delete old_buf;
3847 tok.next();
3848 if (tok == start
3849 && (compatible_flag || input_stack::get_level() == start_level))
3850 break;
3851 if ((buf[i] = tok.ch()) == 0) {
3852 error("missing delimiter (got %1)", tok.description());
3853 if (buf != abuf)
3854 a_delete buf;
3855 return NULL_SYMBOL;
3857 i++;
3859 buf[i] = '\0';
3860 if (buf == abuf) {
3861 if (i == 0) {
3862 error("empty delimited name");
3863 return NULL_SYMBOL;
3865 else
3866 return symbol(buf);
3868 else {
3869 symbol s(buf);
3870 a_delete buf;
3871 return s;
3876 // Implement \R
3878 static void do_register()
3880 token start;
3881 start.next();
3882 if (!start.delimiter(1))
3883 return;
3884 tok.next();
3885 symbol nm = get_long_name(1);
3886 if (nm.is_null())
3887 return;
3888 while (tok.space())
3889 tok.next();
3890 reg *r = (reg *)number_reg_dictionary.lookup(nm);
3891 int prev_value;
3892 if (!r || !r->get_value(&prev_value))
3893 prev_value = 0;
3894 int val;
3895 if (!get_number(&val, 'u', prev_value))
3896 return;
3897 if (start != tok)
3898 warning(WARN_DELIM, "closing delimiter does not match");
3899 if (r)
3900 r->set_value(val);
3901 else
3902 set_number_reg(nm, val);
3905 // this implements the \w escape sequence
3907 static void do_width()
3909 token start;
3910 start.next();
3911 int start_level = input_stack::get_level();
3912 environment env(curenv);
3913 environment *oldenv = curenv;
3914 curenv = &env;
3915 for (;;) {
3916 tok.next();
3917 if (tok.eof()) {
3918 warning(WARN_DELIM, "missing closing delimiter");
3919 break;
3921 if (tok.newline()) {
3922 warning(WARN_DELIM, "missing closing delimiter");
3923 input_stack::push(make_temp_iterator("\n"));
3924 break;
3926 if (tok == start
3927 && (compatible_flag || input_stack::get_level() == start_level))
3928 break;
3929 tok.process();
3931 env.wrap_up_tab();
3932 units x = env.get_input_line_position().to_units();
3933 input_stack::push(make_temp_iterator(i_to_a(x)));
3934 env.width_registers();
3935 curenv = oldenv;
3938 charinfo *page_character;
3940 void set_page_character()
3942 page_character = get_optional_char();
3943 skip_line();
3946 static const symbol percent_symbol("%");
3948 void read_title_parts(node **part, hunits *part_width)
3950 tok.skip();
3951 if (tok.newline() || tok.eof())
3952 return;
3953 token start(tok);
3954 int start_level = input_stack::get_level();
3955 tok.next();
3956 for (int i = 0; i < 3; i++) {
3957 while (!tok.newline() && !tok.eof()) {
3958 if (tok == start
3959 && (compatible_flag || input_stack::get_level() == start_level)) {
3960 tok.next();
3961 break;
3963 if (page_character != 0 && tok.get_char() == page_character)
3964 interpolate_number_reg(percent_symbol, 0);
3965 else
3966 tok.process();
3967 tok.next();
3969 curenv->wrap_up_tab();
3970 part_width[i] = curenv->get_input_line_position();
3971 part[i] = curenv->extract_output_line();
3973 while (!tok.newline() && !tok.eof())
3974 tok.next();
3977 class non_interpreted_node : public node {
3978 macro mac;
3979 public:
3980 non_interpreted_node(const macro &);
3981 int interpret(macro *);
3982 node *copy();
3983 int same(node *);
3984 const char *type();
3987 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
3991 int non_interpreted_node::same(node *nd)
3993 return mac == ((non_interpreted_node *)nd)->mac;
3996 const char *non_interpreted_node::type()
3998 return "non_interpreted_node";
4001 node *non_interpreted_node::copy()
4003 return new non_interpreted_node(mac);
4006 int non_interpreted_node::interpret(macro *m)
4008 string_iterator si(mac);
4009 node *n;
4010 for (;;) {
4011 int c = si.get(&n);
4012 if (c == EOF)
4013 break;
4014 if (c == 0)
4015 m->append(n);
4016 else
4017 m->append(c);
4019 return 1;
4022 static node *do_non_interpreted()
4024 node *n;
4025 int c;
4026 macro mac;
4027 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
4028 if (c == 0)
4029 mac.append(n);
4030 else
4031 mac.append(c);
4032 if (c == EOF || c == '\n') {
4033 error("missing \\?");
4034 return 0;
4036 return new non_interpreted_node(mac);
4039 static void encode_char (macro *mac, char c)
4041 if (c == '\0') {
4042 if ((font::use_charnames_in_special) && tok.special()) {
4043 charinfo *ci=tok.get_char(1);
4044 const char *s=ci->get_symbol()->contents();
4046 if (s[0] != (char)0) {
4047 mac->append('\\');
4048 mac->append('(');
4049 int i=0;
4050 while (s[i] != (char)0) {
4051 mac->append(s[i]);
4052 i++;
4054 mac->append('\\');
4055 mac->append(')');
4057 } else {
4058 error("%1 is illegal within \\X", tok.description());
4060 } else {
4061 if ((font::use_charnames_in_special) && (c == '\\')) {
4063 * add escape escape sequence
4065 mac->append(c);
4067 mac->append(c);
4071 node *do_special()
4073 token start;
4074 start.next();
4075 int start_level = input_stack::get_level();
4076 macro mac;
4077 for (tok.next();
4078 tok != start || input_stack::get_level() != start_level;
4079 tok.next()) {
4080 if (tok.eof()) {
4081 warning(WARN_DELIM, "missing closing delimiter");
4082 return 0;
4084 if (tok.newline()) {
4085 input_stack::push(make_temp_iterator("\n"));
4086 warning(WARN_DELIM, "missing closing delimiter");
4087 break;
4089 unsigned char c;
4090 if (tok.space())
4091 c = ' ';
4092 else if (tok.tab())
4093 c = '\t';
4094 else if (tok.leader())
4095 c = '\001';
4096 else if (tok.backspace())
4097 c = '\b';
4098 else
4099 c = tok.ch();
4100 encode_char(&mac, c);
4102 return new special_node(mac);
4105 void special_node::tprint(troff_output_file *out)
4107 tprint_start(out);
4108 string_iterator iter(mac);
4109 for (;;) {
4110 int c = iter.get(NULL);
4111 if (c == EOF)
4112 break;
4113 for (const char *s = ::asciify(c); *s; s++)
4114 tprint_char(out, *s);
4116 tprint_end(out);
4119 int get_file_line(const char **filename, int *lineno)
4121 return input_stack::get_location(0, filename, lineno);
4124 void line_file()
4126 int n;
4127 if (get_integer(&n)) {
4128 const char *filename = 0;
4129 if (has_arg()) {
4130 symbol s = get_long_name();
4131 filename = s.contents();
4133 (void)input_stack::set_location(filename, n-1);
4135 skip_line();
4138 static int nroff_mode = 0;
4140 static void nroff_request()
4142 nroff_mode = 1;
4143 skip_line();
4146 static void troff_request()
4148 nroff_mode = 0;
4149 skip_line();
4152 static void skip_alternative()
4154 int level = 0;
4155 // ensure that ``.if 0\{'' works as expected
4156 if (tok.left_brace())
4157 level++;
4158 int c;
4159 for (;;) {
4160 c = input_stack::get(NULL);
4161 if (c == EOF)
4162 break;
4163 if (c == ESCAPE_LEFT_BRACE)
4164 ++level;
4165 else if (c == ESCAPE_RIGHT_BRACE)
4166 --level;
4167 else if (c == escape_char && escape_char > 0)
4168 switch(input_stack::get(NULL)) {
4169 case '{':
4170 ++level;
4171 break;
4172 case '}':
4173 --level;
4174 break;
4175 case '"':
4176 while ((c = input_stack::get(NULL)) != '\n' && c != EOF)
4180 Note that the level can properly be < 0, eg
4182 .if 1 \{\
4183 .if 0 \{\
4184 .\}\}
4186 So don't give an error message in this case.
4188 if (level <= 0 && c == '\n')
4189 break;
4191 tok.next();
4194 static void begin_alternative()
4196 while (tok.space() || tok.left_brace())
4197 tok.next();
4200 void nop_request()
4202 while (tok.space())
4203 tok.next();
4206 static int_stack if_else_stack;
4208 int do_if_request()
4210 int invert = 0;
4211 while (tok.space())
4212 tok.next();
4213 while (tok.ch() == '!') {
4214 tok.next();
4215 invert = !invert;
4217 int result;
4218 unsigned char c = tok.ch();
4219 if (c == 't') {
4220 tok.next();
4221 result = !nroff_mode;
4223 else if (c == 'n') {
4224 tok.next();
4225 result = nroff_mode;
4227 else if (c == 'v') {
4228 tok.next();
4229 result = 0;
4231 else if (c == 'o') {
4232 result = (topdiv->get_page_number() & 1);
4233 tok.next();
4235 else if (c == 'e') {
4236 result = !(topdiv->get_page_number() & 1);
4237 tok.next();
4239 else if (c == 'd' || c == 'r') {
4240 tok.next();
4241 symbol nm = get_name(1);
4242 if (nm.is_null()) {
4243 skip_alternative();
4244 return 0;
4246 result = (c == 'd'
4247 ? request_dictionary.lookup(nm) != 0
4248 : number_reg_dictionary.lookup(nm) != 0);
4250 else if (c == 'c') {
4251 tok.next();
4252 tok.skip();
4253 charinfo *ci = tok.get_char(1);
4254 if (ci == 0) {
4255 skip_alternative();
4256 return 0;
4258 result = character_exists(ci, curenv);
4259 tok.next();
4261 else if (tok.space())
4262 result = 0;
4263 else if (tok.delimiter()) {
4264 token delim = tok;
4265 int delim_level = input_stack::get_level();
4266 environment env1(curenv);
4267 environment env2(curenv);
4268 environment *oldenv = curenv;
4269 curenv = &env1;
4270 for (int i = 0; i < 2; i++) {
4271 for (;;) {
4272 tok.next();
4273 if (tok.newline() || tok.eof()) {
4274 warning(WARN_DELIM, "missing closing delimiter");
4275 tok.next();
4276 curenv = oldenv;
4277 return 0;
4279 if (tok == delim
4280 && (compatible_flag || input_stack::get_level() == delim_level))
4281 break;
4282 tok.process();
4284 curenv = &env2;
4286 node *n1 = env1.extract_output_line();
4287 node *n2 = env2.extract_output_line();
4288 result = same_node_list(n1, n2);
4289 delete_node_list(n1);
4290 delete_node_list(n2);
4291 curenv = oldenv;
4292 tok.next();
4294 else {
4295 units n;
4296 if (!get_number(&n, 'u')) {
4297 skip_alternative();
4298 return 0;
4300 else
4301 result = n > 0;
4303 if (invert)
4304 result = !result;
4305 if (result)
4306 begin_alternative();
4307 else
4308 skip_alternative();
4309 return result;
4312 void if_else_request()
4314 if_else_stack.push(do_if_request());
4317 void if_request()
4319 do_if_request();
4322 void else_request()
4324 if (if_else_stack.is_empty()) {
4325 warning(WARN_EL, "unbalanced .el request");
4326 skip_alternative();
4328 else {
4329 if (if_else_stack.pop())
4330 skip_alternative();
4331 else
4332 begin_alternative();
4336 static int while_depth = 0;
4337 static int while_break_flag = 0;
4339 void while_request()
4341 macro mac;
4342 int escaped = 0;
4343 int level = 0;
4344 mac.append(new token_node(tok));
4345 for (;;) {
4346 node *n;
4347 int c = input_stack::get(&n);
4348 if (c == EOF)
4349 break;
4350 if (c == 0) {
4351 escaped = 0;
4352 mac.append(n);
4354 else if (escaped) {
4355 if (c == '{')
4356 level += 1;
4357 else if (c == '}')
4358 level -= 1;
4359 escaped = 0;
4360 mac.append(c);
4362 else {
4363 if (c == ESCAPE_LEFT_BRACE)
4364 level += 1;
4365 else if (c == ESCAPE_RIGHT_BRACE)
4366 level -= 1;
4367 else if (c == escape_char)
4368 escaped = 1;
4369 mac.append(c);
4370 if (c == '\n' && level <= 0)
4371 break;
4374 if (level != 0)
4375 error("unbalanced \\{ \\}");
4376 else {
4377 while_depth++;
4378 input_stack::add_boundary();
4379 for (;;) {
4380 input_stack::push(new string_iterator(mac, "while loop"));
4381 tok.next();
4382 if (!do_if_request()) {
4383 while (input_stack::get(NULL) != EOF)
4385 break;
4387 process_input_stack();
4388 if (while_break_flag) {
4389 while_break_flag = 0;
4390 break;
4393 input_stack::remove_boundary();
4394 while_depth--;
4396 tok.next();
4399 void while_break_request()
4401 if (!while_depth) {
4402 error("no while loop");
4403 skip_line();
4405 else {
4406 while_break_flag = 1;
4407 while (input_stack::get(NULL) != EOF)
4409 tok.next();
4413 void while_continue_request()
4415 if (!while_depth) {
4416 error("no while loop");
4417 skip_line();
4419 else {
4420 while (input_stack::get(NULL) != EOF)
4422 tok.next();
4426 // .so
4428 void source()
4430 symbol nm = get_long_name(1);
4431 if (nm.is_null())
4432 skip_line();
4433 else {
4434 while (!tok.newline() && !tok.eof())
4435 tok.next();
4436 errno = 0;
4437 FILE *fp = fopen(nm.contents(), "r");
4438 if (fp)
4439 input_stack::push(new file_iterator(fp, nm.contents()));
4440 else
4441 error("can't open `%1': %2", nm.contents(), strerror(errno));
4442 tok.next();
4446 // like .so but use popen()
4448 void pipe_source()
4450 #ifdef POPEN_MISSING
4451 error("pipes not available on this system");
4452 skip_line();
4453 #else /* not POPEN_MISSING */
4454 if (tok.newline() || tok.eof())
4455 error("missing command");
4456 else {
4457 int c;
4458 while ((c = get_copy(NULL)) == ' ' || c == '\t')
4460 int buf_size = 24;
4461 char *buf = new char[buf_size];
4462 int buf_used = 0;
4463 for (; c != '\n' && c != EOF; c = get_copy(NULL)) {
4464 const char *s = asciify(c);
4465 int slen = strlen(s);
4466 if (buf_used + slen + 1> buf_size) {
4467 char *old_buf = buf;
4468 int old_buf_size = buf_size;
4469 buf_size *= 2;
4470 buf = new char[buf_size];
4471 memcpy(buf, old_buf, old_buf_size);
4472 a_delete old_buf;
4474 strcpy(buf + buf_used, s);
4475 buf_used += slen;
4477 buf[buf_used] = '\0';
4478 errno = 0;
4479 FILE *fp = popen(buf, POPEN_RT);
4480 if (fp)
4481 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
4482 else
4483 error("can't open pipe to process `%1': %2", buf, strerror(errno));
4484 a_delete buf;
4486 tok.next();
4487 #endif /* not POPEN_MISSING */
4491 // .psbb
4493 static int llx_reg_contents = 0;
4494 static int lly_reg_contents = 0;
4495 static int urx_reg_contents = 0;
4496 static int ury_reg_contents = 0;
4498 struct bounding_box {
4499 int llx, lly, urx, ury;
4502 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
4503 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
4505 int parse_bounding_box(char *p, bounding_box *bb)
4507 if (sscanf(p, "%d %d %d %d",
4508 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
4509 return 1;
4510 else {
4511 /* The Document Structuring Conventions say that the numbers
4512 should be integers. Unfortunately some broken applications
4513 get this wrong. */
4514 double x1, x2, x3, x4;
4515 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
4516 bb->llx = (int)x1;
4517 bb->lly = (int)x2;
4518 bb->urx = (int)x3;
4519 bb->ury = (int)x4;
4520 return 1;
4522 else {
4523 for (; *p == ' ' || *p == '\t'; p++)
4525 if (strncmp(p, "(atend)", 7) == 0) {
4526 return 2;
4530 bb->llx = bb->lly = bb->urx = bb->ury = 0;
4531 return 0;
4534 // This version is taken from psrm.cc
4536 #define PS_LINE_MAX 255
4537 cset white_space("\n\r \t");
4539 int ps_get_line(char *buf, FILE *fp, const char* filename)
4541 int c = getc(fp);
4542 if (c == EOF) {
4543 buf[0] = '\0';
4544 return 0;
4546 int i = 0;
4547 int err = 0;
4548 while (c != '\r' && c != '\n' && c != EOF) {
4549 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
4550 error("illegal input character code %1 in `%2'", int(c), filename);
4551 else if (i < PS_LINE_MAX)
4552 buf[i++] = c;
4553 else if (!err) {
4554 err = 1;
4555 error("PostScript file `%1' is non-conforming "
4556 "because length of line exceeds 255", filename);
4558 c = getc(fp);
4560 buf[i++] = '\n';
4561 buf[i] = '\0';
4562 if (c == '\r') {
4563 c = getc(fp);
4564 if (c != EOF && c != '\n')
4565 ungetc(c, fp);
4567 return 1;
4570 inline void assign_registers(int llx, int lly, int urx, int ury)
4572 llx_reg_contents = llx;
4573 lly_reg_contents = lly;
4574 urx_reg_contents = urx;
4575 ury_reg_contents = ury;
4578 void do_ps_file(FILE *fp, const char* filename)
4580 bounding_box bb;
4581 int bb_at_end = 0;
4582 char buf[PS_LINE_MAX];
4583 llx_reg_contents = lly_reg_contents =
4584 urx_reg_contents = ury_reg_contents = 0;
4585 if (!ps_get_line(buf, fp, filename)) {
4586 error("`%1' is empty", filename);
4587 return;
4589 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
4590 error("`%1' is not conforming to the Document Structuring Conventions",
4591 filename);
4592 return;
4594 while (ps_get_line(buf, fp, filename) != 0) {
4595 if (buf[0] != '%' || buf[1] != '%'
4596 || strncmp(buf + 2, "EndComments", 11) == 0)
4597 break;
4598 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
4599 int res = parse_bounding_box(buf + 14, &bb);
4600 if (res == 1) {
4601 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
4602 return;
4603 } else if (res == 2) {
4604 bb_at_end = 1;
4605 break;
4607 else {
4608 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
4609 filename);
4610 return;
4614 if (bb_at_end) {
4615 long offset;
4616 int last_try = 0;
4617 /* in the trailer, the last BoundingBox comment is significant */
4618 for (offset = 512; !last_try; offset *= 2) {
4619 int had_trailer = 0;
4620 int got_bb = 0;
4621 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
4622 last_try = 1;
4623 if (fseek(fp, 0L, 0) == -1)
4624 break;
4626 while (ps_get_line(buf, fp, filename) != 0) {
4627 if (buf[0] == '%' && buf[1] == '%') {
4628 if (!had_trailer) {
4629 if (strncmp(buf + 2, "Trailer", 7) == 0)
4630 had_trailer = 1;
4632 else {
4633 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
4634 int res = parse_bounding_box(buf + 14, &bb);
4635 if (res == 1)
4636 got_bb = 1;
4637 else if (res == 2) {
4638 error("`(atend)' not allowed in trailer of `%1'", filename);
4639 return;
4641 else {
4642 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
4643 filename);
4644 return;
4650 if (got_bb) {
4651 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
4652 return;
4656 error("%%%%BoundingBox comment not found in `%1'", filename);
4659 void ps_bbox_request()
4661 symbol nm = get_long_name(1);
4662 if (nm.is_null())
4663 skip_line();
4664 else {
4665 while (!tok.newline() && !tok.eof())
4666 tok.next();
4667 errno = 0;
4668 // PS files might contain non-printable characters, such as ^Z
4669 // and CRs not followed by an LF, so open them in binary mode.
4670 FILE *fp = fopen(nm.contents(), FOPEN_RB);
4671 if (fp) {
4672 do_ps_file(fp, nm.contents());
4673 fclose(fp);
4675 else
4676 error("can't open `%1': %2", nm.contents(), strerror(errno));
4677 tok.next();
4681 const char *asciify(int c)
4683 static char buf[3];
4684 buf[0] = escape_char == '\0' ? '\\' : escape_char;
4685 buf[1] = buf[2] = '\0';
4686 switch (c) {
4687 case ESCAPE_QUESTION:
4688 buf[1] = '?';
4689 break;
4690 case ESCAPE_AMPERSAND:
4691 buf[1] = '&';
4692 break;
4693 case ESCAPE_UNDERSCORE:
4694 buf[1] = '_';
4695 break;
4696 case ESCAPE_BAR:
4697 buf[1] = '|';
4698 break;
4699 case ESCAPE_CIRCUMFLEX:
4700 buf[1] = '^';
4701 break;
4702 case ESCAPE_LEFT_BRACE:
4703 buf[1] = '{';
4704 break;
4705 case ESCAPE_RIGHT_BRACE:
4706 buf[1] = '}';
4707 break;
4708 case ESCAPE_LEFT_QUOTE:
4709 buf[1] = '`';
4710 break;
4711 case ESCAPE_RIGHT_QUOTE:
4712 buf[1] = '\'';
4713 break;
4714 case ESCAPE_HYPHEN:
4715 buf[1] = '-';
4716 break;
4717 case ESCAPE_BANG:
4718 buf[1] = '!';
4719 break;
4720 case ESCAPE_c:
4721 buf[1] = 'c';
4722 break;
4723 case ESCAPE_e:
4724 buf[1] = 'e';
4725 break;
4726 case ESCAPE_E:
4727 buf[1] = 'E';
4728 break;
4729 case ESCAPE_PERCENT:
4730 buf[1] = '%';
4731 break;
4732 case ESCAPE_SPACE:
4733 buf[1] = ' ';
4734 break;
4735 default:
4736 if (illegal_input_char(c))
4737 buf[0] = '\0';
4738 else
4739 buf[0] = c;
4740 break;
4742 return buf;
4746 const char *input_char_description(int c)
4748 switch (c) {
4749 case '\n':
4750 return "a newline character";
4751 case '\b':
4752 return "a backspace character";
4753 case '\001':
4754 return "a leader character";
4755 case '\t':
4756 return "a tab character";
4757 case ' ':
4758 return "a space character";
4759 case '\0':
4760 return "a node";
4762 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
4763 if (illegal_input_char(c)) {
4764 const char *s = asciify(c);
4765 if (*s) {
4766 buf[0] = '`';
4767 strcpy(buf + 1, s);
4768 strcat(buf, "'");
4769 return buf;
4771 sprintf(buf, "magic character code %d", c);
4772 return buf;
4774 if (csprint(c)) {
4775 buf[0] = '`';
4776 buf[1] = c;
4777 buf[2] = '\'';
4778 return buf;
4780 sprintf(buf, "character code %d", c);
4781 return buf;
4784 // .tm, .tm1, and .tmc
4786 void do_terminal(int newline, int string_like)
4788 if (!tok.newline() && !tok.eof()) {
4789 int c;
4790 for (;;) {
4791 c = get_copy(NULL);
4792 if (string_like && c == '"') {
4793 c = get_copy(NULL);
4794 break;
4796 if (c != ' ' && c != '\t')
4797 break;
4799 for (; c != '\n' && c != EOF; c = get_copy(NULL))
4800 fputs(asciify(c), stderr);
4802 if (newline)
4803 fputc('\n', stderr);
4804 fflush(stderr);
4805 tok.next();
4808 void terminal()
4810 do_terminal(1, 0);
4813 void terminal1()
4815 do_terminal(1, 1);
4818 void terminal_continue()
4820 do_terminal(0, 1);
4823 dictionary stream_dictionary(20);
4825 void do_open(int append)
4827 symbol stream = get_name(1);
4828 if (!stream.is_null()) {
4829 symbol filename = get_long_name(1);
4830 if (!filename.is_null()) {
4831 errno = 0;
4832 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
4833 if (!fp) {
4834 error("can't open `%1' for %2: %3",
4835 filename.contents(),
4836 append ? "appending" : "writing",
4837 strerror(errno));
4838 fp = (FILE *)stream_dictionary.remove(stream);
4840 else
4841 fp = (FILE *)stream_dictionary.lookup(stream, fp);
4842 if (fp)
4843 fclose(fp);
4846 skip_line();
4849 void open_request()
4851 do_open(0);
4854 void opena_request()
4856 do_open(1);
4859 void close_request()
4861 symbol stream = get_name(1);
4862 if (!stream.is_null()) {
4863 FILE *fp = (FILE *)stream_dictionary.remove(stream);
4864 if (!fp)
4865 error("no stream named `%1'", stream.contents());
4866 else
4867 fclose(fp);
4869 skip_line();
4872 void write_request()
4874 symbol stream = get_name(1);
4875 if (stream.is_null()) {
4876 skip_line();
4877 return;
4879 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
4880 if (!fp) {
4881 error("no stream named `%1'", stream.contents());
4882 skip_line();
4883 return;
4885 int c;
4886 while ((c = get_copy(NULL)) == ' ')
4888 if (c == '"')
4889 c = get_copy(NULL);
4890 for (; c != '\n' && c != EOF; c = get_copy(NULL))
4891 fputs(asciify(c), fp);
4892 fputc('\n', fp);
4893 fflush(fp);
4894 tok.next();
4897 void write_macro_request()
4899 symbol stream = get_name(1);
4900 if (stream.is_null()) {
4901 skip_line();
4902 return;
4904 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
4905 if (!fp) {
4906 error("no stream named `%1'", stream.contents());
4907 skip_line();
4908 return;
4910 symbol s = get_name(1);
4911 if (s.is_null()) {
4912 skip_line();
4913 return;
4915 request_or_macro *p = lookup_request(s);
4916 macro *m = p->to_macro();
4917 if (!m)
4918 error("cannot write request");
4919 else {
4920 string_iterator iter(*m);
4921 for (;;) {
4922 int c = iter.get(0);
4923 if (c == EOF)
4924 break;
4925 fputs(asciify(c), fp);
4927 fflush(fp);
4929 skip_line();
4932 static void init_charset_table()
4934 char buf[16];
4935 strcpy(buf, "char");
4936 for (int i = 0; i < 256; i++) {
4937 strcpy(buf + 4, i_to_a(i));
4938 charset_table[i] = get_charinfo(symbol(buf));
4939 charset_table[i]->set_ascii_code(i);
4940 if (csalpha(i))
4941 charset_table[i]->set_hyphenation_code(cmlower(i));
4943 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
4944 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
4945 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
4946 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
4947 charset_table['"']->set_flags(charinfo::TRANSPARENT);
4948 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
4949 charset_table[')']->set_flags(charinfo::TRANSPARENT);
4950 charset_table[']']->set_flags(charinfo::TRANSPARENT);
4951 charset_table['*']->set_flags(charinfo::TRANSPARENT);
4952 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
4953 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
4954 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
4955 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4956 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4957 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4958 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4959 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
4960 page_character = charset_table['%'];
4963 static
4964 void do_translate(int translate_transparent)
4966 tok.skip();
4967 while (!tok.newline() && !tok.eof()) {
4968 if (tok.space()) {
4969 // This is a really bizarre troff feature.
4970 tok.next();
4971 translate_space_to_dummy = tok.dummy();
4972 if (tok.newline() || tok.eof())
4973 break;
4974 tok.next();
4975 continue;
4977 charinfo *ci1 = tok.get_char(1);
4978 if (ci1 == 0)
4979 break;
4980 tok.next();
4981 if (tok.newline() || tok.eof()) {
4982 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
4983 translate_transparent);
4984 break;
4986 if (tok.space())
4987 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
4988 translate_transparent);
4989 else if (tok.dummy())
4990 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
4991 translate_transparent);
4992 else if (tok.hyphen_indicator())
4993 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
4994 translate_transparent);
4995 else {
4996 charinfo *ci2 = tok.get_char(1);
4997 if (ci2 == 0)
4998 break;
4999 if (ci1 == ci2)
5000 ci1->set_translation(0, translate_transparent);
5001 else
5002 ci1->set_translation(ci2, translate_transparent);
5004 tok.next();
5006 skip_line();
5009 void translate()
5011 do_translate(1);
5014 void translate_no_transparent()
5016 do_translate(0);
5019 void char_flags()
5021 int flags;
5022 if (get_integer(&flags))
5023 while (has_arg()) {
5024 charinfo *ci = tok.get_char(1);
5025 if (ci) {
5026 charinfo *tem = ci->get_translation();
5027 if (tem)
5028 ci = tem;
5029 ci->set_flags(flags);
5031 tok.next();
5033 skip_line();
5036 void hyphenation_code()
5038 tok.skip();
5039 while (!tok.newline() && !tok.eof()) {
5040 charinfo *ci = tok.get_char(1);
5041 if (ci == 0)
5042 break;
5043 tok.next();
5044 tok.skip();
5045 unsigned char c = tok.ch();
5046 if (c == 0) {
5047 error("hyphenation code must be ordinary character");
5048 break;
5050 if (csdigit(c)) {
5051 error("hyphenation code cannot be digit");
5052 break;
5054 ci->set_hyphenation_code(c);
5055 tok.next();
5056 tok.skip();
5058 skip_line();
5061 charinfo *token::get_char(int required)
5063 if (type == TOKEN_CHAR)
5064 return charset_table[c];
5065 if (type == TOKEN_SPECIAL)
5066 return get_charinfo(nm);
5067 if (type == TOKEN_NUMBERED_CHAR)
5068 return get_charinfo_by_number(val);
5069 if (type == TOKEN_ESCAPE) {
5070 if (escape_char != 0)
5071 return charset_table[escape_char];
5072 else {
5073 error("`\\e' used while no current escape character");
5074 return 0;
5077 if (required) {
5078 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
5079 warning(WARN_MISSING, "missing normal or special character");
5080 else
5081 error("normal or special character expected (got %1)", description());
5083 return 0;
5086 charinfo *get_optional_char()
5088 while (tok.space())
5089 tok.next();
5090 charinfo *ci = tok.get_char();
5091 if (!ci)
5092 check_missing_character();
5093 else
5094 tok.next();
5095 return ci;
5098 void check_missing_character()
5100 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
5101 error("normal or special character expected (got %1): "
5102 "treated as missing",
5103 tok.description());
5106 int token::add_to_node_list(node **pp)
5108 hunits w;
5109 node *n = 0;
5110 switch (type) {
5111 case TOKEN_CHAR:
5112 *pp = (*pp)->add_char(charset_table[c], curenv, &w);
5113 break;
5114 case TOKEN_DUMMY:
5115 n = new dummy_node;
5116 break;
5117 case TOKEN_ESCAPE:
5118 if (escape_char != 0)
5119 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w);
5120 break;
5121 case TOKEN_HYPHEN_INDICATOR:
5122 *pp = (*pp)->add_discretionary_hyphen();
5123 break;
5124 case TOKEN_ITALIC_CORRECTION:
5125 *pp = (*pp)->add_italic_correction(&w);
5126 break;
5127 case TOKEN_LEFT_BRACE:
5128 break;
5129 case TOKEN_MARK_INPUT:
5130 set_number_reg(nm, curenv->get_input_line_position().to_units());
5131 break;
5132 case TOKEN_NODE:
5133 n = nd;
5134 nd = 0;
5135 break;
5136 case TOKEN_NUMBERED_CHAR:
5137 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w);
5138 break;
5139 case TOKEN_RIGHT_BRACE:
5140 break;
5141 case TOKEN_SPACE:
5142 n = new hmotion_node(curenv->get_space_width());
5143 break;
5144 case TOKEN_SPECIAL:
5145 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w);
5146 break;
5147 default:
5148 return 0;
5150 if (n) {
5151 n->next = *pp;
5152 *pp = n;
5154 return 1;
5157 void token::process()
5159 if (possibly_handle_first_page_transition())
5160 return;
5161 switch (type) {
5162 case TOKEN_BACKSPACE:
5163 curenv->add_node(new hmotion_node(-curenv->get_space_width()));
5164 break;
5165 case TOKEN_CHAR:
5166 curenv->add_char(charset_table[c]);
5167 break;
5168 case TOKEN_DUMMY:
5169 curenv->add_node(new dummy_node);
5170 break;
5171 case TOKEN_EOF:
5172 assert(0);
5173 break;
5174 case TOKEN_EMPTY:
5175 assert(0);
5176 break;
5177 case TOKEN_ESCAPE:
5178 if (escape_char != 0)
5179 curenv->add_char(charset_table[escape_char]);
5180 break;
5181 case TOKEN_BEGIN_TRAP:
5182 case TOKEN_END_TRAP:
5183 case TOKEN_PAGE_EJECTOR:
5184 // these are all handled in process_input_stack()
5185 break;
5186 case TOKEN_HYPHEN_INDICATOR:
5187 curenv->add_hyphen_indicator();
5188 break;
5189 case TOKEN_INTERRUPT:
5190 curenv->interrupt();
5191 break;
5192 case TOKEN_ITALIC_CORRECTION:
5193 curenv->add_italic_correction();
5194 break;
5195 case TOKEN_LEADER:
5196 curenv->handle_tab(1);
5197 break;
5198 case TOKEN_LEFT_BRACE:
5199 break;
5200 case TOKEN_MARK_INPUT:
5201 set_number_reg(nm, curenv->get_input_line_position().to_units());
5202 break;
5203 case TOKEN_NEWLINE:
5204 curenv->newline();
5205 break;
5206 case TOKEN_NODE:
5207 curenv->add_node(nd);
5208 nd = 0;
5209 break;
5210 case TOKEN_NUMBERED_CHAR:
5211 curenv->add_char(get_charinfo_by_number(val));
5212 break;
5213 case TOKEN_REQUEST:
5214 // handled in process_input_stack
5215 break;
5216 case TOKEN_RIGHT_BRACE:
5217 break;
5218 case TOKEN_SPACE:
5219 curenv->space();
5220 break;
5221 case TOKEN_SPECIAL:
5222 curenv->add_char(get_charinfo(nm));
5223 break;
5224 case TOKEN_SPREAD:
5225 curenv->spread();
5226 break;
5227 case TOKEN_TAB:
5228 curenv->handle_tab(0);
5229 break;
5230 case TOKEN_TRANSPARENT:
5231 break;
5232 default:
5233 assert(0);
5237 class nargs_reg : public reg {
5238 public:
5239 const char *get_string();
5242 const char *nargs_reg::get_string()
5244 return i_to_a(input_stack::nargs());
5247 class lineno_reg : public reg {
5248 public:
5249 const char *get_string();
5252 const char *lineno_reg::get_string()
5254 int line;
5255 const char *file;
5256 if (!input_stack::get_location(0, &file, &line))
5257 line = 0;
5258 return i_to_a(line);
5262 class writable_lineno_reg : public general_reg {
5263 public:
5264 writable_lineno_reg();
5265 void set_value(units);
5266 int get_value(units *);
5269 writable_lineno_reg::writable_lineno_reg()
5273 int writable_lineno_reg::get_value(units *res)
5275 int line;
5276 const char *file;
5277 if (!input_stack::get_location(0, &file, &line))
5278 return 0;
5279 *res = line;
5280 return 1;
5283 void writable_lineno_reg::set_value(units n)
5285 input_stack::set_location(0, n);
5288 class filename_reg : public reg {
5289 public:
5290 const char *get_string();
5293 const char *filename_reg::get_string()
5295 int line;
5296 const char *file;
5297 if (input_stack::get_location(0, &file, &line))
5298 return file;
5299 else
5300 return 0;
5304 class constant_reg : public reg {
5305 const char *s;
5306 public:
5307 constant_reg(const char *);
5308 const char *get_string();
5311 constant_reg::constant_reg(const char *p) : s(p)
5315 const char *constant_reg::get_string()
5317 return s;
5320 constant_int_reg::constant_int_reg(int *q) : p(q)
5324 const char *constant_int_reg::get_string()
5326 return i_to_a(*p);
5329 void abort_request()
5331 int c;
5332 if (tok.eof())
5333 c = EOF;
5334 else if (tok.newline())
5335 c = '\n';
5336 else {
5337 while ((c = get_copy(0)) == ' ')
5340 if (c == EOF || c == '\n')
5341 fputs("User Abort.", stderr);
5342 else {
5343 for (; c != '\n' && c != EOF; c = get_copy(NULL))
5344 fputs(asciify(c), stderr);
5346 fputc('\n', stderr);
5347 cleanup_and_exit(1);
5350 char *read_string()
5352 int len = 256;
5353 char *s = new char[len];
5354 int c;
5355 while ((c = get_copy(0)) == ' ')
5357 int i = 0;
5358 while (c != '\n' && c != EOF) {
5359 if (!illegal_input_char(c)) {
5360 if (i + 2 > len) {
5361 char *tem = s;
5362 s = new char[len*2];
5363 memcpy(s, tem, len);
5364 len *= 2;
5365 a_delete tem;
5367 s[i++] = c;
5369 c = get_copy(0);
5371 s[i] = '\0';
5372 tok.next();
5373 if (i == 0) {
5374 a_delete s;
5375 return 0;
5377 return s;
5380 void pipe_output()
5382 #ifdef POPEN_MISSING
5383 error("pipes not available on this system");
5384 skip_line();
5385 #else /* not POPEN_MISSING */
5386 if (the_output) {
5387 error("can't pipe: output already started");
5388 skip_line();
5390 else {
5391 if ((pipe_command = read_string()) == 0)
5392 error("can't pipe to empty command");
5394 #endif /* not POPEN_MISSING */
5397 static int system_status;
5399 void system_request()
5401 char *command = read_string();
5402 if (!command)
5403 error("empty command");
5404 else {
5405 system_status = system(command);
5406 a_delete command;
5410 void copy_file()
5412 if (curdiv == topdiv && topdiv->before_first_page) {
5413 handle_initial_request(COPY_FILE_REQUEST);
5414 return;
5416 symbol filename = get_long_name(1);
5417 while (!tok.newline() && !tok.eof())
5418 tok.next();
5419 if (break_flag)
5420 curenv->do_break();
5421 if (!filename.is_null())
5422 curdiv->copy_file(filename.contents());
5423 tok.next();
5426 #ifdef COLUMN
5428 void vjustify()
5430 if (curdiv == topdiv && topdiv->before_first_page) {
5431 handle_initial_request(VJUSTIFY_REQUEST);
5432 return;
5434 symbol type = get_long_name(1);
5435 if (!type.is_null())
5436 curdiv->vjustify(type);
5437 skip_line();
5440 #endif /* COLUMN */
5442 void transparent_file()
5444 if (curdiv == topdiv && topdiv->before_first_page) {
5445 handle_initial_request(TRANSPARENT_FILE_REQUEST);
5446 return;
5448 symbol filename = get_long_name(1);
5449 while (!tok.newline() && !tok.eof())
5450 tok.next();
5451 if (break_flag)
5452 curenv->do_break();
5453 if (!filename.is_null()) {
5454 errno = 0;
5455 FILE *fp = fopen(filename.contents(), "r");
5456 if (!fp)
5457 error("can't open `%1': %2", filename.contents(), strerror(errno));
5458 else {
5459 int bol = 1;
5460 for (;;) {
5461 int c = getc(fp);
5462 if (c == EOF)
5463 break;
5464 if (illegal_input_char(c))
5465 warning(WARN_INPUT, "illegal input character code %1", int(c));
5466 else {
5467 curdiv->transparent_output(c);
5468 bol = c == '\n';
5471 if (!bol)
5472 curdiv->transparent_output('\n');
5473 fclose(fp);
5476 tok.next();
5479 class page_range {
5480 int first;
5481 int last;
5482 public:
5483 page_range *next;
5484 page_range(int, int, page_range *);
5485 int contains(int n);
5488 page_range::page_range(int i, int j, page_range *p)
5489 : first(i), last(j), next(p)
5493 int page_range::contains(int n)
5495 return n >= first && (last <= 0 || n <= last);
5498 page_range *output_page_list = 0;
5500 int in_output_page_list(int n)
5502 if (!output_page_list)
5503 return 1;
5504 for (page_range *p = output_page_list; p; p = p->next)
5505 if (p->contains(n))
5506 return 1;
5507 return 0;
5510 static void parse_output_page_list(char *p)
5512 for (;;) {
5513 int i;
5514 if (*p == '-')
5515 i = 1;
5516 else if (csdigit(*p)) {
5517 i = 0;
5519 i = i*10 + *p++ - '0';
5520 while (csdigit(*p));
5522 else
5523 break;
5524 int j;
5525 if (*p == '-') {
5526 p++;
5527 j = 0;
5528 if (csdigit(*p)) {
5530 j = j*10 + *p++ - '0';
5531 while (csdigit(*p));
5534 else
5535 j = i;
5536 if (j == 0)
5537 last_page_number = -1;
5538 else if (last_page_number >= 0 && j > last_page_number)
5539 last_page_number = j;
5540 output_page_list = new page_range(i, j, output_page_list);
5541 if (*p != ',')
5542 break;
5543 ++p;
5545 if (*p != '\0') {
5546 error("bad output page list");
5547 output_page_list = 0;
5551 static FILE *open_mac_file(const char *mac, char **path)
5553 char *s = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
5554 strcpy(s, MACRO_PREFIX);
5555 strcat(s, mac);
5556 FILE *fp = macro_path.open_file(s, path);
5557 // Try FOOBAR.tmac if tmac.FOOBAR failed. Some tmac.* file names
5558 // clash after truncation to 8+3 DOS limits, so this allows to
5559 // rename them to *.tmac instead.
5560 if (!fp) {
5561 strcpy(s, mac);
5562 strcat(s, ".");
5563 strncat(s, MACRO_PREFIX, strcspn(MACRO_PREFIX, "."));
5564 fp = macro_path.open_file(s, path);
5566 a_delete s;
5567 return fp;
5570 static void process_macro_file(const char *mac)
5572 char *path;
5573 FILE *fp = open_mac_file(mac, &path);
5574 if (!fp)
5575 fatal("can't find macro file %1", mac);
5576 const char *s = symbol(path).contents();
5577 a_delete path;
5578 input_stack::push(new file_iterator(fp, s));
5579 tok.next();
5580 process_input_stack();
5583 static void process_startup_file(char *filename)
5585 char *path;
5586 FILE *fp = macro_path.open_file(filename, &path);
5587 if (fp) {
5588 input_stack::push(new file_iterator(fp, symbol(path).contents()));
5589 a_delete path;
5590 tok.next();
5591 process_input_stack();
5595 void macro_source()
5597 symbol nm = get_long_name(1);
5598 if (nm.is_null())
5599 skip_line();
5600 else {
5601 while (!tok.newline() && !tok.eof())
5602 tok.next();
5603 char *path;
5604 FILE *fp = macro_path.open_file(nm.contents(), &path);
5605 // .mso doesn't (and cannot) go through open_mac_file, so we
5606 // need to do it here manually...
5607 if (!fp) {
5608 const char *fn = nm.contents();
5609 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
5610 char *s = new char[strlen(fn) + 1];
5611 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
5612 strcat(s, ".");
5613 strncat(s, MACRO_PREFIX, strcspn(MACRO_PREFIX, "."));
5614 fp = macro_path.open_file(s, &path);
5615 a_delete s;
5618 if (fp) {
5619 input_stack::push(new file_iterator(fp, symbol(path).contents()));
5620 a_delete path;
5622 else
5623 error("can't find macro file `%1'", nm.contents());
5624 tok.next();
5628 static void process_input_file(const char *name)
5630 FILE *fp;
5631 if (strcmp(name, "-") == 0) {
5632 clearerr(stdin);
5633 fp = stdin;
5635 else {
5636 errno = 0;
5637 fp = fopen(name, "r");
5638 if (!fp)
5639 fatal("can't open `%1': %2", name, strerror(errno));
5641 input_stack::push(new file_iterator(fp, name));
5642 tok.next();
5643 process_input_stack();
5646 // make sure the_input is empty before calling this
5648 static int evaluate_expression(const char *expr, units *res)
5650 input_stack::push(make_temp_iterator(expr));
5651 tok.next();
5652 int success = get_number(res, 'u');
5653 while (input_stack::get(NULL) != EOF)
5655 return success;
5658 static void do_register_assignment(const char *s)
5660 const char *p = strchr(s, '=');
5661 if (!p) {
5662 char buf[2];
5663 buf[0] = s[0];
5664 buf[1] = 0;
5665 units n;
5666 if (evaluate_expression(s + 1, &n))
5667 set_number_reg(buf, n);
5669 else {
5670 char *buf = new char[p - s + 1];
5671 memcpy(buf, s, p - s);
5672 buf[p - s] = 0;
5673 units n;
5674 if (evaluate_expression(p + 1, &n))
5675 set_number_reg(buf, n);
5676 a_delete buf;
5680 static void set_string(const char *name, const char *value)
5682 macro *m = new macro;
5683 for (const char *p = value; *p; p++)
5684 if (!illegal_input_char((unsigned char)*p))
5685 m->append(*p);
5686 request_dictionary.define(name, m);
5690 static void do_string_assignment(const char *s)
5692 const char *p = strchr(s, '=');
5693 if (!p) {
5694 char buf[2];
5695 buf[0] = s[0];
5696 buf[1] = 0;
5697 set_string(buf, s + 1);
5699 else {
5700 char *buf = new char[p - s + 1];
5701 memcpy(buf, s, p - s);
5702 buf[p - s] = 0;
5703 set_string(buf, p + 1);
5704 a_delete buf;
5708 struct string_list {
5709 const char *s;
5710 string_list *next;
5711 string_list(const char *ss) : s(ss), next(0) {}
5714 static void prepend_string(const char *s, string_list **p)
5716 string_list *l = new string_list(s);
5717 l->next = *p;
5718 *p = l;
5721 static void add_string(const char *s, string_list **p)
5723 while (*p)
5724 p = &((*p)->next);
5725 *p = new string_list(s);
5728 void usage(const char *prog)
5730 errprint(
5731 "usage: %1 -abivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
5732 " -rcn -Tname -Fdir -Mdir [files...]\n",
5733 prog);
5734 exit(USAGE_EXIT_CODE);
5737 int main(int argc, char **argv)
5739 program_name = argv[0];
5740 static char stderr_buf[BUFSIZ];
5741 setbuf(stderr, stderr_buf);
5742 int c;
5743 string_list *macros = 0;
5744 string_list *register_assignments = 0;
5745 string_list *string_assignments = 0;
5746 int iflag = 0;
5747 int tflag = 0;
5748 int fflag = 0;
5749 int nflag = 0;
5750 int safer_flag = 1; // safer by default
5751 int no_rc = 0; // don't process troffrc and troffrc-end
5752 int next_page_number;
5753 opterr = 0;
5754 hresolution = vresolution = 1;
5755 while ((c = getopt(argc, argv, "abivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"))
5756 != EOF)
5757 switch(c) {
5758 case 'v':
5760 extern const char *Version_string;
5761 fprintf(stderr, "GNU troff version %s\n", Version_string);
5762 fflush(stderr);
5763 break;
5765 case 'T':
5766 device = optarg;
5767 tflag = 1;
5768 is_html2 = (strcmp(device, "html2") == 0);
5769 break;
5770 case 'C':
5771 compatible_flag = 1;
5772 break;
5773 case 'M':
5774 macro_path.command_line_dir(optarg);
5775 break;
5776 case 'F':
5777 font::command_line_font_dir(optarg);
5778 break;
5779 case 'm':
5780 add_string(optarg, &macros);
5781 break;
5782 case 'E':
5783 inhibit_errors = 1;
5784 break;
5785 case 'R':
5786 no_rc = 1;
5787 break;
5788 case 'w':
5789 enable_warning(optarg);
5790 break;
5791 case 'W':
5792 disable_warning(optarg);
5793 break;
5794 case 'i':
5795 iflag = 1;
5796 break;
5797 case 'b':
5798 backtrace_flag = 1;
5799 break;
5800 case 'a':
5801 ascii_output_flag = 1;
5802 break;
5803 case 'z':
5804 suppress_output_flag = 1;
5805 break;
5806 case 'n':
5807 if (sscanf(optarg, "%d", &next_page_number) == 1)
5808 nflag++;
5809 else
5810 error("bad page number");
5811 break;
5812 case 'o':
5813 parse_output_page_list(optarg);
5814 break;
5815 case 'd':
5816 if (*optarg == '\0')
5817 error("`-d' requires non-empty argument");
5818 else
5819 add_string(optarg, &string_assignments);
5820 break;
5821 case 'r':
5822 if (*optarg == '\0')
5823 error("`-r' requires non-empty argument");
5824 else
5825 add_string(optarg, &register_assignments);
5826 break;
5827 case 'f':
5828 default_family = symbol(optarg);
5829 fflag = 1;
5830 break;
5831 case 'q':
5832 case 's':
5833 case 't':
5834 // silently ignore these
5835 break;
5836 case 'U':
5837 safer_flag = 0; // unsafe behaviour
5838 break;
5839 case '?':
5840 usage(argv[0]);
5841 break; // never reached
5842 default:
5843 assert(0);
5845 set_string(".T", device);
5846 init_charset_table();
5847 if (!font::load_desc())
5848 fatal("sorry, I can't continue");
5849 units_per_inch = font::res;
5850 hresolution = font::hor;
5851 vresolution = font::vert;
5852 sizescale = font::sizescale;
5853 tcommand_flag = font::tcommand;
5854 if (!fflag && font::family != 0 && *font::family != '\0')
5855 default_family = symbol(font::family);
5856 font_size::init_size_table(font::sizes);
5857 int i;
5858 int j = 1;
5859 if (font::style_table) {
5860 for (i = 0; font::style_table[i]; i++)
5861 mount_style(j++, symbol(font::style_table[i]));
5863 for (i = 0; font::font_name_table[i]; i++, j++)
5864 // In the DESC file a font name of 0 (zero) means leave this
5865 // position empty.
5866 if (strcmp(font::font_name_table[i], "0") != 0)
5867 mount_font(j, symbol(font::font_name_table[i]));
5868 curdiv = topdiv = new top_level_diversion;
5869 if (nflag)
5870 topdiv->set_next_page_number(next_page_number);
5871 init_input_requests();
5872 init_env_requests();
5873 init_div_requests();
5874 #ifdef COLUMN
5875 init_column_requests();
5876 #endif /* COLUMN */
5877 init_node_requests();
5878 init_output_requests();
5879 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
5880 init_registers();
5881 init_reg_requests();
5882 init_hyphen_requests();
5883 init_environments();
5884 while (string_assignments) {
5885 do_string_assignment(string_assignments->s);
5886 string_list *tem = string_assignments;
5887 string_assignments = string_assignments->next;
5888 delete tem;
5890 while (register_assignments) {
5891 do_register_assignment(register_assignments->s);
5892 string_list *tem = register_assignments;
5893 register_assignments = register_assignments->next;
5894 delete tem;
5896 if (!no_rc)
5897 process_startup_file(INITIAL_STARTUP_FILE);
5898 if (safer_flag)
5899 prepend_string("safer", &macros);
5900 while (macros) {
5901 process_macro_file(macros->s);
5902 string_list *tem = macros;
5903 macros = macros->next;
5904 delete tem;
5906 if (!no_rc)
5907 process_startup_file(FINAL_STARTUP_FILE);
5908 for (i = optind; i < argc; i++)
5909 process_input_file(argv[i]);
5910 if (optind >= argc || iflag)
5911 process_input_file("-");
5912 exit_troff();
5913 return 0; // not reached
5916 void warn_request()
5918 int n;
5919 if (has_arg() && get_integer(&n)) {
5920 if (n & ~WARN_TOTAL) {
5921 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
5922 n &= WARN_TOTAL;
5924 warning_mask = n;
5926 else
5927 warning_mask = WARN_TOTAL;
5928 skip_line();
5931 static void init_registers()
5933 #ifdef LONG_FOR_TIME_T
5934 long
5935 #else /* not LONG_FOR_TIME_T */
5936 time_t
5937 #endif /* not LONG_FOR_TIME_T */
5938 t = time(0);
5939 // Use struct here to work around misfeature in old versions of g++.
5940 struct tm *tt = localtime(&t);
5941 set_number_reg("dw", int(tt->tm_wday + 1));
5942 set_number_reg("dy", int(tt->tm_mday));
5943 set_number_reg("mo", int(tt->tm_mon + 1));
5944 set_number_reg("year", int(1900 + tt->tm_year));
5945 set_number_reg("yr", int(tt->tm_year));
5946 set_number_reg("$$", getpid());
5947 number_reg_dictionary.define(".A",
5948 new constant_reg(ascii_output_flag
5949 ? "1"
5950 : "0"));
5954 * .output request and associated registers
5957 static int output_reg_minx_contents = -1;
5958 static int output_reg_miny_contents = -1;
5959 static int output_reg_maxx_contents = -1;
5960 static int output_reg_maxy_contents = -1;
5962 void check_output_limits (int x, int y)
5964 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents)) {
5965 output_reg_minx_contents = x;
5967 if (x > output_reg_maxx_contents) {
5968 output_reg_maxx_contents = x;
5970 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents)) {
5971 output_reg_miny_contents = y;
5973 if (y > output_reg_maxy_contents) {
5974 output_reg_maxy_contents = y;
5976 // fprintf(stderr, "x = %d y=%d miny=%d maxy=%d\n", x, y, output_reg_miny_contents, output_reg_maxy_contents);
5979 void reset_output_registers()
5981 // fprintf(stderr, "reset_output_registers\n");
5982 output_reg_minx_contents = -1;
5983 output_reg_miny_contents = -1;
5984 output_reg_maxx_contents = -1;
5985 output_reg_maxy_contents = -1;
5988 void output_request()
5990 if (has_arg()) {
5991 int n;
5993 if (! get_integer(&n)) {
5994 error("missing integer argument for output request");
5995 n = 1;
5998 if (break_flag)
5999 curenv->do_break();
6001 if (!the_output)
6002 init_output();
6003 if (n == 0) {
6004 the_output->off();
6005 } else {
6006 the_output->on();
6008 } else {
6009 error("missing argument for output request");
6011 skip_line();
6014 void init_output_requests()
6016 init_request("output", output_request);
6019 void init_input_requests()
6021 init_request("ds", define_string);
6022 init_request("as", append_string);
6023 init_request("de", define_macro);
6024 init_request("dei", define_indirect_macro);
6025 init_request("am", append_macro);
6026 init_request("ig", ignore);
6027 init_request("rm", remove_macro);
6028 init_request("rn", rename_macro);
6029 init_request("nop", nop_request);
6030 init_request("if", if_request);
6031 init_request("ie", if_else_request);
6032 init_request("el", else_request);
6033 init_request("so", source);
6034 init_request("nx", next_file);
6035 init_request("pm", print_macros);
6036 init_request("eo", escape_off);
6037 init_request("ec", set_escape_char);
6038 init_request("pc", set_page_character);
6039 init_request("tm", terminal);
6040 init_request("tm1", terminal1);
6041 init_request("tmc", terminal_continue);
6042 init_request("ex", exit_request);
6043 init_request("em", end_macro);
6044 init_request("blm", blank_line_macro);
6045 init_request("tr", translate);
6046 init_request("trnt", translate_no_transparent);
6047 init_request("ab", abort_request);
6048 init_request("pi", pipe_output);
6049 init_request("cf", copy_file);
6050 init_request("sy", system_request);
6051 init_request("lf", line_file);
6052 init_request("cflags", char_flags);
6053 init_request("shift", shift);
6054 init_request("rd", read_request);
6055 init_request("cp", compatible);
6056 init_request("char", define_character);
6057 init_request("rchar", remove_character);
6058 init_request("hcode", hyphenation_code);
6059 init_request("while", while_request);
6060 init_request("break", while_break_request);
6061 init_request("continue", while_continue_request);
6062 init_request("als", alias_macro);
6063 init_request("backtrace", backtrace_request);
6064 init_request("chop", chop_macro);
6065 init_request("substring", substring_macro);
6066 init_request("length", length_macro);
6067 init_request("asciify", asciify_macro);
6068 init_request("warn", warn_request);
6069 init_request("open", open_request);
6070 init_request("opena", opena_request);
6071 init_request("close", close_request);
6072 init_request("write", write_request);
6073 init_request("writem", write_macro_request);
6074 init_request("trf", transparent_file);
6075 #ifdef WIDOW_CONTROL
6076 init_request("fpl", flush_pending_lines);
6077 #endif /* WIDOW_CONTROL */
6078 init_request("nroff", nroff_request);
6079 init_request("troff", troff_request);
6080 #ifdef COLUMN
6081 init_request("vj", vjustify);
6082 #endif /* COLUMN */
6083 init_request("mso", macro_source);
6084 init_request("do", do_request);
6085 #ifndef POPEN_MISSING
6086 init_request("pso", pipe_source);
6087 #endif /* not POPEN_MISSING */
6088 init_request("psbb", ps_bbox_request);
6089 number_reg_dictionary.define("systat", new variable_reg(&system_status));
6090 number_reg_dictionary.define("slimit",
6091 new variable_reg(&input_stack::limit));
6092 number_reg_dictionary.define(".$", new nargs_reg);
6093 number_reg_dictionary.define(".c", new lineno_reg);
6094 number_reg_dictionary.define("c.", new writable_lineno_reg);
6095 number_reg_dictionary.define(".F", new filename_reg);
6096 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
6097 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
6098 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
6099 number_reg_dictionary.define(".R", new constant_reg("10000"));
6100 extern const char *major_version;
6101 number_reg_dictionary.define(".x", new constant_reg(major_version));
6102 extern const char *minor_version;
6103 number_reg_dictionary.define(".y", new constant_reg(minor_version));
6104 extern const char *revision;
6105 number_reg_dictionary.define(".Y", new constant_reg(revision));
6106 number_reg_dictionary.define(".g", new constant_reg("1"));
6107 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
6108 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
6109 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
6110 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
6111 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
6112 number_reg_dictionary.define("opminx", new variable_reg(&output_reg_minx_contents));
6113 number_reg_dictionary.define("opminy", new variable_reg(&output_reg_miny_contents));
6114 number_reg_dictionary.define("opmaxx", new variable_reg(&output_reg_maxx_contents));
6115 number_reg_dictionary.define("opmaxy", new variable_reg(&output_reg_maxy_contents));
6118 object_dictionary request_dictionary(501);
6120 void init_request(const char *s, REQUEST_FUNCP f)
6122 request_dictionary.define(s, new request(f));
6125 static request_or_macro *lookup_request(symbol nm)
6127 assert(!nm.is_null());
6128 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
6129 if (p == 0) {
6130 warning(WARN_MAC, "`%1' not defined", nm.contents());
6131 p = new macro;
6132 request_dictionary.define(nm, p);
6134 return p;
6138 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
6140 // Don't interpret character definitions in compatible mode.
6141 int old_compatible_flag = compatible_flag;
6142 compatible_flag = 0;
6143 int old_escape_char = escape_char;
6144 escape_char = '\\';
6145 macro *mac = ci->set_macro(0);
6146 assert(mac != 0);
6147 environment *oldenv = curenv;
6148 environment env(envp);
6149 curenv = &env;
6150 curenv->set_composite();
6151 token old_tok = tok;
6152 input_stack::add_boundary();
6153 string_iterator *si = new string_iterator(*mac, "composite character", ci->nm);
6154 input_stack::push(si);
6155 // we don't use process_input_stack, because we don't want to recognise
6156 // requests
6157 for (;;) {
6158 tok.next();
6159 if (tok.eof())
6160 break;
6161 if (tok.newline()) {
6162 error("composite character mustn't contain newline");
6163 while (!tok.eof())
6164 tok.next();
6165 break;
6167 else
6168 tok.process();
6170 node *n = curenv->extract_output_line();
6171 input_stack::remove_boundary();
6172 ci->set_macro(mac);
6173 tok = old_tok;
6174 curenv = oldenv;
6175 compatible_flag = old_compatible_flag;
6176 escape_char = old_escape_char;
6177 return n;
6180 static node *read_draw_node()
6182 token start;
6183 start.next();
6184 if (!start.delimiter(1)){
6185 do {
6186 tok.next();
6187 } while (tok != start && !tok.newline() && !tok.eof());
6189 else {
6190 tok.next();
6191 if (tok == start)
6192 error("missing argument");
6193 else {
6194 unsigned char type = tok.ch();
6195 tok.next();
6196 int maxpoints = 10;
6197 hvpair *point = new hvpair[maxpoints];
6198 int npoints = 0;
6199 int no_last_v = 0;
6200 int err = 0;
6201 int i;
6202 for (i = 0; tok != start; i++) {
6203 if (i == maxpoints) {
6204 hvpair *oldpoint = point;
6205 point = new hvpair[maxpoints*2];
6206 for (int j = 0; j < maxpoints; j++)
6207 point[j] = oldpoint[j];
6208 maxpoints *= 2;
6209 a_delete oldpoint;
6211 if (!get_hunits(&point[i].h,
6212 type == 'f' || type == 't' ? 'u' : 'm')) {
6213 err = 1;
6214 break;
6216 ++npoints;
6217 tok.skip();
6218 point[i].v = V0;
6219 if (tok == start) {
6220 no_last_v = 1;
6221 break;
6223 if (!get_vunits(&point[i].v, 'v')) {
6224 err = 1;
6225 break;
6227 tok.skip();
6229 while (tok != start && !tok.newline() && !tok.eof())
6230 tok.next();
6231 if (!err) {
6232 switch (type) {
6233 case 'l':
6234 if (npoints != 1 || no_last_v) {
6235 error("two arguments needed for line");
6236 npoints = 1;
6238 break;
6239 case 'c':
6240 if (npoints != 1 || !no_last_v) {
6241 error("one argument needed for circle");
6242 npoints = 1;
6243 point[0].v = V0;
6245 break;
6246 case 'e':
6247 if (npoints != 1 || no_last_v) {
6248 error("two arguments needed for ellipse");
6249 npoints = 1;
6251 break;
6252 case 'a':
6253 if (npoints != 2 || no_last_v) {
6254 error("four arguments needed for arc");
6255 npoints = 2;
6257 break;
6258 case '~':
6259 if (no_last_v)
6260 error("even number of arguments needed for spline");
6261 break;
6262 default:
6263 // silently pass it through
6264 break;
6266 draw_node *dn = new draw_node(type, point, npoints,
6267 curenv->get_font_size());
6268 a_delete point;
6269 return dn;
6271 else {
6272 a_delete point;
6276 return 0;
6279 static struct {
6280 const char *name;
6281 int mask;
6282 } warning_table[] = {
6283 { "char", WARN_CHAR },
6284 { "range", WARN_RANGE },
6285 { "break", WARN_BREAK },
6286 { "delim", WARN_DELIM },
6287 { "el", WARN_EL },
6288 { "scale", WARN_SCALE },
6289 { "number", WARN_NUMBER },
6290 { "syntax", WARN_SYNTAX },
6291 { "tab", WARN_TAB },
6292 { "right-brace", WARN_RIGHT_BRACE },
6293 { "missing", WARN_MISSING },
6294 { "input", WARN_INPUT },
6295 { "escape", WARN_ESCAPE },
6296 { "space", WARN_SPACE },
6297 { "font", WARN_FONT },
6298 { "di", WARN_DI },
6299 { "mac", WARN_MAC },
6300 { "reg", WARN_REG },
6301 { "ig", WARN_IG },
6302 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
6303 { "w", WARN_TOTAL },
6304 { "default", DEFAULT_WARNING_MASK },
6307 static int lookup_warning(const char *name)
6309 for (int i = 0;
6310 i < sizeof(warning_table)/sizeof(warning_table[0]);
6311 i++)
6312 if (strcmp(name, warning_table[i].name) == 0)
6313 return warning_table[i].mask;
6314 return 0;
6317 static void enable_warning(const char *name)
6319 int mask = lookup_warning(name);
6320 if (mask)
6321 warning_mask |= mask;
6322 else
6323 error("unknown warning `%1'", name);
6326 static void disable_warning(const char *name)
6328 int mask = lookup_warning(name);
6329 if (mask)
6330 warning_mask &= ~mask;
6331 else
6332 error("unknown warning `%1'", name);
6335 static void copy_mode_error(const char *format,
6336 const errarg &arg1,
6337 const errarg &arg2,
6338 const errarg &arg3)
6340 if (ignoring) {
6341 static const char prefix[] = "(in ignored input) ";
6342 char *s = new char[sizeof(prefix) + strlen(format)];
6343 strcpy(s, prefix);
6344 strcat(s, format);
6345 warning(WARN_IG, s, arg1, arg2, arg3);
6346 a_delete s;
6348 else
6349 error(format, arg1, arg2, arg3);
6352 enum error_type { WARNING, ERROR, FATAL };
6354 static void do_error(error_type type,
6355 const char *format,
6356 const errarg &arg1,
6357 const errarg &arg2,
6358 const errarg &arg3)
6360 const char *filename;
6361 int lineno;
6362 if (inhibit_errors && type < FATAL)
6363 return;
6364 if (backtrace_flag)
6365 input_stack::backtrace();
6366 if (!get_file_line(&filename, &lineno))
6367 filename = 0;
6368 if (filename)
6369 errprint("%1:%2: ", filename, lineno);
6370 else if (program_name)
6371 fprintf(stderr, "%s: ", program_name);
6372 switch (type) {
6373 case FATAL:
6374 fputs("fatal error: ", stderr);
6375 break;
6376 case ERROR:
6377 break;
6378 case WARNING:
6379 fputs("warning: ", stderr);
6380 break;
6382 errprint(format, arg1, arg2, arg3);
6383 fputc('\n', stderr);
6384 fflush(stderr);
6385 if (type == FATAL)
6386 cleanup_and_exit(1);
6389 int warning(warning_type t,
6390 const char *format,
6391 const errarg &arg1,
6392 const errarg &arg2,
6393 const errarg &arg3)
6395 if ((t & warning_mask) != 0) {
6396 do_error(WARNING, format, arg1, arg2, arg3);
6397 return 1;
6399 else
6400 return 0;
6403 void error(const char *format,
6404 const errarg &arg1,
6405 const errarg &arg2,
6406 const errarg &arg3)
6408 do_error(ERROR, format, arg1, arg2, arg3);
6411 void fatal(const char *format,
6412 const errarg &arg1,
6413 const errarg &arg2,
6414 const errarg &arg3)
6416 do_error(FATAL, format, arg1, arg2, arg3);
6419 void fatal_with_file_and_line(const char *filename, int lineno,
6420 const char *format,
6421 const errarg &arg1,
6422 const errarg &arg2,
6423 const errarg &arg3)
6425 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
6426 errprint(format, arg1, arg2, arg3);
6427 fputc('\n', stderr);
6428 fflush(stderr);
6429 cleanup_and_exit(1);
6432 void error_with_file_and_line(const char *filename, int lineno,
6433 const char *format,
6434 const errarg &arg1,
6435 const errarg &arg2,
6436 const errarg &arg3)
6438 fprintf(stderr, "%s:%d: error: ", filename, lineno);
6439 errprint(format, arg1, arg2, arg3);
6440 fputc('\n', stderr);
6441 fflush(stderr);
6444 dictionary charinfo_dictionary(501);
6446 charinfo *get_charinfo(symbol nm)
6448 void *p = charinfo_dictionary.lookup(nm);
6449 if (p != 0)
6450 return (charinfo *)p;
6451 charinfo *cp = new charinfo(nm);
6452 (void)charinfo_dictionary.lookup(nm, cp);
6453 return cp;
6456 int charinfo::next_index = 0;
6458 charinfo::charinfo(symbol s)
6459 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
6460 hyphenation_code(0), flags(0), ascii_code(0), not_found(0),
6461 transparent_translate(1), nm(s)
6463 index = next_index++;
6466 void charinfo::set_hyphenation_code(unsigned char c)
6468 hyphenation_code = c;
6471 void charinfo::set_translation(charinfo *ci, int tt)
6473 translation = ci;
6474 special_translation = TRANSLATE_NONE;
6475 transparent_translate = tt;
6478 void charinfo::set_special_translation(int c, int tt)
6480 special_translation = c;
6481 translation = 0;
6482 transparent_translate = tt;
6485 void charinfo::set_ascii_code(unsigned char c)
6487 ascii_code = c;
6490 macro *charinfo::set_macro(macro *m)
6492 macro *tem = mac;
6493 mac = m;
6494 return tem;
6497 void charinfo::set_number(int n)
6499 number = n;
6500 flags |= NUMBERED;
6503 int charinfo::get_number()
6505 assert(flags & NUMBERED);
6506 return number;
6509 symbol UNNAMED_SYMBOL("---");
6511 // For numbered characters not between 0 and 255, we make a symbol out
6512 // of the number and store them in this dictionary.
6514 dictionary numbered_charinfo_dictionary(11);
6516 charinfo *get_charinfo_by_number(int n)
6518 static charinfo *number_table[256];
6520 if (n >= 0 && n < 256) {
6521 charinfo *ci = number_table[n];
6522 if (!ci) {
6523 ci = new charinfo(UNNAMED_SYMBOL);
6524 ci->set_number(n);
6525 number_table[n] = ci;
6527 return ci;
6529 else {
6530 symbol ns(i_to_a(n));
6531 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
6532 if (!ci) {
6533 ci = new charinfo(UNNAMED_SYMBOL);
6534 ci->set_number(n);
6535 numbered_charinfo_dictionary.lookup(ns, ci);
6537 return ci;
6541 int font::name_to_index(const char *nm)
6543 charinfo *ci;
6544 if (nm[1] == 0)
6545 ci = charset_table[nm[0] & 0xff];
6546 else if (nm[0] == '\\' && nm[2] == 0)
6547 ci = get_charinfo(symbol(nm + 1));
6548 else
6549 ci = get_charinfo(symbol(nm));
6550 if (ci == 0)
6551 return -1;
6552 else
6553 return ci->get_index();
6556 int font::number_to_index(int n)
6558 return get_charinfo_by_number(n)->get_index();