groff before CVS: release 1.15
[s-roff.git] / troff / input.cc
blob9d980403a7bdd7628c9ade94f7a1930ad808ef8f
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 #ifdef ISATTY_MISSING
41 #undef isatty
42 #define isatty(n) (1)
43 #else /* not ISATTY_MISSING */
44 #ifndef isatty
45 extern "C" {
46 int isatty(int);
48 #endif /* not isatty */
49 #endif /* not ISATTY_MISSING */
51 #define USAGE_EXIT_CODE 1
52 #define MACRO_PREFIX "tmac."
53 #define STARTUP_FILE "troffrc"
54 #define DEFAULT_INPUT_STACK_LIMIT 1000
56 #ifndef DEFAULT_WARNING_MASK
57 // warnings that are enabled by default
58 #define DEFAULT_WARNING_MASK \
59 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
60 #endif
62 // initial size of buffer for reading names; expanded as necessary
63 #define ABUF_SIZE 16
65 #ifdef COLUMN
66 void init_column_requests();
67 #endif /* COLUMN */
69 static node *read_draw_node();
70 void handle_first_page_transition();
71 static void push_token(const token &);
72 void copy_file();
73 #ifdef COLUMN
74 void vjustify();
75 #endif /* COLUMN */
76 void transparent();
77 void transparent_file();
79 const char *program_name = 0;
80 token tok;
81 int break_flag = 0;
82 static int backtrace_flag = 0;
83 #ifndef POPEN_MISSING
84 char *pipe_command = 0;
85 #endif
86 charinfo *charset_table[256];
88 static int warning_mask = DEFAULT_WARNING_MASK;
89 static int inhibit_errors = 0;
90 static int ignoring = 0;
92 static void enable_warning(const char *);
93 static void disable_warning(const char *);
95 static int escape_char = '\\';
96 static symbol end_macro_name;
97 static symbol blank_line_macro_name;
98 static int compatible_flag = 0;
99 int ascii_output_flag = 0;
100 int suppress_output_flag = 0;
102 int tcommand_flag = 0;
104 static int get_copy(node**, int = 0);
105 static void copy_mode_error(const char *,
106 const errarg & = empty_errarg,
107 const errarg & = empty_errarg,
108 const errarg & = empty_errarg);
110 static symbol read_escape_name();
111 static void interpolate_string(symbol);
112 static void interpolate_macro(symbol);
113 static void interpolate_number_format(symbol);
114 static void interpolate_environment_variable(symbol);
116 static void interpolate_arg(symbol);
117 static request_or_macro *lookup_request(symbol);
118 static int get_delim_number(units *, int);
119 static int get_delim_number(units *, int, units);
120 static int get_line_arg(units *res, int si, charinfo **cp);
121 static int read_size(int *);
122 static symbol get_delim_name();
123 static void init_registers();
124 static void trapping_blank_line();
126 struct input_iterator;
127 input_iterator *make_temp_iterator(const char *);
128 const char *input_char_description(int);
130 const int ESCAPE_QUESTION = 015;
131 const int BEGIN_TRAP = 016;
132 const int END_TRAP = 017;
133 const int PAGE_EJECTOR = 020;
134 const int ESCAPE_NEWLINE = 021;
135 const int ESCAPE_AMPERSAND = 022;
136 const int ESCAPE_UNDERSCORE = 023;
137 const int ESCAPE_BAR = 024;
138 const int ESCAPE_CIRCUMFLEX = 025;
139 const int ESCAPE_LEFT_BRACE = 026;
140 const int ESCAPE_RIGHT_BRACE = 027;
141 const int ESCAPE_LEFT_QUOTE = 030;
142 const int ESCAPE_RIGHT_QUOTE = 031;
143 const int ESCAPE_HYPHEN = 032;
144 const int ESCAPE_BANG = 033;
145 const int ESCAPE_c = 034;
146 const int ESCAPE_e = 035;
147 const int ESCAPE_PERCENT = 036;
148 const int ESCAPE_SPACE = 037;
150 const int TITLE_REQUEST = 0200;
151 const int COPY_FILE_REQUEST = 0201;
152 const int TRANSPARENT_FILE_REQUEST = 0202;
153 #ifdef COLUMN
154 const int VJUSTIFY_REQUEST = 0203;
155 #endif /* COLUMN */
156 const int ESCAPE_E = 0204;
157 const int LAST_PAGE_EJECTOR = 0205;
158 const int ESCAPE_RIGHT_PARENTHESIS = 0206;
160 void set_escape_char()
162 if (has_arg()) {
163 if (tok.ch() == 0) {
164 error("bad escape character");
165 escape_char = '\\';
167 else
168 escape_char = tok.ch();
170 else
171 escape_char = '\\';
172 skip_line();
175 void escape_off()
177 escape_char = 0;
178 skip_line();
181 class input_iterator {
182 public:
183 input_iterator();
184 virtual ~input_iterator();
185 int get(node **);
186 friend class input_stack;
187 protected:
188 const unsigned char *ptr;
189 const unsigned char *eptr;
190 input_iterator *next;
191 private:
192 virtual int fill(node **);
193 virtual int peek();
194 virtual int has_args() { return 0; }
195 virtual int nargs() { return 0; }
196 virtual input_iterator *get_arg(int) { return NULL; }
197 virtual int get_location(int, const char **, int *)
198 { return 0; }
199 virtual void backtrace() {}
200 virtual int set_location(const char *, int)
201 { return 0; }
202 virtual int next_file(FILE *, const char *) { return 0; }
203 virtual void shift(int) {}
204 virtual int is_boundary();
205 virtual int internal_level() { return 0; }
206 virtual int is_file() { return 0; }
209 input_iterator::input_iterator()
210 : ptr(0), eptr(0)
214 input_iterator::~input_iterator()
218 int input_iterator::fill(node **)
220 return EOF;
223 int input_iterator::peek()
225 return EOF;
228 int input_iterator::is_boundary()
230 return 0;
233 inline int input_iterator::get(node **p)
235 return ptr < eptr ? *ptr++ : fill(p);
239 class input_boundary : public input_iterator {
240 public:
241 int is_boundary() { return 1; }
244 class file_iterator : public input_iterator {
245 FILE *fp;
246 int lineno;
247 const char *filename;
248 int popened;
249 int newline_flag;
250 enum { BUF_SIZE = 512 };
251 unsigned char buf[BUF_SIZE];
252 void close();
253 public:
254 file_iterator(FILE *, const char *, int = 0);
255 ~file_iterator();
256 int fill(node **);
257 int peek();
258 int get_location(int, const char **, int *);
259 void backtrace();
260 int set_location(const char *, int);
261 int next_file(FILE *, const char *);
262 int is_file();
265 file_iterator::file_iterator(FILE *f, const char *fn, int po)
266 : fp(f), filename(fn), lineno(1), newline_flag(0), popened(po)
270 file_iterator::~file_iterator()
272 close();
275 void file_iterator::close()
277 if (fp == stdin)
278 clearerr(stdin);
279 #ifndef POPEN_MISSING
280 else if (popened)
281 pclose(fp);
282 #endif /* not POPEN_MISSING */
283 else
284 fclose(fp);
287 int file_iterator::is_file()
289 return 1;
292 int file_iterator::next_file(FILE *f, const char *s)
294 close();
295 filename = s;
296 fp = f;
297 lineno = 1;
298 newline_flag = 0;
299 popened = 0;
300 ptr = 0;
301 eptr = 0;
302 return 1;
305 int file_iterator::fill(node **)
307 if (newline_flag)
308 lineno++;
309 newline_flag = 0;
310 unsigned char *p = buf;
311 ptr = p;
312 unsigned char *e = p + BUF_SIZE;
313 while (p < e) {
314 int c = getc(fp);
315 if (c == EOF)
316 break;
317 if (illegal_input_char(c))
318 warning(WARN_INPUT, "illegal input character code %1", int(c));
319 else {
320 *p++ = c;
321 if (c == '\n') {
322 newline_flag = 1;
323 break;
327 if (p > buf) {
328 eptr = p;
329 return *ptr++;
331 else {
332 eptr = p;
333 return EOF;
337 int file_iterator::peek()
339 int c = getc(fp);
340 while (illegal_input_char(c)) {
341 warning(WARN_INPUT, "illegal input character code %1", int(c));
342 c = getc(fp);
344 if (c != EOF)
345 ungetc(c, fp);
346 return c;
349 int file_iterator::get_location(int /*allow_macro*/,
350 const char **filenamep, int *linenop)
352 *linenop = lineno;
353 if (filename != 0 && strcmp(filename, "-") == 0)
354 *filenamep = "<standard input>";
355 else
356 *filenamep = filename;
357 return 1;
360 void file_iterator::backtrace()
362 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
363 popened ? "process" : "file");
366 int file_iterator::set_location(const char *f, int ln)
368 if (f)
369 filename = f;
370 lineno = ln;
371 return 1;
374 input_iterator nil_iterator;
376 class input_stack {
377 public:
378 static int get(node **);
379 static int peek();
380 static void push(input_iterator *);
381 static input_iterator *get_arg(int);
382 static int nargs();
383 static int get_location(int, const char **, int *);
384 static int set_location(const char *, int);
385 static void backtrace();
386 static void backtrace_all();
387 static void next_file(FILE *, const char *);
388 static void end_file();
389 static void shift(int n);
390 static void add_boundary();
391 static void remove_boundary();
392 static int get_level();
393 static void clear();
395 static int limit;
396 private:
397 static input_iterator *top;
398 static int level;
400 static int finish_get(node **);
401 static int finish_peek();
404 input_iterator *input_stack::top = &nil_iterator;
405 int input_stack::level = 0;
406 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
408 inline int input_stack::get_level()
410 return level + top->internal_level();
413 inline int input_stack::get(node **np)
415 return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
418 int input_stack::finish_get(node **np)
420 for (;;) {
421 int c = top->fill(np);
422 if (c != EOF || top->is_boundary())
423 return c;
424 if (top == &nil_iterator)
425 break;
426 input_iterator *tem = top;
427 top = top->next;
428 level--;
429 delete tem;
430 if (top->ptr < top->eptr)
431 return *top->ptr++;
433 assert(level == 0);
434 return EOF;
437 inline int input_stack::peek()
439 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
442 int input_stack::finish_peek()
444 for (;;) {
445 int c = top->peek();
446 if (c != EOF || top->is_boundary())
447 return c;
448 if (top == &nil_iterator)
449 break;
450 input_iterator *tem = top;
451 top = top->next;
452 level--;
453 delete tem;
454 if (top->ptr < top->eptr)
455 return *top->ptr;
457 assert(level == 0);
458 return EOF;
461 void input_stack::add_boundary()
463 push(new input_boundary);
466 void input_stack::remove_boundary()
468 assert(top->is_boundary());
469 input_iterator *temp = top->next;
470 delete top;
471 top = temp;
472 level--;
475 void input_stack::push(input_iterator *in)
477 if (in == 0)
478 return;
479 if (++level > limit && limit > 0)
480 fatal("input stack limit exceeded (probable infinite loop)");
481 in->next = top;
482 top = in;
485 input_iterator *input_stack::get_arg(int i)
487 input_iterator *p;
488 for (p = top; p != NULL; p = p->next)
489 if (p->has_args())
490 return p->get_arg(i);
491 return 0;
494 void input_stack::shift(int n)
496 for (input_iterator *p = top; p; p = p->next)
497 if (p->has_args()) {
498 p->shift(n);
499 return;
503 int input_stack::nargs()
505 for (input_iterator *p =top; p != 0; p = p->next)
506 if (p->has_args())
507 return p->nargs();
508 return 0;
511 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
513 for (input_iterator *p = top; p; p = p->next)
514 if (p->get_location(allow_macro, filenamep, linenop))
515 return 1;
516 return 0;
519 void input_stack::backtrace()
521 const char *f;
522 int n;
523 // only backtrace down to (not including) the topmost file
524 for (input_iterator *p = top;
525 p && !p->get_location(0, &f, &n);
526 p = p->next)
527 p->backtrace();
530 void input_stack::backtrace_all()
532 for (input_iterator *p = top; p; p = p->next)
533 p->backtrace();
536 int input_stack::set_location(const char *filename, int lineno)
538 for (input_iterator *p = top; p; p = p->next)
539 if (p->set_location(filename, lineno))
540 return 1;
541 return 0;
544 void input_stack::next_file(FILE *fp, const char *s)
546 input_iterator **pp;
547 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
548 if ((*pp)->next_file(fp, s))
549 return;
550 if (++level > limit && limit > 0)
551 fatal("input stack limit exceeded");
552 *pp = new file_iterator(fp, s);
553 (*pp)->next = &nil_iterator;
556 void input_stack::end_file()
558 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
559 if ((*pp)->is_file()) {
560 input_iterator *tem = *pp;
561 *pp = (*pp)->next;
562 delete tem;
563 level--;
564 return;
568 void input_stack::clear()
570 int nboundaries = 0;
571 while (top != &nil_iterator) {
572 if (top->is_boundary())
573 nboundaries++;
574 input_iterator *tem = top;
575 top = top->next;
576 level--;
577 delete tem;
579 // Keep while_request happy.
580 for (; nboundaries > 0; --nboundaries)
581 add_boundary();
584 void backtrace_request()
586 input_stack::backtrace_all();
587 fflush(stderr);
588 skip_line();
591 void next_file()
593 symbol nm = get_long_name(0);
594 while (!tok.newline() && !tok.eof())
595 tok.next();
596 if (nm.is_null())
597 input_stack::end_file();
598 else {
599 errno = 0;
600 FILE *fp = fopen(nm.contents(), "r");
601 if (!fp)
602 error("can't open `%1': %2", nm.contents(), strerror(errno));
603 else
604 input_stack::next_file(fp, nm.contents());
606 tok.next();
609 void shift()
611 int n;
612 if (!has_arg() || !get_integer(&n))
613 n = 1;
614 input_stack::shift(n);
615 skip_line();
618 static int get_char_for_escape_name()
620 int c = get_copy(NULL);
621 switch (c) {
622 case EOF:
623 copy_mode_error("end of input in escape name");
624 return '\0';
625 default:
626 if (!illegal_input_char(c))
627 break;
628 // fall through
629 case '\n':
630 if (c == '\n')
631 input_stack::push(make_temp_iterator("\n"));
632 case ' ':
633 case '\t':
634 case '\001':
635 case '\b':
636 copy_mode_error("%1 is not allowed in an escape name",
637 input_char_description(c));
638 return '\0';
640 return c;
643 static symbol read_two_char_escape_name()
645 char buf[3];
646 buf[0] = get_char_for_escape_name();
647 if (buf[0] != '\0') {
648 buf[1] = get_char_for_escape_name();
649 if (buf[1] == '\0')
650 buf[0] = 0;
651 else
652 buf[2] = 0;
654 return symbol(buf);
657 static symbol read_long_escape_name()
659 int start_level = input_stack::get_level();
660 char abuf[ABUF_SIZE];
661 char *buf = abuf;
662 int buf_size = ABUF_SIZE;
663 int i = 0;
664 for (;;) {
665 int c = get_char_for_escape_name();
666 if (c == 0) {
667 if (buf != abuf)
668 a_delete buf;
669 return NULL_SYMBOL;
671 if (i + 2 > buf_size) {
672 if (buf == abuf) {
673 buf = new char [ABUF_SIZE*2];
674 memcpy(buf, abuf, buf_size);
675 buf_size = ABUF_SIZE*2;
677 else {
678 char *old_buf = buf;
679 buf = new char[buf_size*2];
680 memcpy(buf, old_buf, buf_size);
681 buf_size *= 2;
682 a_delete old_buf;
685 if (c == ']' && input_stack::get_level() == start_level)
686 break;
687 buf[i++] = c;
689 buf[i] = 0;
690 if (buf == abuf) {
691 if (i == 0) {
692 copy_mode_error("empty escape name");
693 return NULL_SYMBOL;
695 return symbol(abuf);
697 else {
698 symbol s(buf);
699 a_delete buf;
700 return s;
704 static symbol read_escape_name()
706 int c = get_char_for_escape_name();
707 if (c == 0)
708 return NULL_SYMBOL;
709 if (c == '(')
710 return read_two_char_escape_name();
711 if (c == '[' && !compatible_flag)
712 return read_long_escape_name();
713 char buf[2];
714 buf[0] = c;
715 buf[1] = '\0';
716 return symbol(buf);
719 static symbol read_increment_and_escape_name(int *incp)
721 int c = get_char_for_escape_name();
722 switch (c) {
723 case 0:
724 *incp = 0;
725 return NULL_SYMBOL;
726 case '(':
727 *incp = 0;
728 return read_two_char_escape_name();
729 case '+':
730 *incp = 1;
731 return read_escape_name();
732 case '-':
733 *incp = -1;
734 return read_escape_name();
735 case '[':
736 if (!compatible_flag) {
737 *incp = 0;
738 return read_long_escape_name();
740 break;
742 *incp = 0;
743 char buf[2];
744 buf[0] = c;
745 buf[1] = '\0';
746 return symbol(buf);
749 static int get_copy(node **nd, int defining)
751 for (;;) {
752 int c = input_stack::get(nd);
753 if (c == ESCAPE_NEWLINE) {
754 if (defining)
755 return c;
756 do {
757 c = input_stack::get(nd);
758 } while (c == ESCAPE_NEWLINE);
760 if (c != escape_char || escape_char <= 0)
761 return c;
762 c = input_stack::peek();
763 switch(c) {
764 case 0:
765 return escape_char;
766 case '"':
767 (void)input_stack::get(NULL);
768 while ((c = input_stack::get(NULL)) != '\n' && c != EOF)
770 return c;
771 case '#': // Like \" but newline is ignored.
772 (void)input_stack::get(NULL);
773 while ((c = input_stack::get(NULL)) != '\n')
774 if (c == EOF)
775 return EOF;
776 break;
777 case '$':
779 (void)input_stack::get(NULL);
780 symbol s = read_escape_name();
781 if (!s.is_null())
782 interpolate_arg(s);
783 break;
785 case '*':
787 (void)input_stack::get(NULL);
788 symbol s = read_escape_name();
789 if (!s.is_null())
790 interpolate_string(s);
791 break;
793 case 'a':
794 (void)input_stack::get(NULL);
795 return '\001';
796 case 'e':
797 (void)input_stack::get(NULL);
798 return ESCAPE_e;
799 case 'E':
800 (void)input_stack::get(NULL);
801 return ESCAPE_E;
802 case 'n':
804 (void)input_stack::get(NULL);
805 int inc;
806 symbol s = read_increment_and_escape_name(&inc);
807 if (!s.is_null())
808 interpolate_number_reg(s, inc);
809 break;
811 case 'g':
813 (void)input_stack::get(NULL);
814 symbol s = read_escape_name();
815 if (!s.is_null())
816 interpolate_number_format(s);
817 break;
819 case 't':
820 (void)input_stack::get(NULL);
821 return '\t';
822 case 'V':
824 (void)input_stack::get(NULL);
825 symbol s = read_escape_name();
826 if (!s.is_null())
827 interpolate_environment_variable(s);
828 break;
830 case '\n':
831 (void)input_stack::get(NULL);
832 if (defining)
833 return ESCAPE_NEWLINE;
834 break;
835 case ' ':
836 (void)input_stack::get(NULL);
837 return ESCAPE_SPACE;
838 case '|':
839 (void)input_stack::get(NULL);
840 return ESCAPE_BAR;
841 case '^':
842 (void)input_stack::get(NULL);
843 return ESCAPE_CIRCUMFLEX;
844 case '{':
845 (void)input_stack::get(NULL);
846 return ESCAPE_LEFT_BRACE;
847 case '}':
848 (void)input_stack::get(NULL);
849 return ESCAPE_RIGHT_BRACE;
850 case '`':
851 (void)input_stack::get(NULL);
852 return ESCAPE_LEFT_QUOTE;
853 case '\'':
854 (void)input_stack::get(NULL);
855 return ESCAPE_RIGHT_QUOTE;
856 case '-':
857 (void)input_stack::get(NULL);
858 return ESCAPE_HYPHEN;
859 case '_':
860 (void)input_stack::get(NULL);
861 return ESCAPE_UNDERSCORE;
862 case 'c':
863 (void)input_stack::get(NULL);
864 return ESCAPE_c;
865 case '!':
866 (void)input_stack::get(NULL);
867 return ESCAPE_BANG;
868 case '?':
869 (void)input_stack::get(NULL);
870 return ESCAPE_QUESTION;
871 case '&':
872 (void)input_stack::get(NULL);
873 return ESCAPE_AMPERSAND;
874 case ')':
875 (void)input_stack::get(NULL);
876 return ESCAPE_RIGHT_PARENTHESIS;
877 case '.':
878 (void)input_stack::get(NULL);
879 return c;
880 case '%':
881 (void)input_stack::get(NULL);
882 return ESCAPE_PERCENT;
883 default:
884 if (c == escape_char) {
885 (void)input_stack::get(NULL);
886 return c;
888 else
889 return escape_char;
894 class non_interpreted_char_node : public node {
895 unsigned char c;
896 public:
897 non_interpreted_char_node(unsigned char);
898 node *copy();
899 int interpret(macro *);
900 int same(node *);
901 const char *type();
904 int non_interpreted_char_node::same(node *nd)
906 return c == ((non_interpreted_char_node *)nd)->c;
909 const char *non_interpreted_char_node::type()
911 return "non_interpreted_char_node";
914 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
916 assert(n != 0);
919 node *non_interpreted_char_node::copy()
921 return new non_interpreted_char_node(c);
924 int non_interpreted_char_node::interpret(macro *mac)
926 mac->append(c);
927 return 1;
930 static void do_width();
931 static node *do_non_interpreted();
932 static node *do_special();
933 static void do_register();
935 static node *do_overstrike()
937 token start;
938 overstrike_node *on = new overstrike_node;
939 start.next();
940 tok.next();
941 while (tok != start) {
942 if (tok.newline() || tok.eof()) {
943 warning(WARN_DELIM, "missing closing delimiter");
944 break;
946 charinfo *ci = tok.get_char(1);
947 if (ci) {
948 node *n = curenv->make_char_node(ci);
949 if (n)
950 on->overstrike(n);
952 tok.next();
954 return on;
957 static node *do_bracket()
959 token start;
960 bracket_node *bn = new bracket_node;
961 start.next();
962 tok.next();
963 while (tok != start) {
964 if (tok.eof()) {
965 warning(WARN_DELIM, "missing closing delimiter");
966 break;
968 if (tok.newline()) {
969 warning(WARN_DELIM, "missing closing delimiter");
970 input_stack::push(make_temp_iterator("\n"));
971 break;
973 charinfo *ci = tok.get_char(1);
974 if (ci) {
975 node *n = curenv->make_char_node(ci);
976 if (n)
977 bn->bracket(n);
979 tok.next();
981 return bn;
984 static int do_name_test()
986 token start;
987 start.next();
988 int start_level = input_stack::get_level();
989 int bad_char = 0;
990 int some_char = 0;
991 for (;;) {
992 tok.next();
993 if (tok.newline() || tok.eof()) {
994 warning(WARN_DELIM, "missing closing delimiter");
995 break;
997 if (tok == start
998 && (compatible_flag || input_stack::get_level() == start_level))
999 break;
1000 if (!tok.ch())
1001 bad_char = 1;
1002 some_char = 1;
1004 return some_char && !bad_char;
1007 #if 0
1008 static node *do_zero_width()
1010 token start;
1011 start.next();
1012 int start_level = input_stack::get_level();
1013 environment env(curenv);
1014 environment *oldenv = curenv;
1015 curenv = &env;
1016 for (;;) {
1017 tok.next();
1018 if (tok.newline() || tok.eof()) {
1019 error("missing closing delimiter");
1020 break;
1022 if (tok == start
1023 && (compatible_flag || input_stack::get_level() == start_level))
1024 break;
1025 tok.process();
1027 curenv = oldenv;
1028 node *rev = env.extract_output_line();
1029 node *n = 0;
1030 while (rev) {
1031 node *tem = rev;
1032 rev = rev->next;
1033 tem->next = n;
1034 n = tem;
1036 return new zero_width_node(n);
1039 #else
1041 // It's undesirable for \Z to change environments, because then
1042 // \n(.w won't work as expected.
1044 static node *do_zero_width()
1046 node *rev = new dummy_node;
1047 token start;
1048 start.next();
1049 int start_level = input_stack::get_level();
1050 for (;;) {
1051 tok.next();
1052 if (tok.newline() || tok.eof()) {
1053 warning(WARN_DELIM, "missing closing delimiter");
1054 break;
1056 if (tok == start
1057 && (compatible_flag || input_stack::get_level() == start_level))
1058 break;
1059 if (!tok.add_to_node_list(&rev))
1060 error("illegal token in argument to \\Z");
1062 node *n = 0;
1063 while (rev) {
1064 node *tem = rev;
1065 rev = rev->next;
1066 tem->next = n;
1067 n = tem;
1069 return new zero_width_node(n);
1072 #endif
1074 token_node *node::get_token_node()
1076 return 0;
1079 class token_node : public node {
1080 public:
1081 token tk;
1082 token_node(const token &t);
1083 node *copy();
1084 token_node *get_token_node();
1085 int same(node *);
1086 const char *type();
1089 token_node::token_node(const token &t) : tk(t)
1093 node *token_node::copy()
1095 return new token_node(tk);
1098 token_node *token_node::get_token_node()
1100 return this;
1103 int token_node::same(node *nd)
1105 return tk == ((token_node *)nd)->tk;
1108 const char *token_node::type()
1110 return "token_node";
1113 token::token() : nd(0), type(TOKEN_EMPTY)
1117 token::~token()
1119 delete nd;
1122 token::token(const token &t)
1123 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1125 // Use two statements to work around bug in SGI C++.
1126 node *tem = t.nd;
1127 nd = tem ? tem->copy() : 0;
1130 void token::operator=(const token &t)
1132 delete nd;
1133 nm = t.nm;
1134 // Use two statements to work around bug in SGI C++.
1135 node *tem = t.nd;
1136 nd = tem ? tem->copy() : 0;
1137 c = t.c;
1138 val = t.val;
1139 dim = t.dim;
1140 type = t.type;
1143 void token::skip()
1145 while (space())
1146 next();
1149 int has_arg()
1151 while (tok.space())
1152 tok.next();
1153 return !tok.newline();
1156 void token::make_space()
1158 type = TOKEN_SPACE;
1161 void token::make_newline()
1163 type = TOKEN_NEWLINE;
1166 void token::next()
1168 if (nd) {
1169 delete nd;
1170 nd = 0;
1172 units x;
1173 for (;;) {
1174 node *n;
1175 int cc = input_stack::get(&n);
1176 if (cc != escape_char || escape_char == 0) {
1177 handle_normal_char:
1178 switch(cc) {
1179 case EOF:
1180 type = TOKEN_EOF;
1181 return;
1182 case TRANSPARENT_FILE_REQUEST:
1183 case TITLE_REQUEST:
1184 case COPY_FILE_REQUEST:
1185 #ifdef COLUMN
1186 case VJUSTIFY_REQUEST:
1187 #endif /* COLUMN */
1188 type = TOKEN_REQUEST;
1189 c = cc;
1190 return;
1191 case BEGIN_TRAP:
1192 type = TOKEN_BEGIN_TRAP;
1193 return;
1194 case END_TRAP:
1195 type = TOKEN_END_TRAP;
1196 return;
1197 case LAST_PAGE_EJECTOR:
1198 seen_last_page_ejector = 1;
1199 // fall through
1200 case PAGE_EJECTOR:
1201 type = TOKEN_PAGE_EJECTOR;
1202 return;
1203 case ESCAPE_PERCENT:
1204 ESCAPE_PERCENT:
1205 type = TOKEN_HYPHEN_INDICATOR;
1206 return;
1207 case ESCAPE_SPACE:
1208 ESCAPE_SPACE:
1209 type = TOKEN_NODE;
1210 nd = new space_char_hmotion_node(curenv->get_space_width());
1211 return;
1212 case ESCAPE_e:
1213 ESCAPE_e:
1214 type = TOKEN_ESCAPE;
1215 return;
1216 case ESCAPE_E:
1217 goto handle_escape_char;
1218 case ESCAPE_BAR:
1219 ESCAPE_BAR:
1220 type = TOKEN_NODE;
1221 nd = new hmotion_node(curenv->get_narrow_space_width());
1222 return;
1223 case ESCAPE_CIRCUMFLEX:
1224 ESCAPE_CIRCUMFLEX:
1225 type = TOKEN_NODE;
1226 nd = new hmotion_node(curenv->get_half_narrow_space_width());
1227 return;
1228 case ESCAPE_NEWLINE:
1229 break;
1230 case ESCAPE_LEFT_BRACE:
1231 ESCAPE_LEFT_BRACE:
1232 type = TOKEN_LEFT_BRACE;
1233 return;
1234 case ESCAPE_RIGHT_BRACE:
1235 ESCAPE_RIGHT_BRACE:
1236 type = TOKEN_RIGHT_BRACE;
1237 return;
1238 case ESCAPE_LEFT_QUOTE:
1239 ESCAPE_LEFT_QUOTE:
1240 type = TOKEN_SPECIAL;
1241 nm = symbol("ga");
1242 return;
1243 case ESCAPE_RIGHT_QUOTE:
1244 ESCAPE_RIGHT_QUOTE:
1245 type = TOKEN_SPECIAL;
1246 nm = symbol("aa");
1247 return;
1248 case ESCAPE_HYPHEN:
1249 ESCAPE_HYPHEN:
1250 type = TOKEN_SPECIAL;
1251 nm = symbol("-");
1252 return;
1253 case ESCAPE_UNDERSCORE:
1254 ESCAPE_UNDERSCORE:
1255 type = TOKEN_SPECIAL;
1256 nm = symbol("ul");
1257 return;
1258 case ESCAPE_c:
1259 ESCAPE_c:
1260 type = TOKEN_INTERRUPT;
1261 return;
1262 case ESCAPE_BANG:
1263 ESCAPE_BANG:
1264 type = TOKEN_TRANSPARENT;
1265 return;
1266 case ESCAPE_QUESTION:
1267 ESCAPE_QUESTION:
1268 nd = do_non_interpreted();
1269 if (nd) {
1270 type = TOKEN_NODE;
1271 return;
1273 break;
1274 case ESCAPE_AMPERSAND:
1275 ESCAPE_AMPERSAND:
1276 type = TOKEN_DUMMY;
1277 return;
1278 case ESCAPE_RIGHT_PARENTHESIS:
1279 ESCAPE_RIGHT_PARENTHESIS:
1280 type = TOKEN_NODE;
1281 nd = new transparent_dummy_node;
1282 return;
1283 case '\b':
1284 type = TOKEN_BACKSPACE;
1285 return;
1286 case ' ':
1287 type = TOKEN_SPACE;
1288 return;
1289 case '\t':
1290 type = TOKEN_TAB;
1291 return;
1292 case '\n':
1293 type = TOKEN_NEWLINE;
1294 return;
1295 case '\001':
1296 type = TOKEN_LEADER;
1297 return;
1298 case 0:
1300 assert(n != 0);
1301 token_node *tn = n->get_token_node();
1302 if (tn) {
1303 *this = tn->tk;
1304 delete tn;
1306 else {
1307 nd = n;
1308 type = TOKEN_NODE;
1311 return;
1312 default:
1313 type = TOKEN_CHAR;
1314 c = cc;
1315 return;
1318 else {
1319 handle_escape_char:
1320 cc = input_stack::get(NULL);
1321 switch(cc) {
1322 case '(':
1323 nm = read_two_char_escape_name();
1324 type = TOKEN_SPECIAL;
1325 return;
1326 case EOF:
1327 type = TOKEN_EOF;
1328 error("end of input after escape character");
1329 return;
1330 case '`':
1331 goto ESCAPE_LEFT_QUOTE;
1332 case '\'':
1333 goto ESCAPE_RIGHT_QUOTE;
1334 case '-':
1335 goto ESCAPE_HYPHEN;
1336 case '_':
1337 goto ESCAPE_UNDERSCORE;
1338 case '%':
1339 goto ESCAPE_PERCENT;
1340 case ' ':
1341 goto ESCAPE_SPACE;
1342 case '0':
1343 nd = new hmotion_node(curenv->get_digit_width());
1344 type = TOKEN_NODE;
1345 return;
1346 case '|':
1347 goto ESCAPE_BAR;
1348 case '^':
1349 goto ESCAPE_CIRCUMFLEX;
1350 case '/':
1351 type = TOKEN_ITALIC_CORRECTION;
1352 return;
1353 case ',':
1354 type = TOKEN_NODE;
1355 nd = new left_italic_corrected_node;
1356 return;
1357 case '&':
1358 goto ESCAPE_AMPERSAND;
1359 case ')':
1360 goto ESCAPE_RIGHT_PARENTHESIS;
1361 case '!':
1362 goto ESCAPE_BANG;
1363 case '?':
1364 goto ESCAPE_QUESTION;
1365 case '~':
1366 nd = new unbreakable_space_node(curenv->get_space_width());
1367 type = TOKEN_NODE;
1368 return;
1369 case '"':
1370 while ((cc = input_stack::get(NULL)) != '\n' && cc != EOF)
1372 if (cc == '\n')
1373 type = TOKEN_NEWLINE;
1374 else
1375 type = TOKEN_EOF;
1376 return;
1377 case '#': // Like \" but newline is ignored.
1378 while ((cc = input_stack::get(NULL)) != '\n')
1379 if (cc == EOF) {
1380 type = TOKEN_EOF;
1381 return;
1383 break;
1384 case '$':
1386 symbol nm = read_escape_name();
1387 if (!nm.is_null())
1388 interpolate_arg(nm);
1389 break;
1391 case '*':
1393 symbol nm = read_escape_name();
1394 if (!nm.is_null())
1395 interpolate_string(nm);
1396 break;
1398 case 'a':
1399 nd = new non_interpreted_char_node('\001');
1400 type = TOKEN_NODE;
1401 return;
1402 case 'A':
1403 c = '0' + do_name_test();
1404 type = TOKEN_CHAR;
1405 return;
1406 case 'b':
1407 nd = do_bracket();
1408 type = TOKEN_NODE;
1409 return;
1410 case 'c':
1411 goto ESCAPE_c;
1412 case 'C':
1413 nm = get_delim_name();
1414 if (nm.is_null())
1415 break;
1416 type = TOKEN_SPECIAL;
1417 return;
1418 case 'd':
1419 type = TOKEN_NODE;
1420 nd = new vmotion_node(curenv->get_size()/2);
1421 return;
1422 case 'D':
1423 nd = read_draw_node();
1424 if (!nd)
1425 break;
1426 type = TOKEN_NODE;
1427 return;
1428 case 'e':
1429 goto ESCAPE_e;
1430 case 'E':
1431 goto handle_escape_char;
1432 case 'f':
1434 symbol s = read_escape_name();
1435 if (s.is_null())
1436 break;
1437 const char *p;
1438 for (p = s.contents(); *p != '\0'; p++)
1439 if (!csdigit(*p))
1440 break;
1441 if (*p)
1442 curenv->set_font(s);
1443 else
1444 curenv->set_font(atoi(s.contents()));
1445 break;
1447 case 'g':
1449 symbol s = read_escape_name();
1450 if (!s.is_null())
1451 interpolate_number_format(s);
1452 break;
1454 case 'h':
1455 if (!get_delim_number(&x, 'm'))
1456 break;
1457 type = TOKEN_NODE;
1458 nd = new hmotion_node(x);
1459 return;
1460 case 'H':
1461 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1462 curenv->set_char_height(x);
1463 break;
1464 case 'k':
1465 nm = read_escape_name();
1466 if (nm.is_null())
1467 break;
1468 type = TOKEN_MARK_INPUT;
1469 return;
1470 case 'l':
1471 case 'L':
1473 charinfo *s = 0;
1474 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1475 break;
1476 if (s == 0)
1477 s = get_charinfo(cc == 'l' ? "ru" : "br");
1478 type = TOKEN_NODE;
1479 node *n = curenv->make_char_node(s);
1480 if (cc == 'l')
1481 nd = new hline_node(x, n);
1482 else
1483 nd = new vline_node(x, n);
1484 return;
1486 case 'n':
1488 int inc;
1489 symbol nm = read_increment_and_escape_name(&inc);
1490 if (!nm.is_null())
1491 interpolate_number_reg(nm, inc);
1492 break;
1494 case 'N':
1495 if (!get_delim_number(&val, 0))
1496 break;
1497 type = TOKEN_NUMBERED_CHAR;
1498 return;
1499 case 'o':
1500 nd = do_overstrike();
1501 type = TOKEN_NODE;
1502 return;
1503 case 'p':
1504 type = TOKEN_SPREAD;
1505 return;
1506 case 'r':
1507 type = TOKEN_NODE;
1508 nd = new vmotion_node(-curenv->get_size());
1509 return;
1510 case 'R':
1511 do_register();
1512 break;
1513 case 's':
1514 if (read_size(&x))
1515 curenv->set_size(x);
1516 break;
1517 case 'S':
1518 if (get_delim_number(&x, 0))
1519 curenv->set_char_slant(x);
1520 break;
1521 case 't':
1522 type = TOKEN_NODE;
1523 nd = new non_interpreted_char_node('\t');
1524 return;
1525 case 'u':
1526 type = TOKEN_NODE;
1527 nd = new vmotion_node(-curenv->get_size()/2);
1528 return;
1529 case 'v':
1530 if (!get_delim_number(&x, 'v'))
1531 break;
1532 type = TOKEN_NODE;
1533 nd = new vmotion_node(x);
1534 return;
1535 case 'V':
1537 symbol nm = read_escape_name();
1538 if (!nm.is_null())
1539 interpolate_environment_variable(nm);
1540 break;
1542 case 'w':
1543 do_width();
1544 break;
1545 case 'x':
1546 if (!get_delim_number(&x, 'v'))
1547 break;
1548 type = TOKEN_NODE;
1549 nd = new extra_size_node(x);
1550 return;
1551 case 'X':
1552 nd = do_special();
1553 if (!nd)
1554 break;
1555 type = TOKEN_NODE;
1556 return;
1557 case 'Y':
1559 symbol s = read_escape_name();
1560 if (s.is_null())
1561 break;
1562 request_or_macro *p = lookup_request(s);
1563 macro *m = p->to_macro();
1564 if (!m) {
1565 error("can't transparently throughput a request");
1566 break;
1568 nd = new special_node(*m);
1569 type = TOKEN_NODE;
1570 return;
1572 case 'z':
1574 next();
1575 if (type == TOKEN_NODE)
1576 nd = new zero_width_node(nd);
1577 else {
1578 charinfo *ci = get_char(1);
1579 if (ci == 0)
1580 break;
1581 node *gn = curenv->make_char_node(ci);
1582 if (gn == 0)
1583 break;
1584 nd = new zero_width_node(gn);
1585 type = TOKEN_NODE;
1587 return;
1589 case 'Z':
1590 nd = do_zero_width();
1591 if (nd == 0)
1592 break;
1593 type = TOKEN_NODE;
1594 return;
1595 case '{':
1596 goto ESCAPE_LEFT_BRACE;
1597 case '}':
1598 goto ESCAPE_RIGHT_BRACE;
1599 case '\n':
1600 break;
1601 case '[':
1602 if (!compatible_flag) {
1603 nm = read_long_escape_name();
1604 if (nm.is_null())
1605 break;
1606 type = TOKEN_SPECIAL;
1607 return;
1609 goto handle_normal_char;
1610 default:
1611 if (cc != escape_char && cc != '.')
1612 warning(WARN_ESCAPE, "escape character ignored before %1",
1613 input_char_description(cc));
1614 goto handle_normal_char;
1620 int token::operator==(const token &t)
1622 if (type != t.type)
1623 return 0;
1624 switch(type) {
1625 case TOKEN_CHAR:
1626 return c == t.c;
1627 case TOKEN_SPECIAL:
1628 return nm == t.nm;
1629 case TOKEN_NUMBERED_CHAR:
1630 return val == t.val;
1631 default:
1632 return 1;
1636 int token::operator!=(const token &t)
1638 return !(*this == t);
1641 // is token a suitable delimiter (like ')?
1643 int token::delimiter(int err)
1645 switch(type) {
1646 case TOKEN_CHAR:
1647 switch(c) {
1648 case '0':
1649 case '1':
1650 case '2':
1651 case '3':
1652 case '4':
1653 case '5':
1654 case '6':
1655 case '7':
1656 case '8':
1657 case '9':
1658 case '+':
1659 case '-':
1660 case '/':
1661 case '*':
1662 case '%':
1663 case '<':
1664 case '>':
1665 case '=':
1666 case '&':
1667 case ':':
1668 case '(':
1669 case ')':
1670 case '.':
1671 if (err)
1672 error("cannot use character `%1' as a starting delimiter", char(c));
1673 return 0;
1674 default:
1675 return 1;
1677 case TOKEN_NODE:
1678 case TOKEN_SPACE:
1679 case TOKEN_TAB:
1680 case TOKEN_NEWLINE:
1681 if (err)
1682 error("cannot use %1 as a starting delimiter", description());
1683 return 0;
1684 default:
1685 return 1;
1689 const char *token::description()
1691 static char buf[4];
1692 switch (type) {
1693 case TOKEN_BACKSPACE:
1694 return "a backspace character";
1695 case TOKEN_CHAR:
1696 buf[0] = '`';
1697 buf[1] = c;
1698 buf[2] = '\'';
1699 buf[3] = '\0';
1700 return buf;
1701 case TOKEN_DUMMY:
1702 return "`\\&'";
1703 case TOKEN_ESCAPE:
1704 return "`\\e'";
1705 case TOKEN_HYPHEN_INDICATOR:
1706 return "`\\%'";
1707 case TOKEN_INTERRUPT:
1708 return "`\\c'";
1709 case TOKEN_ITALIC_CORRECTION:
1710 return "`\\/'";
1711 case TOKEN_LEADER:
1712 return "a leader character";
1713 case TOKEN_LEFT_BRACE:
1714 return "`\\{'";
1715 case TOKEN_MARK_INPUT:
1716 return "`\\k'";
1717 case TOKEN_NEWLINE:
1718 return "newline";
1719 case TOKEN_NODE:
1720 return "a node";
1721 case TOKEN_NUMBERED_CHAR:
1722 return "`\\N'";
1723 case TOKEN_RIGHT_BRACE:
1724 return "`\\}'";
1725 case TOKEN_SPACE:
1726 return "a space";
1727 case TOKEN_SPECIAL:
1728 return "a special character";
1729 case TOKEN_SPREAD:
1730 return "`\\p'";
1731 case TOKEN_TAB:
1732 return "a tab character";
1733 case TOKEN_TRANSPARENT:
1734 return "`\\!'";
1735 case TOKEN_EOF:
1736 return "end of input";
1737 default:
1738 break;
1740 return "a magic token";
1743 void skip_line()
1745 while (!tok.newline())
1746 if (tok.eof())
1747 return;
1748 else
1749 tok.next();
1750 tok.next();
1753 void compatible()
1755 int n;
1756 if (has_arg() && get_integer(&n))
1757 compatible_flag = n != 0;
1758 else
1759 compatible_flag = 1;
1760 skip_line();
1763 static void empty_name_warning(int required)
1765 if (tok.newline() || tok.eof()) {
1766 if (required)
1767 warning(WARN_MISSING, "missing name");
1769 else if (tok.right_brace() || tok.tab()) {
1770 const char *start = tok.description();
1771 do {
1772 tok.next();
1773 } while (tok.space() || tok.right_brace() || tok.tab());
1774 if (!tok.newline() && !tok.eof())
1775 error("%1 is not allowed before an argument", start);
1776 else if (required)
1777 warning(WARN_MISSING, "missing name");
1779 else if (required)
1780 error("name expected (got %1)", tok.description());
1781 else
1782 error("name expected (got %1): treated as missing", tok.description());
1785 static void non_empty_name_warning()
1787 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
1788 && !tok.right_brace()
1789 // We don't want to give a warning for .el\{
1790 && !tok.left_brace())
1791 error("%1 is not allowed in a name", tok.description());
1794 symbol get_name(int required)
1796 if (compatible_flag) {
1797 char buf[3];
1798 tok.skip();
1799 if ((buf[0] = tok.ch()) != 0) {
1800 tok.next();
1801 if ((buf[1] = tok.ch()) != 0) {
1802 buf[2] = 0;
1803 tok.make_space();
1805 else
1806 non_empty_name_warning();
1807 return symbol(buf);
1809 else {
1810 empty_name_warning(required);
1811 return NULL_SYMBOL;
1814 else
1815 return get_long_name(required);
1818 symbol get_long_name(int required)
1820 while (tok.space())
1821 tok.next();
1822 char abuf[ABUF_SIZE];
1823 char *buf = abuf;
1824 int buf_size = ABUF_SIZE;
1825 int i = 0;
1826 for (;;) {
1827 if (i + 1 > buf_size) {
1828 if (buf == abuf) {
1829 buf = new char [ABUF_SIZE*2];
1830 memcpy(buf, abuf, buf_size);
1831 buf_size = ABUF_SIZE*2;
1833 else {
1834 char *old_buf = buf;
1835 buf = new char[buf_size*2];
1836 memcpy(buf, old_buf, buf_size);
1837 buf_size *= 2;
1838 a_delete old_buf;
1841 if ((buf[i] = tok.ch()) == 0)
1842 break;
1843 i++;
1844 tok.next();
1846 if (i == 0) {
1847 empty_name_warning(required);
1848 return NULL_SYMBOL;
1850 non_empty_name_warning();
1851 if (buf == abuf)
1852 return symbol(buf);
1853 else {
1854 symbol s(buf);
1855 a_delete buf;
1856 return s;
1860 void exit_troff()
1862 exit_started = 1;
1863 topdiv->set_last_page();
1864 if (!end_macro_name.is_null()) {
1865 spring_trap(end_macro_name);
1866 tok.next();
1867 process_input_stack();
1869 curenv->final_break();
1870 tok.next();
1871 process_input_stack();
1872 end_diversions();
1873 done_end_macro = 1;
1874 topdiv->set_ejecting();
1875 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
1876 input_stack::push(make_temp_iterator((char *)buf));
1877 topdiv->space(topdiv->get_page_length(), 1);
1878 tok.next();
1879 process_input_stack();
1880 seen_last_page_ejector = 1; // should be set already
1881 topdiv->set_ejecting();
1882 push_page_ejector();
1883 topdiv->space(topdiv->get_page_length(), 1);
1884 tok.next();
1885 process_input_stack();
1886 // This will only happen if a trap-invoked macro starts a diversion,
1887 // or if vertical position traps have been disabled.
1888 cleanup_and_exit(0);
1891 // This implements .ex. The input stack must be cleared before calling
1892 // exit_troff().
1894 void exit_request()
1896 input_stack::clear();
1897 if (exit_started)
1898 tok.next();
1899 else
1900 exit_troff();
1903 void end_macro()
1905 end_macro_name = get_name();
1906 skip_line();
1909 void blank_line_macro()
1911 blank_line_macro_name = get_name();
1912 skip_line();
1915 static void trapping_blank_line()
1917 if (!blank_line_macro_name.is_null())
1918 spring_trap(blank_line_macro_name);
1919 else
1920 blank_line();
1923 void do_request()
1925 int saved_compatible_flag = compatible_flag;
1926 compatible_flag = 0;
1927 symbol nm = get_name();
1928 if (nm.is_null())
1929 skip_line();
1930 else
1931 interpolate_macro(nm);
1932 compatible_flag = saved_compatible_flag;
1935 inline int possibly_handle_first_page_transition()
1937 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
1938 handle_first_page_transition();
1939 return 1;
1941 else
1942 return 0;
1945 static int transparent_translate(int cc)
1947 if (!illegal_input_char(cc)) {
1948 charinfo *ci = charset_table[cc];
1949 switch (ci->get_special_translation(1)) {
1950 case charinfo::TRANSLATE_SPACE:
1951 return ' ';
1952 case charinfo::TRANSLATE_DUMMY:
1953 return ESCAPE_AMPERSAND;
1954 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
1955 return ESCAPE_PERCENT;
1957 // This is realy ugly.
1958 ci = ci->get_translation(1);
1959 if (ci) {
1960 int c = ci->get_ascii_code();
1961 if (c != '\0')
1962 return c;
1963 error("can't translate %1 to special character `%2'"
1964 " in transparent throughput",
1965 input_char_description(cc),
1966 ci->nm.contents());
1969 return cc;
1972 class int_stack {
1973 struct int_stack_element {
1974 int n;
1975 int_stack_element *next;
1976 } *top;
1977 public:
1978 int_stack();
1979 ~int_stack();
1980 void push(int);
1981 int is_empty();
1982 int pop();
1985 int_stack::int_stack()
1987 top = 0;
1990 int_stack::~int_stack()
1992 while (top != 0) {
1993 int_stack_element *temp = top;
1994 top = top->next;
1995 delete temp;
2000 int int_stack::is_empty()
2002 return top == 0;
2005 void int_stack::push(int n)
2007 int_stack_element *p = new int_stack_element;
2008 p->next = top;
2009 p->n = n;
2010 top = p;
2014 int int_stack::pop()
2016 assert(top != 0);
2017 int_stack_element *p = top;
2018 top = top->next;
2019 int n = p->n;
2020 delete p;
2021 return n;
2024 int node::reread(int *)
2026 return 0;
2029 int diverted_space_node::reread(int *bolp)
2031 if (curenv->get_fill())
2032 trapping_blank_line();
2033 else
2034 curdiv->space(n);
2035 *bolp = 1;
2036 return 1;
2039 int diverted_copy_file_node::reread(int *bolp)
2041 curdiv->copy_file(filename.contents());
2042 *bolp = 1;
2043 return 1;
2046 void process_input_stack()
2048 int_stack trap_bol_stack;
2049 int bol = 1;
2050 for (;;) {
2051 int suppress_next = 0;
2052 switch (tok.type) {
2053 case token::TOKEN_CHAR:
2055 unsigned char ch = tok.c;
2056 if (bol &&
2057 (ch == curenv->control_char
2058 || ch == curenv->no_break_control_char)) {
2059 break_flag = ch == curenv->control_char;
2060 // skip tabs as well as spaces here
2061 do {
2062 tok.next();
2063 } while (tok.white_space());
2064 symbol nm = get_name();
2065 if (nm.is_null())
2066 skip_line();
2067 else
2068 interpolate_macro(nm);
2069 suppress_next = 1;
2071 else {
2072 if (possibly_handle_first_page_transition())
2074 else {
2075 for (;;) {
2076 curenv->add_char(charset_table[ch]);
2077 tok.next();
2078 if (tok.type != token::TOKEN_CHAR)
2079 break;
2080 ch = tok.c;
2082 suppress_next = 1;
2083 bol = 0;
2086 break;
2088 case token::TOKEN_TRANSPARENT:
2090 if (bol) {
2091 if (possibly_handle_first_page_transition())
2093 else {
2094 int cc;
2095 do {
2096 node *n;
2097 cc = get_copy(&n);
2098 if (cc != EOF)
2099 if (cc != '\0')
2100 curdiv->transparent_output(transparent_translate(cc));
2101 else
2102 curdiv->transparent_output(n);
2103 } while (cc != '\n' && cc != EOF);
2104 if (cc == EOF)
2105 curdiv->transparent_output('\n');
2108 break;
2110 case token::TOKEN_NEWLINE:
2112 if (bol && !curenv->get_prev_line_interrupted())
2113 trapping_blank_line();
2114 else {
2115 curenv->newline();
2116 bol = 1;
2118 break;
2120 case token::TOKEN_REQUEST:
2122 int request_code = tok.c;
2123 tok.next();
2124 switch (request_code) {
2125 case TITLE_REQUEST:
2126 title();
2127 break;
2128 case COPY_FILE_REQUEST:
2129 copy_file();
2130 break;
2131 case TRANSPARENT_FILE_REQUEST:
2132 transparent_file();
2133 break;
2134 #ifdef COLUMN
2135 case VJUSTIFY_REQUEST:
2136 vjustify();
2137 break;
2138 #endif /* COLUMN */
2139 default:
2140 assert(0);
2141 break;
2143 suppress_next = 1;
2144 break;
2146 case token::TOKEN_SPACE:
2148 if (possibly_handle_first_page_transition())
2150 else if (bol && !curenv->get_prev_line_interrupted()) {
2151 int nspaces = 0;
2152 do {
2153 nspaces += tok.nspaces();
2154 tok.next();
2155 } while (tok.space());
2156 if (tok.newline())
2157 trapping_blank_line();
2158 else {
2159 push_token(tok);
2160 curenv->do_break();
2161 curenv->add_node(new hmotion_node(curenv->get_space_width()*nspaces));
2162 bol = 0;
2165 else {
2166 curenv->space();
2167 bol = 0;
2169 break;
2171 case token::TOKEN_EOF:
2172 return;
2173 case token::TOKEN_NODE:
2175 if (possibly_handle_first_page_transition())
2177 else if (tok.nd->reread(&bol)) {
2178 delete tok.nd;
2179 tok.nd = 0;
2181 else {
2182 curenv->add_node(tok.nd);
2183 tok.nd = 0;
2184 bol = 0;
2186 break;
2188 case token::TOKEN_PAGE_EJECTOR:
2190 continue_page_eject();
2191 // I think we just want to preserve bol.
2192 // bol = 1;
2193 break;
2195 case token::TOKEN_BEGIN_TRAP:
2197 trap_bol_stack.push(bol);
2198 bol = 1;
2199 break;
2201 case token::TOKEN_END_TRAP:
2203 if (trap_bol_stack.is_empty())
2204 error("spurious end trap token detected!");
2205 else
2206 bol = trap_bol_stack.pop();
2208 /* I'm not totally happy about this. But I can't think of any other
2209 way to do it. Doing an output_pending_lines() whenever a
2210 TOKEN_END_TRAP is detected doesn't work: for example,
2212 .wh -1i x
2213 .de x
2216 .wh -.5i y
2217 .de y
2218 .tl ''-%-''
2221 .ll .5i
2222 .sp |\n(.pu-1i-.5v
2223 a\%very\%very\%long\%word
2225 will print all but the first lines from the word immediately
2226 after the footer, rather than on the next page. */
2228 if (trap_bol_stack.is_empty())
2229 curenv->output_pending_lines();
2230 break;
2232 default:
2234 bol = 0;
2235 tok.process();
2236 break;
2239 if (!suppress_next)
2240 tok.next();
2241 trap_sprung_flag = 0;
2245 #ifdef WIDOW_CONTROL
2247 void flush_pending_lines()
2249 while (!tok.newline() && !tok.eof())
2250 tok.next();
2251 curenv->output_pending_lines();
2252 tok.next();
2255 #endif /* WIDOW_CONTROL */
2257 request_or_macro::request_or_macro()
2261 macro *request_or_macro::to_macro()
2263 return 0;
2266 request::request(REQUEST_FUNCP pp) : p(pp)
2270 void request::invoke(symbol)
2272 (*p)();
2275 struct char_block {
2276 enum { SIZE = 128 };
2277 unsigned char s[SIZE];
2278 char_block *next;
2279 char_block();
2282 char_block::char_block()
2283 : next(0)
2287 class char_list {
2288 public:
2289 char_list();
2290 ~char_list();
2291 void append(unsigned char);
2292 int length();
2293 private:
2294 unsigned char *ptr;
2295 int len;
2296 char_block *head;
2297 char_block *tail;
2298 friend class macro_header;
2299 friend class string_iterator;
2302 char_list::char_list()
2303 : head(0), tail(0), ptr(0), len(0)
2307 char_list::~char_list()
2309 while (head != 0) {
2310 char_block *tem = head;
2311 head = head->next;
2312 delete tem;
2316 int char_list::length()
2318 return len;
2321 void char_list::append(unsigned char c)
2323 if (tail == 0) {
2324 head = tail = new char_block;
2325 ptr = tail->s;
2327 else {
2328 if (ptr >= tail->s + char_block::SIZE) {
2329 tail->next = new char_block;
2330 tail = tail->next;
2331 ptr = tail->s;
2334 *ptr++ = c;
2335 len++;
2338 class node_list {
2339 node *head;
2340 node *tail;
2341 public:
2342 node_list();
2343 ~node_list();
2344 void append(node *);
2345 int length();
2346 node *extract();
2348 friend class macro_header;
2349 friend class string_iterator;
2352 void node_list::append(node *n)
2354 if (head == 0) {
2355 n->next = 0;
2356 head = tail = n;
2358 else {
2359 n->next = 0;
2360 tail = tail->next = n;
2364 int node_list::length()
2366 int total = 0;
2367 for (node *n = head; n != 0; n = n->next)
2368 ++total;
2369 return total;
2372 node_list::node_list()
2374 head = tail = 0;
2377 node *node_list::extract()
2379 node *temp = head;
2380 head = tail = 0;
2381 return temp;
2385 node_list::~node_list()
2387 delete_node_list(head);
2390 struct macro_header {
2391 int count;
2392 char_list cl;
2393 node_list nl;
2394 macro_header() { count = 1; }
2395 macro_header *copy(int);
2399 macro::~macro()
2401 if (p != 0 && --(p->count) <= 0)
2402 delete p;
2405 macro::macro()
2407 if (!input_stack::get_location(1, &filename, &lineno)) {
2408 filename = 0;
2409 lineno = 0;
2411 length = 0;
2412 p = 0;
2415 macro::macro(const macro &m)
2416 : filename(m.filename), lineno(m.lineno), p(m.p), length(m.length)
2418 if (p != 0)
2419 p->count++;
2422 macro &macro::operator=(const macro &m)
2424 // don't assign object
2425 if (m.p != 0)
2426 m.p->count++;
2427 if (p != 0 && --(p->count) <= 0)
2428 delete p;
2429 p = m.p;
2430 filename = m.filename;
2431 lineno = m.lineno;
2432 length = m.length;
2433 return *this;
2436 void macro::append(unsigned char c)
2438 assert(c != 0);
2439 if (p == 0)
2440 p = new macro_header;
2441 if (p->cl.length() != length) {
2442 macro_header *tem = p->copy(length);
2443 if (--(p->count) <= 0)
2444 delete p;
2445 p = tem;
2447 p->cl.append(c);
2448 ++length;
2451 void macro::append(node *n)
2453 assert(n != 0);
2454 if (p == 0)
2455 p = new macro_header;
2456 if (p->cl.length() != length) {
2457 macro_header *tem = p->copy(length);
2458 if (--(p->count) <= 0)
2459 delete p;
2460 p = tem;
2462 p->cl.append(0);
2463 p->nl.append(n);
2464 ++length;
2467 void macro::print_size()
2469 errprint("%1", length);
2472 // make a copy of the first n bytes
2474 macro_header *macro_header::copy(int n)
2476 macro_header *p = new macro_header;
2477 char_block *bp = cl.head;
2478 unsigned char *ptr = bp->s;
2479 node *nd = nl.head;
2480 while (--n >= 0) {
2481 if (ptr >= bp->s + char_block::SIZE) {
2482 bp = bp->next;
2483 ptr = bp->s;
2485 int c = *ptr++;
2486 p->cl.append(c);
2487 if (c == 0) {
2488 p->nl.append(nd->copy());
2489 nd = nd->next;
2492 return p;
2495 void print_macros()
2497 object_dictionary_iterator iter(request_dictionary);
2498 request_or_macro *rm;
2499 symbol s;
2500 while (iter.get(&s, (object **)&rm)) {
2501 assert(!s.is_null());
2502 macro *m = rm->to_macro();
2503 if (m) {
2504 errprint("%1\t", s.contents());
2505 m->print_size();
2506 errprint("\n");
2509 fflush(stderr);
2510 skip_line();
2513 class string_iterator : public input_iterator {
2514 macro mac;
2515 const char *how_invoked;
2516 int newline_flag;
2517 int lineno;
2518 char_block *bp;
2519 int count; // of characters remaining
2520 node *nd;
2521 protected:
2522 symbol nm;
2523 string_iterator();
2524 public:
2525 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
2526 int fill(node **);
2527 int peek();
2528 int get_location(int, const char **, int *);
2529 void backtrace();
2532 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
2533 : lineno(1), mac(m), newline_flag(0), how_invoked(p), nm(s)
2535 count = mac.length;
2536 if (count != 0) {
2537 bp = mac.p->cl.head;
2538 nd = mac.p->nl.head;
2539 ptr = eptr = bp->s;
2541 else {
2542 bp = 0;
2543 nd = 0;
2544 ptr = eptr = 0;
2548 string_iterator::string_iterator()
2550 bp = 0;
2551 nd = 0;
2552 ptr = eptr = 0;
2553 newline_flag = 0;
2554 how_invoked = 0;
2555 lineno = 1;
2556 count = 0;
2559 int string_iterator::fill(node **np)
2561 if (newline_flag)
2562 lineno++;
2563 newline_flag = 0;
2564 if (count <= 0)
2565 return EOF;
2566 const unsigned char *p = eptr;
2567 if (p >= bp->s + char_block::SIZE) {
2568 bp = bp->next;
2569 p = bp->s;
2571 if (*p == '\0') {
2572 if (np)
2573 *np = nd->copy();
2574 nd = nd->next;
2575 eptr = ptr = p + 1;
2576 count--;
2577 return 0;
2579 const unsigned char *e = bp->s + char_block::SIZE;
2580 if (e - p > count)
2581 e = p + count;
2582 ptr = p;
2583 while (p < e) {
2584 unsigned char c = *p;
2585 if (c == '\n' || c == ESCAPE_NEWLINE) {
2586 newline_flag = 1;
2587 p++;
2588 break;
2590 if (c == '\0')
2591 break;
2592 p++;
2594 eptr = p;
2595 count -= p - ptr;
2596 return *ptr++;
2599 int string_iterator::peek()
2601 if (count <= 0)
2602 return EOF;
2603 const unsigned char *p = eptr;
2604 if (count <= 0)
2605 return EOF;
2606 if (p >= bp->s + char_block::SIZE) {
2607 p = bp->next->s;
2609 return *p;
2612 int string_iterator::get_location(int allow_macro,
2613 const char **filep, int *linep)
2615 if (!allow_macro)
2616 return 0;
2617 if (mac.filename == 0)
2618 return 0;
2619 *filep = mac.filename;
2620 *linep = mac.lineno + lineno - 1;
2621 return 1;
2624 void string_iterator::backtrace()
2626 if (mac.filename) {
2627 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
2628 if (how_invoked) {
2629 if (!nm.is_null())
2630 errprint(": %1 `%2'\n", how_invoked, nm.contents());
2631 else
2632 errprint(": %1\n", how_invoked);
2634 else
2635 errprint("\n");
2639 class temp_iterator : public input_iterator {
2640 unsigned char *base;
2641 temp_iterator(const char *, int len);
2642 public:
2643 ~temp_iterator();
2644 friend input_iterator *make_temp_iterator(const char *);
2647 #ifdef __GNUG__
2648 inline
2649 #endif
2650 temp_iterator::temp_iterator(const char *s, int len)
2652 base = new unsigned char[len];
2653 memcpy(base, s, len);
2654 ptr = base;
2655 eptr = base + len;
2658 temp_iterator::~temp_iterator()
2660 a_delete base;
2663 class small_temp_iterator : public input_iterator {
2664 private:
2665 small_temp_iterator(const char *, int);
2666 ~small_temp_iterator();
2667 enum { BLOCK = 16 };
2668 static small_temp_iterator *free_list;
2669 void *operator new(size_t);
2670 void operator delete(void *);
2671 enum { SIZE = 12 };
2672 unsigned char buf[SIZE];
2673 friend input_iterator *make_temp_iterator(const char *);
2676 small_temp_iterator *small_temp_iterator::free_list = 0;
2678 void *small_temp_iterator::operator new(size_t n)
2680 assert(n == sizeof(small_temp_iterator));
2681 if (!free_list) {
2682 free_list = (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
2683 for (int i = 0; i < BLOCK - 1; i++)
2684 free_list[i].next = free_list + i + 1;
2685 free_list[BLOCK-1].next = 0;
2687 small_temp_iterator *p = free_list;
2688 free_list = (small_temp_iterator *)(free_list->next);
2689 p->next = 0;
2690 return p;
2693 #ifdef __GNUG__
2694 inline
2695 #endif
2696 void small_temp_iterator::operator delete(void *p)
2698 if (p) {
2699 ((small_temp_iterator *)p)->next = free_list;
2700 free_list = (small_temp_iterator *)p;
2704 small_temp_iterator::~small_temp_iterator()
2709 #ifdef __GNUG__
2710 inline
2711 #endif
2712 small_temp_iterator::small_temp_iterator(const char *s, int len)
2714 for (int i = 0; i < len; i++)
2715 buf[i] = s[i];
2716 ptr = buf;
2717 eptr = buf + len;
2720 input_iterator *make_temp_iterator(const char *s)
2722 if (s == 0)
2723 return new small_temp_iterator(s, 0);
2724 else {
2725 int n = strlen(s);
2726 if (n <= small_temp_iterator::SIZE)
2727 return new small_temp_iterator(s, n);
2728 else
2729 return new temp_iterator(s, n);
2733 // this is used when macros are interpolated using the .macro_name notation
2735 struct arg_list {
2736 macro mac;
2737 arg_list *next;
2738 arg_list(const macro &);
2739 ~arg_list();
2742 arg_list::arg_list(const macro &m) : mac(m), next(0)
2746 arg_list::~arg_list()
2750 class macro_iterator : public string_iterator {
2751 arg_list *args;
2752 int argc;
2753 public:
2754 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
2755 macro_iterator();
2756 ~macro_iterator();
2757 int has_args() { return 1; }
2758 input_iterator *get_arg(int i);
2759 int nargs() { return argc; }
2760 void add_arg(const macro &m);
2761 void shift(int n);
2764 input_iterator *macro_iterator::get_arg(int i)
2766 if (i == 0)
2767 return make_temp_iterator(nm.contents());
2768 if (i > 0 && i <= argc) {
2769 arg_list *p = args;
2770 for (int j = 1; j < i; j++) {
2771 assert(p != 0);
2772 p = p->next;
2774 return new string_iterator(p->mac);
2776 else
2777 return 0;
2780 void macro_iterator::add_arg(const macro &m)
2782 arg_list **p;
2783 for (p = &args; *p; p = &((*p)->next))
2785 *p = new arg_list(m);
2786 ++argc;
2789 void macro_iterator::shift(int n)
2791 while (n > 0 && argc > 0) {
2792 arg_list *tem = args;
2793 args = args->next;
2794 delete tem;
2795 --argc;
2796 --n;
2800 // This gets used by eg .if '\?xxx\?''.
2802 int operator==(const macro &m1, const macro &m2)
2804 if (m1.length != m2.length)
2805 return 0;
2806 string_iterator iter1(m1);
2807 string_iterator iter2(m2);
2808 int n = m1.length;
2809 while (--n >= 0) {
2810 node *nd1 = 0;
2811 int c1 = iter1.get(&nd1);
2812 assert(c1 != EOF);
2813 node *nd2 = 0;
2814 int c2 = iter2.get(&nd2);
2815 assert(c2 != EOF);
2816 if (c1 != c2) {
2817 if (c1 == 0)
2818 delete nd1;
2819 else if (c2 == 0)
2820 delete nd2;
2821 return 0;
2823 if (c1 == 0) {
2824 assert(nd1 != 0);
2825 assert(nd2 != 0);
2826 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
2827 delete nd1;
2828 delete nd2;
2829 if (!are_same)
2830 return 0;
2833 return 1;
2836 static void interpolate_macro(symbol nm)
2838 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
2839 if (p == 0) {
2840 int warned = 0;
2841 const char *s = nm.contents();
2842 if (strlen(s) > 2) {
2843 request_or_macro *r;
2844 char buf[3];
2845 buf[0] = s[0];
2846 buf[1] = s[1];
2847 buf[2] = '\0';
2848 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
2849 if (r) {
2850 macro *m = r->to_macro();
2851 if (!m || !m->empty())
2852 warned = warning(WARN_SPACE,
2853 "`%1' not defined (probable missing space after `%2')",
2854 nm.contents(), buf);
2857 if (!warned) {
2858 warning(WARN_MAC, "`%1' not defined", nm.contents());
2859 p = new macro;
2860 request_dictionary.define(nm, p);
2863 if (p)
2864 p->invoke(nm);
2865 else {
2866 skip_line();
2867 return;
2871 static void decode_args(macro_iterator *mi)
2873 if (!tok.newline() && !tok.eof()) {
2874 node *n;
2875 int c = get_copy(&n);
2876 for (;;) {
2877 while (c == ' ')
2878 c = get_copy(&n);
2879 if (c == '\n' || c == EOF)
2880 break;
2881 macro arg;
2882 int quote_input_level = 0;
2883 int done_tab_warning = 0;
2884 if (c == '\"') {
2885 quote_input_level = input_stack::get_level();
2886 c = get_copy(&n);
2888 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
2889 if (quote_input_level > 0 && c == '\"'
2890 && (compatible_flag
2891 || input_stack::get_level() == quote_input_level)) {
2892 c = get_copy(&n);
2893 if (c == '"') {
2894 arg.append(c);
2895 c = get_copy(&n);
2897 else
2898 break;
2900 else {
2901 if (c == 0)
2902 arg.append(n);
2903 else {
2904 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
2905 warning(WARN_TAB, "tab character in unquoted macro argument");
2906 done_tab_warning = 1;
2908 arg.append(c);
2910 c = get_copy(&n);
2913 mi->add_arg(arg);
2918 void macro::invoke(symbol nm)
2920 macro_iterator *mi = new macro_iterator(nm, *this);
2921 decode_args(mi);
2922 input_stack::push(mi);
2923 tok.next();
2926 macro *macro::to_macro()
2928 return this;
2931 int macro::empty()
2933 return length == 0;
2936 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
2937 : string_iterator(m, how_invoked, s), args(0), argc(0)
2941 macro_iterator::macro_iterator() : args(0), argc(0)
2945 macro_iterator::~macro_iterator()
2947 while (args != 0) {
2948 arg_list *tem = args;
2949 args = args->next;
2950 delete tem;
2954 int trap_sprung_flag = 0;
2955 int postpone_traps_flag = 0;
2956 symbol postponed_trap;
2958 void spring_trap(symbol nm)
2960 assert(!nm.is_null());
2961 trap_sprung_flag = 1;
2962 if (postpone_traps_flag) {
2963 postponed_trap = nm;
2964 return;
2966 static char buf[2] = { BEGIN_TRAP, 0 };
2967 static char buf2[2] = { END_TRAP, '\0' };
2968 input_stack::push(make_temp_iterator(buf2));
2969 request_or_macro *p = lookup_request(nm);
2970 macro *m = p->to_macro();
2971 if (m)
2972 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
2973 else
2974 error("you can't invoke a request with a trap");
2975 input_stack::push(make_temp_iterator(buf));
2978 void postpone_traps()
2980 postpone_traps_flag = 1;
2983 int unpostpone_traps()
2985 postpone_traps_flag = 0;
2986 if (!postponed_trap.is_null()) {
2987 spring_trap(postponed_trap);
2988 postponed_trap = NULL_SYMBOL;
2989 return 1;
2991 else
2992 return 0;
2995 void read_request()
2997 macro_iterator *mi = new macro_iterator;
2998 int reading_from_terminal = isatty(fileno(stdin));
2999 int had_prompt = 0;
3000 if (!tok.newline() && !tok.eof()) {
3001 int c = get_copy(NULL);
3002 while (c == ' ')
3003 c = get_copy(NULL);
3004 while (c != EOF && c != '\n' && c != ' ') {
3005 if (!illegal_input_char(c)) {
3006 if (reading_from_terminal)
3007 fputc(c, stderr);
3008 had_prompt = 1;
3010 c = get_copy(NULL);
3012 if (c == ' ') {
3013 tok.make_space();
3014 decode_args(mi);
3017 if (reading_from_terminal) {
3018 fputc(had_prompt ? ':' : '\007', stderr);
3019 fflush(stderr);
3021 input_stack::push(mi);
3022 macro mac;
3023 int nl = 0;
3024 int c;
3025 while ((c = getchar()) != EOF) {
3026 if (illegal_input_char(c))
3027 warning(WARN_INPUT, "illegal input character code %1", int(c));
3028 else {
3029 if (c == '\n') {
3030 if (nl)
3031 break;
3032 else
3033 nl = 1;
3035 else
3036 nl = 0;
3037 mac.append(c);
3040 if (reading_from_terminal)
3041 clearerr(stdin);
3042 input_stack::push(new string_iterator(mac));
3043 tok.next();
3046 void do_define_string(int append)
3048 symbol nm;
3049 node *n;
3050 int c;
3051 nm = get_name(1);
3052 if (nm.is_null()) {
3053 skip_line();
3054 return;
3056 if (tok.newline())
3057 c = '\n';
3058 else if (tok.tab())
3059 c = '\t';
3060 else if (!tok.space()) {
3061 error("bad string definition");
3062 skip_line();
3063 return;
3065 else
3066 c = get_copy(&n);
3067 while (c == ' ')
3068 c = get_copy(&n);
3069 if (c == '"')
3070 c = get_copy(&n);
3071 macro mac;
3072 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3073 macro *mm = rm ? rm->to_macro() : 0;
3074 if (append && mm)
3075 mac = *mm;
3076 while (c != '\n' && c != EOF) {
3077 if (c == 0)
3078 mac.append(n);
3079 else
3080 mac.append((unsigned char)c);
3081 c = get_copy(&n);
3083 if (!mm) {
3084 mm = new macro;
3085 request_dictionary.define(nm, mm);
3087 *mm = mac;
3088 tok.next();
3091 void define_string()
3093 do_define_string(0);
3096 void append_string()
3098 do_define_string(1);
3101 void define_character()
3103 node *n;
3104 int c;
3105 tok.skip();
3106 charinfo *ci = tok.get_char(1);
3107 if (ci == 0) {
3108 skip_line();
3109 return;
3111 tok.next();
3112 if (tok.newline())
3113 c = '\n';
3114 else if (tok.tab())
3115 c = '\t';
3116 else if (!tok.space()) {
3117 error("bad character definition");
3118 skip_line();
3119 return;
3121 else
3122 c = get_copy(&n);
3123 while (c == ' ' || c == '\t')
3124 c = get_copy(&n);
3125 if (c == '"')
3126 c = get_copy(&n);
3127 macro *m = new macro;
3128 while (c != '\n' && c != EOF) {
3129 if (c == 0)
3130 m->append(n);
3131 else
3132 m->append((unsigned char)c);
3133 c = get_copy(&n);
3135 m = ci->set_macro(m);
3136 if (m)
3137 delete m;
3138 tok.next();
3142 static void remove_character()
3144 tok.skip();
3145 while (!tok.newline() && !tok.eof()) {
3146 if (!tok.space() && !tok.tab()) {
3147 charinfo *ci = tok.get_char(1);
3148 if (!ci)
3149 break;
3150 macro *m = ci->set_macro(0);
3151 if (m)
3152 delete m;
3154 tok.next();
3156 skip_line();
3159 static void interpolate_string(symbol nm)
3161 request_or_macro *p = lookup_request(nm);
3162 macro *m = p->to_macro();
3163 if (!m)
3164 error("you can only invoke a string using \\*");
3165 else {
3166 string_iterator *si = new string_iterator(*m, "string", nm);
3167 input_stack::push(si);
3171 /* This class is used for the implementation of \$@. It is used for
3172 each of the closing double quotes. It artificially increases the
3173 input level by 2, so that the closing double quote will appear to have
3174 the same input level as the opening quote. */
3176 class end_quote_iterator : public input_iterator {
3177 unsigned char buf[1];
3178 public:
3179 end_quote_iterator();
3180 ~end_quote_iterator() { }
3181 int internal_level() { return 2; }
3184 end_quote_iterator::end_quote_iterator()
3186 buf[0] = '"';
3187 ptr = buf;
3188 eptr = buf + 1;
3191 static void interpolate_arg(symbol nm)
3193 const char *s = nm.contents();
3194 if (!s || *s == '\0')
3195 copy_mode_error("missing argument name");
3196 else if (s[1] == 0 && csdigit(s[0]))
3197 input_stack::push(input_stack::get_arg(s[0] - '0'));
3198 else if (s[0] == '*' && s[1] == '\0') {
3199 for (int i = input_stack::nargs(); i > 0; i--) {
3200 input_stack::push(input_stack::get_arg(i));
3201 if (i != 1)
3202 input_stack::push(make_temp_iterator(" "));
3205 else if (s[0] == '@' && s[1] == '\0') {
3206 for (int i = input_stack::nargs(); i > 0; i--) {
3207 input_stack::push(new end_quote_iterator);
3208 input_stack::push(input_stack::get_arg(i));
3209 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
3212 else {
3213 const char *p;
3214 for (p = s; *p && csdigit(*p); p++)
3216 if (*p)
3217 copy_mode_error("bad argument name `%1'", s);
3218 else
3219 input_stack::push(input_stack::get_arg(atoi(s)));
3223 void handle_first_page_transition()
3225 push_token(tok);
3226 topdiv->begin_page();
3229 // We push back a token by wrapping it up in a token_node, and
3230 // wrapping that up in a string_iterator.
3232 static void push_token(const token &t)
3234 macro m;
3235 m.append(new token_node(t));
3236 input_stack::push(new string_iterator(m));
3239 void push_page_ejector()
3241 static char buf[2] = { PAGE_EJECTOR, '\0' };
3242 input_stack::push(make_temp_iterator(buf));
3245 void handle_initial_request(unsigned char code)
3247 char buf[2];
3248 buf[0] = code;
3249 buf[1] = '\0';
3250 macro mac;
3251 mac.append(new token_node(tok));
3252 input_stack::push(new string_iterator(mac));
3253 input_stack::push(make_temp_iterator(buf));
3254 topdiv->begin_page();
3255 tok.next();
3258 void handle_initial_title()
3260 handle_initial_request(TITLE_REQUEST);
3263 // this should be local to define_macro, but cfront 1.2 doesn't support that
3264 static symbol dot_symbol(".");
3266 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3268 void do_define_macro(define_mode mode)
3270 symbol nm;
3271 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3272 nm = get_name(1);
3273 if (nm.is_null()) {
3274 skip_line();
3275 return;
3278 symbol term = get_name(); // the request that terminates the definition
3279 if (term.is_null())
3280 term = dot_symbol;
3281 while (!tok.newline() && !tok.eof())
3282 tok.next();
3283 const char *start_filename;
3284 int start_lineno;
3285 int have_start_location = input_stack::get_location(0, &start_filename,
3286 &start_lineno);
3287 node *n;
3288 // doing this here makes the line numbers come out right
3289 int c = get_copy(&n, 1);
3290 macro mac;
3291 macro *mm = 0;
3292 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3293 request_or_macro *rm =
3294 (request_or_macro *)request_dictionary.lookup(nm);
3295 if (rm)
3296 mm = rm->to_macro();
3297 if (mm && mode == DEFINE_APPEND)
3298 mac = *mm;
3300 int bol = 1;
3301 for (;;) {
3302 while (c == ESCAPE_NEWLINE) {
3303 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
3304 mac.append(c);
3305 c = get_copy(&n, 1);
3307 if (bol && c == '.') {
3308 const char *s = term.contents();
3309 int d;
3310 // see if it matches term
3311 int i;
3312 for (i = 0; s[i] != 0; i++) {
3313 d = get_copy(&n);
3314 if ((unsigned char)s[i] != d)
3315 break;
3317 if (s[i] == 0
3318 && ((i == 2 && compatible_flag)
3319 || (d = get_copy(&n)) == ' '
3320 || d == '\n')) { // we found it
3321 if (d == '\n')
3322 tok.make_newline();
3323 else
3324 tok.make_space();
3325 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3326 if (!mm) {
3327 mm = new macro;
3328 request_dictionary.define(nm, mm);
3330 *mm = mac;
3332 if (term != dot_symbol) {
3333 ignoring = 0;
3334 interpolate_macro(term);
3336 else
3337 skip_line();
3338 return;
3340 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3341 mac.append(c);
3342 for (int j = 0; j < i; j++)
3343 mac.append(s[j]);
3345 c = d;
3347 if (c == EOF) {
3348 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3349 if (have_start_location)
3350 error_with_file_and_line(start_filename, start_lineno,
3351 "end of file while defining macro `%1'",
3352 nm.contents());
3353 else
3354 error("end of file while defining macro `%1'", nm.contents());
3356 else {
3357 if (have_start_location)
3358 error_with_file_and_line(start_filename, start_lineno,
3359 "end of file while ignoring input lines");
3360 else
3361 error("end of file while ignoring input lines");
3363 tok.next();
3364 return;
3366 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3367 if (c == 0)
3368 mac.append(n);
3369 else
3370 mac.append(c);
3372 bol = (c == '\n');
3373 c = get_copy(&n, 1);
3377 void define_macro()
3379 do_define_macro(DEFINE_NORMAL);
3382 void append_macro()
3384 do_define_macro(DEFINE_APPEND);
3387 void ignore()
3389 ignoring = 1;
3390 do_define_macro(DEFINE_IGNORE);
3391 ignoring = 0;
3394 void remove_macro()
3396 for (;;) {
3397 symbol s = get_name();
3398 if (s.is_null())
3399 break;
3400 request_dictionary.remove(s);
3402 skip_line();
3405 void rename_macro()
3407 symbol s1 = get_name(1);
3408 if (!s1.is_null()) {
3409 symbol s2 = get_name(1);
3410 if (!s2.is_null())
3411 request_dictionary.rename(s1, s2);
3413 skip_line();
3416 void alias_macro()
3418 symbol s1 = get_name(1);
3419 if (!s1.is_null()) {
3420 symbol s2 = get_name(1);
3421 if (!s2.is_null()) {
3422 if (!request_dictionary.alias(s1, s2))
3423 warning(WARN_MAC, "`%1' not defined", s2.contents());
3426 skip_line();
3429 void chop_macro()
3431 symbol s = get_name(1);
3432 if (!s.is_null()) {
3433 request_or_macro *p = lookup_request(s);
3434 macro *m = p->to_macro();
3435 if (!m)
3436 error("cannot chop request");
3437 else if (m->length == 0)
3438 error("cannot chop empty macro");
3439 else
3440 m->length -= 1;
3442 skip_line();
3445 void substring_macro()
3447 int start;
3448 symbol s = get_name(1);
3449 if (!s.is_null() && get_integer(&start)) {
3450 request_or_macro *p = lookup_request(s);
3451 macro *m = p->to_macro();
3452 if (!m)
3453 error("cannot substring request");
3454 else {
3455 if (start <= 0)
3456 start += m->length;
3457 else
3458 start--;
3459 int end = 0;
3460 if (!has_arg() || get_integer(&end)) {
3461 if (end <= 0)
3462 end += m->length;
3463 else
3464 end--;
3465 if (start > end) {
3466 int tem = start;
3467 start = end;
3468 end = tem;
3470 if (start >= m->length || start == end) {
3471 m->length = 0;
3472 if (m->p) {
3473 if (--(m->p->count) <= 0)
3474 delete m->p;
3475 m->p = 0;
3478 else if (start == 0)
3479 m->length = end;
3480 else {
3481 string_iterator iter(*m);
3482 int i;
3483 for (i = 0; i < start; i++)
3484 if (iter.get(0) == EOF)
3485 break;
3486 macro mac;
3487 for (; i < end; i++) {
3488 node *nd;
3489 int c = iter.get(&nd);
3490 if (c == EOF)
3491 break;
3492 if (c == 0)
3493 mac.append(nd);
3494 else
3495 mac.append((unsigned char)c);
3497 *m = mac;
3502 skip_line();
3505 void asciify_macro()
3507 symbol s = get_name(1);
3508 if (!s.is_null()) {
3509 request_or_macro *p = lookup_request(s);
3510 macro *m = p->to_macro();
3511 if (!m)
3512 error("cannot asciify request");
3513 else {
3514 macro am;
3515 string_iterator iter(*m);
3516 for (;;) {
3517 node *nd;
3518 int c = iter.get(&nd);
3519 if (c == EOF)
3520 break;
3521 if (c != 0)
3522 am.append(c);
3523 else
3524 nd->asciify(&am);
3526 *m = am;
3529 skip_line();
3532 static void interpolate_environment_variable(symbol nm)
3534 const char *s = getenv(nm.contents());
3535 if (s && *s)
3536 input_stack::push(make_temp_iterator(s));
3539 void interpolate_number_reg(symbol nm, int inc)
3541 reg *r = lookup_number_reg(nm);
3542 if (inc < 0)
3543 r->decrement();
3544 else if (inc > 0)
3545 r->increment();
3546 input_stack::push(make_temp_iterator(r->get_string()));
3549 static void interpolate_number_format(symbol nm)
3551 reg *r = (reg *)number_reg_dictionary.lookup(nm);
3552 if (r)
3553 input_stack::push(make_temp_iterator(r->get_format()));
3556 static int get_delim_number(units *n, int si, int prev_value)
3558 token start;
3559 start.next();
3560 if (start.delimiter(1)) {
3561 tok.next();
3562 if (get_number(n, si, prev_value)) {
3563 if (start != tok)
3564 warning(WARN_DELIM, "closing delimiter does not match");
3565 return 1;
3568 return 0;
3571 static int get_delim_number(units *n, int si)
3573 token start;
3574 start.next();
3575 if (start.delimiter(1)) {
3576 tok.next();
3577 if (get_number(n, si)) {
3578 if (start != tok)
3579 warning(WARN_DELIM, "closing delimiter does not match");
3580 return 1;
3583 return 0;
3586 static int get_line_arg(units *n, int si, charinfo **cp)
3588 token start;
3589 start.next();
3590 if (start.delimiter(1)) {
3591 tok.next();
3592 if (get_number(n, si)) {
3593 if (tok.dummy())
3594 tok.next();
3595 if (start != tok) {
3596 *cp = tok.get_char(1);
3597 tok.next();
3599 if (start != tok)
3600 warning(WARN_DELIM, "closing delimiter does not match");
3601 return 1;
3604 return 0;
3607 static int read_size(int *x)
3609 tok.next();
3610 int c = tok.ch();
3611 int inc = 0;
3612 if (c == '-') {
3613 inc = -1;
3614 tok.next();
3615 c = tok.ch();
3617 else if (c == '+') {
3618 inc = 1;
3619 tok.next();
3620 c = tok.ch();
3622 int val;
3623 int bad = 0;
3624 if (c == '(') {
3625 tok.next();
3626 c = tok.ch();
3627 if (!inc) {
3628 // allow an increment either before or after the left parenthesis
3629 if (c == '-') {
3630 inc = -1;
3631 tok.next();
3632 c = tok.ch();
3634 else if (c == '+') {
3635 inc = 1;
3636 tok.next();
3637 c = tok.ch();
3640 if (!csdigit(c))
3641 bad = 1;
3642 else {
3643 val = c - '0';
3644 tok.next();
3645 c = tok.ch();
3646 if (!csdigit(c))
3647 bad = 1;
3648 else {
3649 val = val*10 + (c - '0');
3650 val *= sizescale;
3654 else if (csdigit(c)) {
3655 val = c - '0';
3656 if (!inc && c != '0' && c < '4') {
3657 tok.next();
3658 c = tok.ch();
3659 if (!csdigit(c))
3660 bad = 1;
3661 else
3662 val = val*10 + (c - '0');
3664 val *= sizescale;
3666 else if (!tok.delimiter(1))
3667 return 0;
3668 else {
3669 token start(tok);
3670 tok.next();
3671 if (!(inc
3672 ? get_number(&val, 'z')
3673 : get_number(&val, 'z', curenv->get_requested_point_size())))
3674 return 0;
3675 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
3676 if (start.ch() == '[')
3677 error("missing `]'");
3678 else
3679 error("missing closing delimiter");
3680 return 0;
3683 if (!bad) {
3684 switch (inc) {
3685 case 0:
3686 *x = val;
3687 break;
3688 case 1:
3689 *x = curenv->get_requested_point_size() + val;
3690 break;
3691 case -1:
3692 *x = curenv->get_requested_point_size() - val;
3693 break;
3694 default:
3695 assert(0);
3697 return 1;
3699 else {
3700 error("bad digit in point size");
3701 return 0;
3705 static symbol get_delim_name()
3707 token start;
3708 start.next();
3709 if (start.eof()) {
3710 error("end of input at start of delimited name");
3711 return NULL_SYMBOL;
3713 if (start.newline()) {
3714 error("can't delimit name with a newline");
3715 return NULL_SYMBOL;
3717 int start_level = input_stack::get_level();
3718 char abuf[ABUF_SIZE];
3719 char *buf = abuf;
3720 int buf_size = ABUF_SIZE;
3721 int i = 0;
3722 for (;;) {
3723 if (i + 1 > buf_size) {
3724 if (buf == abuf) {
3725 buf = new char [ABUF_SIZE*2];
3726 memcpy(buf, abuf, buf_size);
3727 buf_size = ABUF_SIZE*2;
3729 else {
3730 char *old_buf = buf;
3731 buf = new char[buf_size*2];
3732 memcpy(buf, old_buf, buf_size);
3733 buf_size *= 2;
3734 a_delete old_buf;
3737 tok.next();
3738 if (tok == start
3739 && (compatible_flag || input_stack::get_level() == start_level))
3740 break;
3741 if ((buf[i] = tok.ch()) == 0) {
3742 error("missing delimiter (got %1)", tok.description());
3743 if (buf != abuf)
3744 a_delete buf;
3745 return NULL_SYMBOL;
3747 i++;
3749 buf[i] = '\0';
3750 if (buf == abuf) {
3751 if (i == 0) {
3752 error("empty delimited name");
3753 return NULL_SYMBOL;
3755 else
3756 return symbol(buf);
3758 else {
3759 symbol s(buf);
3760 a_delete buf;
3761 return s;
3766 // Implement \R
3768 static void do_register()
3770 token start;
3771 start.next();
3772 if (!start.delimiter(1))
3773 return;
3774 tok.next();
3775 symbol nm = get_long_name(1);
3776 if (nm.is_null())
3777 return;
3778 while (tok.space())
3779 tok.next();
3780 reg *r = (reg *)number_reg_dictionary.lookup(nm);
3781 int prev_value;
3782 if (!r || !r->get_value(&prev_value))
3783 prev_value = 0;
3784 int val;
3785 if (!get_number(&val, 'u', prev_value))
3786 return;
3787 if (start != tok)
3788 warning(WARN_DELIM, "closing delimiter does not match");
3789 if (r)
3790 r->set_value(val);
3791 else
3792 set_number_reg(nm, val);
3795 // this implements the \w escape sequence
3797 static void do_width()
3799 token start;
3800 start.next();
3801 int start_level = input_stack::get_level();
3802 environment env(curenv);
3803 environment *oldenv = curenv;
3804 curenv = &env;
3805 for (;;) {
3806 tok.next();
3807 if (tok.eof()) {
3808 warning(WARN_DELIM, "missing closing delimiter");
3809 break;
3811 if (tok.newline()) {
3812 warning(WARN_DELIM, "missing closing delimiter");
3813 input_stack::push(make_temp_iterator("\n"));
3814 break;
3816 if (tok == start
3817 && (compatible_flag || input_stack::get_level() == start_level))
3818 break;
3819 tok.process();
3821 env.wrap_up_tab();
3822 units x = env.get_input_line_position().to_units();
3823 input_stack::push(make_temp_iterator(itoa(x)));
3824 env.width_registers();
3825 curenv = oldenv;
3828 charinfo *page_character;
3830 void set_page_character()
3832 page_character = get_optional_char();
3833 skip_line();
3836 static const symbol percent_symbol("%");
3838 void read_title_parts(node **part, hunits *part_width)
3840 tok.skip();
3841 if (tok.newline() || tok.eof())
3842 return;
3843 token start(tok);
3844 int start_level = input_stack::get_level();
3845 tok.next();
3846 for (int i = 0; i < 3; i++) {
3847 while (!tok.newline() && !tok.eof()) {
3848 if (tok == start
3849 && (compatible_flag || input_stack::get_level() == start_level)) {
3850 tok.next();
3851 break;
3853 if (page_character != 0 && tok.get_char() == page_character)
3854 interpolate_number_reg(percent_symbol, 0);
3855 else
3856 tok.process();
3857 tok.next();
3859 curenv->wrap_up_tab();
3860 part_width[i] = curenv->get_input_line_position();
3861 part[i] = curenv->extract_output_line();
3863 while (!tok.newline() && !tok.eof())
3864 tok.next();
3867 class non_interpreted_node : public node {
3868 macro mac;
3869 public:
3870 non_interpreted_node(const macro &);
3871 int interpret(macro *);
3872 node *copy();
3873 int same(node *);
3874 const char *type();
3877 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
3881 int non_interpreted_node::same(node *nd)
3883 return mac == ((non_interpreted_node *)nd)->mac;
3886 const char *non_interpreted_node::type()
3888 return "non_interpreted_node";
3891 node *non_interpreted_node::copy()
3893 return new non_interpreted_node(mac);
3896 int non_interpreted_node::interpret(macro *m)
3898 string_iterator si(mac);
3899 node *n;
3900 for (;;) {
3901 int c = si.get(&n);
3902 if (c == EOF)
3903 break;
3904 if (c == 0)
3905 m->append(n);
3906 else
3907 m->append(c);
3909 return 1;
3912 static node *do_non_interpreted()
3914 node *n;
3915 int c;
3916 macro mac;
3917 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
3918 if (c == 0)
3919 mac.append(n);
3920 else
3921 mac.append(c);
3922 if (c == EOF || c == '\n') {
3923 error("missing \\?");
3924 return 0;
3926 return new non_interpreted_node(mac);
3929 node *do_special()
3931 token start;
3932 start.next();
3933 int start_level = input_stack::get_level();
3934 macro mac;
3935 for (tok.next();
3936 tok != start || input_stack::get_level() != start_level;
3937 tok.next()) {
3938 if (tok.eof()) {
3939 warning(WARN_DELIM, "missing closing delimiter");
3940 return 0;
3942 if (tok.newline()) {
3943 input_stack::push(make_temp_iterator("\n"));
3944 warning(WARN_DELIM, "missing closing delimiter");
3945 break;
3947 unsigned char c;
3948 if (tok.space())
3949 c = ' ';
3950 else if (tok.tab())
3951 c = '\t';
3952 else if (tok.leader())
3953 c = '\001';
3954 else if (tok.backspace())
3955 c = '\b';
3956 else
3957 c = tok.ch();
3958 if (c == '\0')
3959 error("%1 is illegal within \\X", tok.description());
3960 else
3961 mac.append(c);
3963 return new special_node(mac);
3966 void special_node::tprint(troff_output_file *out)
3968 tprint_start(out);
3969 string_iterator iter(mac);
3970 for (;;) {
3971 int c = iter.get(NULL);
3972 if (c == EOF)
3973 break;
3974 for (const char *s = ::asciify(c); *s; s++)
3975 tprint_char(out, *s);
3977 tprint_end(out);
3980 int get_file_line(const char **filename, int *lineno)
3982 return input_stack::get_location(0, filename, lineno);
3985 void line_file()
3987 int n;
3988 if (get_integer(&n)) {
3989 const char *filename = 0;
3990 if (has_arg()) {
3991 symbol s = get_long_name();
3992 filename = s.contents();
3994 (void)input_stack::set_location(filename, n-1);
3996 skip_line();
3999 static int nroff_mode = 0;
4001 static void nroff_request()
4003 nroff_mode = 1;
4004 skip_line();
4007 static void troff_request()
4009 nroff_mode = 0;
4010 skip_line();
4013 static void skip_alternative()
4015 int level = 0;
4016 // ensure that ``.if 0\{'' works as expected
4017 if (tok.left_brace())
4018 level++;
4019 int c;
4020 for (;;) {
4021 c = input_stack::get(NULL);
4022 if (c == EOF)
4023 break;
4024 if (c == ESCAPE_LEFT_BRACE)
4025 ++level;
4026 else if (c == ESCAPE_RIGHT_BRACE)
4027 --level;
4028 else if (c == escape_char && escape_char > 0)
4029 switch(input_stack::get(NULL)) {
4030 case '{':
4031 ++level;
4032 break;
4033 case '}':
4034 --level;
4035 break;
4036 case '"':
4037 while ((c = input_stack::get(NULL)) != '\n' && c != EOF)
4041 Note that the level can properly be < 0, eg
4043 .if 1 \{\
4044 .if 0 \{\
4045 .\}\}
4047 So don't give an error message in this case.
4049 if (level <= 0 && c == '\n')
4050 break;
4052 tok.next();
4055 static void begin_alternative()
4057 while (tok.space() || tok.left_brace())
4058 tok.next();
4062 static int_stack if_else_stack;
4064 int do_if_request()
4066 int invert = 0;
4067 while (tok.space())
4068 tok.next();
4069 while (tok.ch() == '!') {
4070 tok.next();
4071 invert = !invert;
4073 int result;
4074 unsigned char c = tok.ch();
4075 if (c == 't') {
4076 tok.next();
4077 result = !nroff_mode;
4079 else if (c == 'n') {
4080 tok.next();
4081 result = nroff_mode;
4083 else if (c == 'v') {
4084 tok.next();
4085 result = 0;
4087 else if (c == 'o') {
4088 result = (topdiv->get_page_number() & 1);
4089 tok.next();
4091 else if (c == 'e') {
4092 result = !(topdiv->get_page_number() & 1);
4093 tok.next();
4095 else if (c == 'd' || c == 'r') {
4096 tok.next();
4097 symbol nm = get_name(1);
4098 if (nm.is_null()) {
4099 skip_alternative();
4100 return 0;
4102 result = (c == 'd'
4103 ? request_dictionary.lookup(nm) != 0
4104 : number_reg_dictionary.lookup(nm) != 0);
4106 else if (c == 'c') {
4107 tok.next();
4108 tok.skip();
4109 charinfo *ci = tok.get_char(1);
4110 if (ci == 0) {
4111 skip_alternative();
4112 return 0;
4114 result = character_exists(ci, curenv);
4115 tok.next();
4117 else if (tok.space())
4118 result = 0;
4119 else if (tok.delimiter()) {
4120 token delim = tok;
4121 int delim_level = input_stack::get_level();
4122 environment env1(curenv);
4123 environment env2(curenv);
4124 environment *oldenv = curenv;
4125 curenv = &env1;
4126 for (int i = 0; i < 2; i++) {
4127 for (;;) {
4128 tok.next();
4129 if (tok.newline() || tok.eof()) {
4130 warning(WARN_DELIM, "missing closing delimiter");
4131 tok.next();
4132 curenv = oldenv;
4133 return 0;
4135 if (tok == delim
4136 && (compatible_flag || input_stack::get_level() == delim_level))
4137 break;
4138 tok.process();
4140 curenv = &env2;
4142 node *n1 = env1.extract_output_line();
4143 node *n2 = env2.extract_output_line();
4144 result = same_node_list(n1, n2);
4145 delete_node_list(n1);
4146 delete_node_list(n2);
4147 curenv = oldenv;
4148 tok.next();
4150 else {
4151 units n;
4152 if (!get_number(&n, 'u')) {
4153 skip_alternative();
4154 return 0;
4156 else
4157 result = n > 0;
4159 if (invert)
4160 result = !result;
4161 if (result)
4162 begin_alternative();
4163 else
4164 skip_alternative();
4165 return result;
4168 void if_else_request()
4170 if_else_stack.push(do_if_request());
4173 void if_request()
4175 do_if_request();
4178 void else_request()
4180 if (if_else_stack.is_empty()) {
4181 warning(WARN_EL, "unbalanced .el request");
4182 skip_alternative();
4184 else {
4185 if (if_else_stack.pop())
4186 skip_alternative();
4187 else
4188 begin_alternative();
4192 static int while_depth = 0;
4193 static int while_break_flag = 0;
4195 void while_request()
4197 macro mac;
4198 int escaped = 0;
4199 int level = 0;
4200 mac.append(new token_node(tok));
4201 for (;;) {
4202 node *n;
4203 int c = input_stack::get(&n);
4204 if (c == EOF)
4205 break;
4206 if (c == 0) {
4207 escaped = 0;
4208 mac.append(n);
4210 else if (escaped) {
4211 if (c == '{')
4212 level += 1;
4213 else if (c == '}')
4214 level -= 1;
4215 escaped = 0;
4216 mac.append(c);
4218 else {
4219 if (c == ESCAPE_LEFT_BRACE)
4220 level += 1;
4221 else if (c == ESCAPE_RIGHT_BRACE)
4222 level -= 1;
4223 else if (c == escape_char)
4224 escaped = 1;
4225 mac.append(c);
4226 if (c == '\n' && level <= 0)
4227 break;
4230 if (level != 0)
4231 error("unbalanced \\{ \\}");
4232 else {
4233 while_depth++;
4234 input_stack::add_boundary();
4235 for (;;) {
4236 input_stack::push(new string_iterator(mac, "while loop"));
4237 tok.next();
4238 if (!do_if_request()) {
4239 while (input_stack::get(NULL) != EOF)
4241 break;
4243 process_input_stack();
4244 if (while_break_flag) {
4245 while_break_flag = 0;
4246 break;
4249 input_stack::remove_boundary();
4250 while_depth--;
4252 tok.next();
4255 void while_break_request()
4257 if (!while_depth) {
4258 error("no while loop");
4259 skip_line();
4261 else {
4262 while_break_flag = 1;
4263 while (input_stack::get(NULL) != EOF)
4265 tok.next();
4269 void while_continue_request()
4271 if (!while_depth) {
4272 error("no while loop");
4273 skip_line();
4275 else {
4276 while (input_stack::get(NULL) != EOF)
4278 tok.next();
4282 // .so
4284 void source()
4286 symbol nm = get_long_name(1);
4287 if (nm.is_null())
4288 skip_line();
4289 else {
4290 while (!tok.newline() && !tok.eof())
4291 tok.next();
4292 errno = 0;
4293 FILE *fp = fopen(nm.contents(), "r");
4294 if (fp)
4295 input_stack::push(new file_iterator(fp, nm.contents()));
4296 else
4297 error("can't open `%1': %2", nm.contents(), strerror(errno));
4298 tok.next();
4302 // like .so but use popen()
4304 void pipe_source()
4306 #ifdef POPEN_MISSING
4307 error("pipes not available on this system");
4308 skip_line();
4309 #else /* not POPEN_MISSING */
4310 if (tok.newline() || tok.eof())
4311 error("missing command");
4312 else {
4313 int c;
4314 while ((c = get_copy(NULL)) == ' ' || c == '\t')
4316 int buf_size = 24;
4317 char *buf = new char[buf_size];
4318 int buf_used = 0;
4319 for (; c != '\n' && c != EOF; c = get_copy(NULL)) {
4320 const char *s = asciify(c);
4321 int slen = strlen(s);
4322 if (buf_used + slen + 1> buf_size) {
4323 char *old_buf = buf;
4324 int old_buf_size = buf_size;
4325 buf_size *= 2;
4326 buf = new char[buf_size];
4327 memcpy(buf, old_buf, old_buf_size);
4328 a_delete old_buf;
4330 strcpy(buf + buf_used, s);
4331 buf_used += slen;
4333 buf[buf_used] = '\0';
4334 errno = 0;
4335 FILE *fp = popen(buf, "r");
4336 if (fp)
4337 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
4338 else
4339 error("can't open pipe to process `%1': %2", buf, strerror(errno));
4340 a_delete buf;
4342 tok.next();
4343 #endif /* not POPEN_MISSING */
4346 const char *asciify(int c)
4348 static char buf[3];
4349 buf[0] = escape_char == '\0' ? '\\' : escape_char;
4350 buf[1] = buf[2] = '\0';
4351 switch (c) {
4352 case ESCAPE_QUESTION:
4353 buf[1] = '?';
4354 break;
4355 case ESCAPE_AMPERSAND:
4356 buf[1] = '&';
4357 break;
4358 case ESCAPE_UNDERSCORE:
4359 buf[1] = '_';
4360 break;
4361 case ESCAPE_BAR:
4362 buf[1] = '|';
4363 break;
4364 case ESCAPE_CIRCUMFLEX:
4365 buf[1] = '^';
4366 break;
4367 case ESCAPE_LEFT_BRACE:
4368 buf[1] = '{';
4369 break;
4370 case ESCAPE_RIGHT_BRACE:
4371 buf[1] = '}';
4372 break;
4373 case ESCAPE_LEFT_QUOTE:
4374 buf[1] = '`';
4375 break;
4376 case ESCAPE_RIGHT_QUOTE:
4377 buf[1] = '\'';
4378 break;
4379 case ESCAPE_HYPHEN:
4380 buf[1] = '-';
4381 break;
4382 case ESCAPE_BANG:
4383 buf[1] = '!';
4384 break;
4385 case ESCAPE_c:
4386 buf[1] = 'c';
4387 break;
4388 case ESCAPE_e:
4389 buf[1] = 'e';
4390 break;
4391 case ESCAPE_E:
4392 buf[1] = 'E';
4393 break;
4394 case ESCAPE_PERCENT:
4395 buf[1] = '%';
4396 break;
4397 case ESCAPE_SPACE:
4398 buf[1] = ' ';
4399 break;
4400 default:
4401 if (illegal_input_char(c))
4402 buf[0] = '\0';
4403 else
4404 buf[0] = c;
4405 break;
4407 return buf;
4411 const char *input_char_description(int c)
4413 switch (c) {
4414 case '\n':
4415 return "a newline character";
4416 case '\b':
4417 return "a backspace character";
4418 case '\001':
4419 return "a leader character";
4420 case '\t':
4421 return "a tab character ";
4422 case ' ':
4423 return "a space character";
4424 case '\0':
4425 return "a node";
4427 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
4428 if (illegal_input_char(c)) {
4429 const char *s = asciify(c);
4430 if (*s) {
4431 buf[0] = '`';
4432 strcpy(buf + 1, s);
4433 strcat(buf, "'");
4434 return buf;
4436 sprintf(buf, "magic character code %d", c);
4437 return buf;
4439 if (csprint(c)) {
4440 buf[0] = '`';
4441 buf[1] = c;
4442 buf[2] = '\'';
4443 return buf;
4445 sprintf(buf, "character code %d", c);
4446 return buf;
4449 // .tm
4451 void terminal()
4453 if (!tok.newline() && !tok.eof()) {
4454 int c;
4455 while ((c = get_copy(NULL)) == ' ' || c == '\t')
4457 for (; c != '\n' && c != EOF; c = get_copy(NULL))
4458 fputs(asciify(c), stderr);
4460 fputc('\n', stderr);
4461 fflush(stderr);
4462 tok.next();
4465 dictionary stream_dictionary(20);
4467 void do_open(int append)
4469 symbol stream = get_name(1);
4470 if (!stream.is_null()) {
4471 symbol filename = get_long_name(1);
4472 if (!filename.is_null()) {
4473 errno = 0;
4474 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
4475 if (!fp) {
4476 error("can't open `%1' for %2: %3",
4477 filename.contents(),
4478 append ? "appending" : "writing",
4479 strerror(errno));
4480 fp = (FILE *)stream_dictionary.remove(stream);
4482 else
4483 fp = (FILE *)stream_dictionary.lookup(stream, fp);
4484 if (fp)
4485 fclose(fp);
4488 skip_line();
4491 void open_request()
4493 do_open(0);
4496 void opena_request()
4498 do_open(1);
4501 void close_request()
4503 symbol stream = get_name(1);
4504 if (!stream.is_null()) {
4505 FILE *fp = (FILE *)stream_dictionary.remove(stream);
4506 if (!fp)
4507 error("no stream named `%1'", stream.contents());
4508 else
4509 fclose(fp);
4511 skip_line();
4514 void write_request()
4516 symbol stream = get_name(1);
4517 if (stream.is_null()) {
4518 skip_line();
4519 return;
4521 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
4522 if (!fp) {
4523 error("no stream named `%1'", stream.contents());
4524 skip_line();
4525 return;
4527 int c;
4528 while ((c = get_copy(NULL)) == ' ')
4530 if (c == '"')
4531 c = get_copy(NULL);
4532 for (; c != '\n' && c != EOF; c = get_copy(NULL))
4533 fputs(asciify(c), fp);
4534 fputc('\n', fp);
4535 fflush(fp);
4536 tok.next();
4539 void write_macro_request()
4541 symbol stream = get_name(1);
4542 if (stream.is_null()) {
4543 skip_line();
4544 return;
4546 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
4547 if (!fp) {
4548 error("no stream named `%1'", stream.contents());
4549 skip_line();
4550 return;
4552 symbol s = get_name(1);
4553 if (s.is_null()) {
4554 skip_line();
4555 return;
4557 request_or_macro *p = lookup_request(s);
4558 macro *m = p->to_macro();
4559 if (!m)
4560 error("cannot write request");
4561 else {
4562 string_iterator iter(*m);
4563 for (;;) {
4564 int c = iter.get(0);
4565 if (c == EOF)
4566 break;
4567 fputs(asciify(c), fp);
4569 fflush(fp);
4571 skip_line();
4574 static void init_charset_table()
4576 char buf[16];
4577 strcpy(buf, "char");
4578 for (int i = 0; i < 256; i++) {
4579 strcpy(buf + 4, itoa(i));
4580 charset_table[i] = get_charinfo(symbol(buf));
4581 charset_table[i]->set_ascii_code(i);
4582 if (csalpha(i))
4583 charset_table[i]->set_hyphenation_code(cmlower(i));
4585 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
4586 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
4587 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
4588 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
4589 charset_table['"']->set_flags(charinfo::TRANSPARENT);
4590 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
4591 charset_table[')']->set_flags(charinfo::TRANSPARENT);
4592 charset_table[']']->set_flags(charinfo::TRANSPARENT);
4593 charset_table['*']->set_flags(charinfo::TRANSPARENT);
4594 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
4595 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
4596 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
4597 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4598 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4599 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4600 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
4601 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
4602 page_character = charset_table['%'];
4605 static
4606 void do_translate(int translate_transparent)
4608 tok.skip();
4609 while (!tok.newline() && !tok.eof()) {
4610 if (tok.space()) {
4611 // This is a really bizarre troff feature.
4612 tok.next();
4613 translate_space_to_dummy = tok.dummy();
4614 if (tok.newline() || tok.eof())
4615 break;
4616 tok.next();
4617 continue;
4619 charinfo *ci1 = tok.get_char(1);
4620 if (ci1 == 0)
4621 break;
4622 tok.next();
4623 if (tok.newline() || tok.eof()) {
4624 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
4625 translate_transparent);
4626 break;
4628 if (tok.space())
4629 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
4630 translate_transparent);
4631 else if (tok.dummy())
4632 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
4633 translate_transparent);
4634 else if (tok.hyphen_indicator())
4635 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
4636 translate_transparent);
4637 else {
4638 charinfo *ci2 = tok.get_char(1);
4639 if (ci2 == 0)
4640 break;
4641 if (ci1 == ci2)
4642 ci1->set_translation(0, translate_transparent);
4643 else
4644 ci1->set_translation(ci2, translate_transparent);
4646 tok.next();
4648 skip_line();
4651 void translate()
4653 do_translate(1);
4656 void translate_no_transparent()
4658 do_translate(0);
4661 void char_flags()
4663 int flags;
4664 if (get_integer(&flags))
4665 while (has_arg()) {
4666 charinfo *ci = tok.get_char(1);
4667 if (ci) {
4668 charinfo *tem = ci->get_translation();
4669 if (tem)
4670 ci = tem;
4671 ci->set_flags(flags);
4673 tok.next();
4675 skip_line();
4678 void hyphenation_code()
4680 tok.skip();
4681 while (!tok.newline() && !tok.eof()) {
4682 charinfo *ci = tok.get_char(1);
4683 if (ci == 0)
4684 break;
4685 tok.next();
4686 tok.skip();
4687 unsigned char c = tok.ch();
4688 if (c == 0) {
4689 error("hyphenation code must be ordinary character");
4690 break;
4692 if (csdigit(c)) {
4693 error("hyphenation code cannot be digit");
4694 break;
4696 ci->set_hyphenation_code(c);
4697 tok.next();
4698 tok.skip();
4700 skip_line();
4703 charinfo *token::get_char(int required)
4705 if (type == TOKEN_CHAR)
4706 return charset_table[c];
4707 if (type == TOKEN_SPECIAL)
4708 return get_charinfo(nm);
4709 if (type == TOKEN_NUMBERED_CHAR)
4710 return get_charinfo_by_number(val);
4711 if (type == TOKEN_ESCAPE) {
4712 if (escape_char != 0)
4713 return charset_table[escape_char];
4714 else {
4715 error("`\\e' used while no current escape character");
4716 return 0;
4719 if (required) {
4720 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
4721 warning(WARN_MISSING, "missing normal or special character");
4722 else
4723 error("normal or special character expected (got %1)", description());
4725 return 0;
4728 charinfo *get_optional_char()
4730 while (tok.space())
4731 tok.next();
4732 charinfo *ci = tok.get_char();
4733 if (!ci)
4734 check_missing_character();
4735 else
4736 tok.next();
4737 return ci;
4740 void check_missing_character()
4742 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
4743 error("normal or special character expected (got %1): "
4744 "treated as missing",
4745 tok.description());
4748 int token::add_to_node_list(node **pp)
4750 hunits w;
4751 node *n = 0;
4752 switch (type) {
4753 case TOKEN_CHAR:
4754 *pp = (*pp)->add_char(charset_table[c], curenv, &w);
4755 break;
4756 case TOKEN_DUMMY:
4757 n = new dummy_node;
4758 break;
4759 case TOKEN_ESCAPE:
4760 if (escape_char != 0)
4761 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w);
4762 break;
4763 case TOKEN_HYPHEN_INDICATOR:
4764 *pp = (*pp)->add_discretionary_hyphen();
4765 break;
4766 case TOKEN_ITALIC_CORRECTION:
4767 *pp = (*pp)->add_italic_correction(&w);
4768 break;
4769 case TOKEN_LEFT_BRACE:
4770 break;
4771 case TOKEN_MARK_INPUT:
4772 set_number_reg(nm, curenv->get_input_line_position().to_units());
4773 break;
4774 case TOKEN_NODE:
4775 n = nd;
4776 nd = 0;
4777 break;
4778 case TOKEN_NUMBERED_CHAR:
4779 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w);
4780 break;
4781 case TOKEN_RIGHT_BRACE:
4782 break;
4783 case TOKEN_SPACE:
4784 n = new hmotion_node(curenv->get_space_width());
4785 break;
4786 case TOKEN_SPECIAL:
4787 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w);
4788 break;
4789 default:
4790 return 0;
4792 if (n) {
4793 n->next = *pp;
4794 *pp = n;
4796 return 1;
4799 void token::process()
4801 if (possibly_handle_first_page_transition())
4802 return;
4803 switch (type) {
4804 case TOKEN_BACKSPACE:
4805 curenv->add_node(new hmotion_node(-curenv->get_space_width()));
4806 break;
4807 case TOKEN_CHAR:
4808 curenv->add_char(charset_table[c]);
4809 break;
4810 case TOKEN_DUMMY:
4811 curenv->add_node(new dummy_node);
4812 break;
4813 case TOKEN_EOF:
4814 assert(0);
4815 break;
4816 case TOKEN_EMPTY:
4817 assert(0);
4818 break;
4819 case TOKEN_ESCAPE:
4820 if (escape_char != 0)
4821 curenv->add_char(charset_table[escape_char]);
4822 break;
4823 case TOKEN_BEGIN_TRAP:
4824 case TOKEN_END_TRAP:
4825 case TOKEN_PAGE_EJECTOR:
4826 // these are all handled in process_input_stack()
4827 break;
4828 case TOKEN_HYPHEN_INDICATOR:
4829 curenv->add_hyphen_indicator();
4830 break;
4831 case TOKEN_INTERRUPT:
4832 curenv->interrupt();
4833 break;
4834 case TOKEN_ITALIC_CORRECTION:
4835 curenv->add_italic_correction();
4836 break;
4837 case TOKEN_LEADER:
4838 curenv->handle_tab(1);
4839 break;
4840 case TOKEN_LEFT_BRACE:
4841 break;
4842 case TOKEN_MARK_INPUT:
4843 set_number_reg(nm, curenv->get_input_line_position().to_units());
4844 break;
4845 case TOKEN_NEWLINE:
4846 curenv->newline();
4847 break;
4848 case TOKEN_NODE:
4849 curenv->add_node(nd);
4850 nd = 0;
4851 break;
4852 case TOKEN_NUMBERED_CHAR:
4853 curenv->add_char(get_charinfo_by_number(val));
4854 break;
4855 case TOKEN_REQUEST:
4856 // handled in process_input_stack
4857 break;
4858 case TOKEN_RIGHT_BRACE:
4859 break;
4860 case TOKEN_SPACE:
4861 curenv->space();
4862 break;
4863 case TOKEN_SPECIAL:
4864 curenv->add_char(get_charinfo(nm));
4865 break;
4866 case TOKEN_SPREAD:
4867 curenv->spread();
4868 break;
4869 case TOKEN_TAB:
4870 curenv->handle_tab(0);
4871 break;
4872 case TOKEN_TRANSPARENT:
4873 break;
4874 default:
4875 assert(0);
4879 class nargs_reg : public reg {
4880 public:
4881 const char *get_string();
4884 const char *nargs_reg::get_string()
4886 return itoa(input_stack::nargs());
4889 class lineno_reg : public reg {
4890 public:
4891 const char *get_string();
4894 const char *lineno_reg::get_string()
4896 int line;
4897 const char *file;
4898 if (!input_stack::get_location(0, &file, &line))
4899 line = 0;
4900 return itoa(line);
4904 class writable_lineno_reg : public general_reg {
4905 public:
4906 writable_lineno_reg();
4907 void set_value(units);
4908 int get_value(units *);
4911 writable_lineno_reg::writable_lineno_reg()
4915 int writable_lineno_reg::get_value(units *res)
4917 int line;
4918 const char *file;
4919 if (!input_stack::get_location(0, &file, &line))
4920 return 0;
4921 *res = line;
4922 return 1;
4925 void writable_lineno_reg::set_value(units n)
4927 input_stack::set_location(0, n);
4930 class filename_reg : public reg {
4931 public:
4932 const char *get_string();
4935 const char *filename_reg::get_string()
4937 int line;
4938 const char *file;
4939 if (input_stack::get_location(0, &file, &line))
4940 return file;
4941 else
4942 return 0;
4946 class constant_reg : public reg {
4947 const char *s;
4948 public:
4949 constant_reg(const char *);
4950 const char *get_string();
4953 constant_reg::constant_reg(const char *p) : s(p)
4957 const char *constant_reg::get_string()
4959 return s;
4962 constant_int_reg::constant_int_reg(int *q) : p(q)
4966 const char *constant_int_reg::get_string()
4968 return itoa(*p);
4971 void abort_request()
4973 int c;
4974 if (tok.eof())
4975 c = EOF;
4976 else if (tok.newline())
4977 c = '\n';
4978 else {
4979 while ((c = get_copy(0)) == ' ')
4982 if (c == EOF || c == '\n')
4983 fputs("User Abort.", stderr);
4984 else {
4985 for (; c != '\n' && c != EOF; c = get_copy(NULL))
4986 fputs(asciify(c), stderr);
4988 fputc('\n', stderr);
4989 cleanup_and_exit(1);
4992 char *read_string()
4994 int len = 256;
4995 char *s = new char[len];
4996 int c;
4997 while ((c = get_copy(0)) == ' ')
4999 int i = 0;
5000 while (c != '\n' && c != EOF) {
5001 if (!illegal_input_char(c)) {
5002 if (i + 2 > len) {
5003 char *tem = s;
5004 s = new char[len*2];
5005 memcpy(s, tem, len);
5006 len *= 2;
5007 a_delete tem;
5009 s[i++] = c;
5011 c = get_copy(0);
5013 s[i] = '\0';
5014 tok.next();
5015 if (i == 0) {
5016 a_delete s;
5017 return 0;
5019 return s;
5022 void pipe_output()
5024 #ifdef POPEN_MISSING
5025 error("pipes not available on this system");
5026 skip_line();
5027 #else /* not POPEN_MISSING */
5028 if (the_output) {
5029 error("can't pipe: output already started");
5030 skip_line();
5032 else {
5033 if ((pipe_command = read_string()) == 0)
5034 error("can't pipe to empty command");
5036 #endif /* not POPEN_MISSING */
5039 static int system_status;
5041 void system_request()
5043 char *command = read_string();
5044 if (!command)
5045 error("empty command");
5046 else {
5047 system_status = system(command);
5048 a_delete command;
5052 void copy_file()
5054 if (curdiv == topdiv && topdiv->before_first_page) {
5055 handle_initial_request(COPY_FILE_REQUEST);
5056 return;
5058 symbol filename = get_long_name(1);
5059 while (!tok.newline() && !tok.eof())
5060 tok.next();
5061 if (break_flag)
5062 curenv->do_break();
5063 if (!filename.is_null())
5064 curdiv->copy_file(filename.contents());
5065 tok.next();
5068 #ifdef COLUMN
5070 void vjustify()
5072 if (curdiv == topdiv && topdiv->before_first_page) {
5073 handle_initial_request(VJUSTIFY_REQUEST);
5074 return;
5076 symbol type = get_long_name(1);
5077 if (!type.is_null())
5078 curdiv->vjustify(type);
5079 skip_line();
5082 #endif /* COLUMN */
5084 void transparent_file()
5086 if (curdiv == topdiv && topdiv->before_first_page) {
5087 handle_initial_request(TRANSPARENT_FILE_REQUEST);
5088 return;
5090 symbol filename = get_long_name(1);
5091 while (!tok.newline() && !tok.eof())
5092 tok.next();
5093 if (break_flag)
5094 curenv->do_break();
5095 if (!filename.is_null()) {
5096 errno = 0;
5097 FILE *fp = fopen(filename.contents(), "r");
5098 if (!fp)
5099 error("can't open `%1': %2", filename.contents(), strerror(errno));
5100 else {
5101 int bol = 1;
5102 for (;;) {
5103 int c = getc(fp);
5104 if (c == EOF)
5105 break;
5106 if (illegal_input_char(c))
5107 warning(WARN_INPUT, "illegal input character code %1", int(c));
5108 else {
5109 curdiv->transparent_output(c);
5110 bol = c == '\n';
5113 if (!bol)
5114 curdiv->transparent_output('\n');
5115 fclose(fp);
5118 tok.next();
5121 class page_range {
5122 int first;
5123 int last;
5124 public:
5125 page_range *next;
5126 page_range(int, int, page_range *);
5127 int contains(int n);
5130 page_range::page_range(int i, int j, page_range *p)
5131 : first(i), last(j), next(p)
5135 int page_range::contains(int n)
5137 return n >= first && (last <= 0 || n <= last);
5140 page_range *output_page_list = 0;
5142 int in_output_page_list(int n)
5144 if (!output_page_list)
5145 return 1;
5146 for (page_range *p = output_page_list; p; p = p->next)
5147 if (p->contains(n))
5148 return 1;
5149 return 0;
5152 static void parse_output_page_list(char *p)
5154 for (;;) {
5155 int i;
5156 if (*p == '-')
5157 i = 1;
5158 else if (csdigit(*p)) {
5159 i = 0;
5161 i = i*10 + *p++ - '0';
5162 while (csdigit(*p));
5164 else
5165 break;
5166 int j;
5167 if (*p == '-') {
5168 p++;
5169 j = 0;
5170 if (csdigit(*p)) {
5172 j = j*10 + *p++ - '0';
5173 while (csdigit(*p));
5176 else
5177 j = i;
5178 if (j == 0)
5179 last_page_number = -1;
5180 else if (last_page_number >= 0 && j > last_page_number)
5181 last_page_number = j;
5182 output_page_list = new page_range(i, j, output_page_list);
5183 if (*p != ',')
5184 break;
5185 ++p;
5187 if (*p != '\0') {
5188 error("bad output page list");
5189 output_page_list = 0;
5193 static FILE *open_mac_file(const char *mac, char **path)
5195 char *s = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
5196 strcpy(s, MACRO_PREFIX);
5197 strcat(s, mac);
5198 FILE *fp = macro_path.open_file(s, path);
5199 a_delete s;
5200 return fp;
5203 static void process_macro_file(const char *mac)
5205 char *path;
5206 FILE *fp = open_mac_file(mac, &path);
5207 if (!fp)
5208 fatal("can't find macro file %1", mac);
5209 const char *s = symbol(path).contents();
5210 a_delete path;
5211 input_stack::push(new file_iterator(fp, s));
5212 tok.next();
5213 process_input_stack();
5216 static void process_startup_file()
5218 char *path;
5219 FILE *fp = macro_path.open_file(STARTUP_FILE, &path);
5220 if (fp) {
5221 input_stack::push(new file_iterator(fp, symbol(path).contents()));
5222 a_delete path;
5223 tok.next();
5224 process_input_stack();
5228 void macro_source()
5230 symbol nm = get_long_name(1);
5231 if (nm.is_null())
5232 skip_line();
5233 else {
5234 while (!tok.newline() && !tok.eof())
5235 tok.next();
5236 char *path;
5237 FILE *fp = macro_path.open_file(nm.contents(), &path);
5238 if (fp) {
5239 input_stack::push(new file_iterator(fp, symbol(path).contents()));
5240 a_delete path;
5242 else
5243 error("can't find macro file `%1'", nm.contents());
5244 tok.next();
5248 static void process_input_file(const char *name)
5250 FILE *fp;
5251 if (strcmp(name, "-") == 0) {
5252 clearerr(stdin);
5253 fp = stdin;
5255 else {
5256 errno = 0;
5257 fp = fopen(name, "r");
5258 if (!fp)
5259 fatal("can't open `%1': %2", name, strerror(errno));
5261 input_stack::push(new file_iterator(fp, name));
5262 tok.next();
5263 process_input_stack();
5266 // make sure the_input is empty before calling this
5268 static int evaluate_expression(const char *expr, units *res)
5270 input_stack::push(make_temp_iterator(expr));
5271 tok.next();
5272 int success = get_number(res, 'u');
5273 while (input_stack::get(NULL) != EOF)
5275 return success;
5278 static void do_register_assignment(const char *s)
5280 const char *p = strchr(s, '=');
5281 if (!p) {
5282 char buf[2];
5283 buf[0] = s[0];
5284 buf[1] = 0;
5285 units n;
5286 if (evaluate_expression(s + 1, &n))
5287 set_number_reg(buf, n);
5289 else {
5290 char *buf = new char[p - s + 1];
5291 memcpy(buf, s, p - s);
5292 buf[p - s] = 0;
5293 units n;
5294 if (evaluate_expression(p + 1, &n))
5295 set_number_reg(buf, n);
5296 a_delete buf;
5300 static void set_string(const char *name, const char *value)
5302 macro *m = new macro;
5303 for (const char *p = value; *p; p++)
5304 if (!illegal_input_char((unsigned char)*p))
5305 m->append(*p);
5306 request_dictionary.define(name, m);
5310 static void do_string_assignment(const char *s)
5312 const char *p = strchr(s, '=');
5313 if (!p) {
5314 char buf[2];
5315 buf[0] = s[0];
5316 buf[1] = 0;
5317 set_string(buf, s + 1);
5319 else {
5320 char *buf = new char[p - s + 1];
5321 memcpy(buf, s, p - s);
5322 buf[p - s] = 0;
5323 set_string(buf, p + 1);
5324 a_delete buf;
5328 struct string_list {
5329 const char *s;
5330 string_list *next;
5331 string_list(const char *ss) : s(ss), next(0) {}
5334 static void prepend_string(const char *s, string_list **p)
5336 string_list *l = new string_list(s);
5337 l->next = *p;
5338 *p = l;
5341 static void add_string(const char *s, string_list **p)
5343 while (*p)
5344 p = &((*p)->next);
5345 *p = new string_list(s);
5348 void usage(const char *prog)
5350 errprint(
5351 "usage: %1 -abivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
5352 " -rcn -Tname -Fdir -Mdir [files...]\n",
5353 prog);
5354 exit(USAGE_EXIT_CODE);
5357 int main(int argc, char **argv)
5359 program_name = argv[0];
5360 static char stderr_buf[BUFSIZ];
5361 setbuf(stderr, stderr_buf);
5362 int c;
5363 string_list *macros = 0;
5364 string_list *register_assignments = 0;
5365 string_list *string_assignments = 0;
5366 int iflag = 0;
5367 int tflag = 0;
5368 int fflag = 0;
5369 int nflag = 0;
5370 int safer_flag = 1; // safer by default
5371 int no_rc = 0; // don't process troffrc
5372 int next_page_number;
5373 opterr = 0;
5374 hresolution = vresolution = 1;
5375 while ((c = getopt(argc, argv, "abivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"))
5376 != EOF)
5377 switch(c) {
5378 case 'v':
5380 extern const char *version_string;
5381 fprintf(stderr, "GNU troff version %s\n", version_string);
5382 fflush(stderr);
5383 break;
5385 case 'T':
5386 device = optarg;
5387 tflag = 1;
5388 break;
5389 case 'C':
5390 compatible_flag = 1;
5391 break;
5392 case 'M':
5393 macro_path.command_line_dir(optarg);
5394 break;
5395 case 'F':
5396 font::command_line_font_dir(optarg);
5397 break;
5398 case 'm':
5399 add_string(optarg, &macros);
5400 break;
5401 case 'E':
5402 inhibit_errors = 1;
5403 break;
5404 case 'R':
5405 no_rc = 1;
5406 break;
5407 case 'w':
5408 enable_warning(optarg);
5409 break;
5410 case 'W':
5411 disable_warning(optarg);
5412 break;
5413 case 'i':
5414 iflag = 1;
5415 break;
5416 case 'b':
5417 backtrace_flag = 1;
5418 break;
5419 case 'a':
5420 ascii_output_flag = 1;
5421 break;
5422 case 'z':
5423 suppress_output_flag = 1;
5424 break;
5425 case 'n':
5426 if (sscanf(optarg, "%d", &next_page_number) == 1)
5427 nflag++;
5428 else
5429 error("bad page number");
5430 break;
5431 case 'o':
5432 parse_output_page_list(optarg);
5433 break;
5434 case 'd':
5435 if (*optarg == '\0')
5436 error("`-d' requires non-empty argument");
5437 else
5438 add_string(optarg, &string_assignments);
5439 break;
5440 case 'r':
5441 if (*optarg == '\0')
5442 error("`-r' requires non-empty argument");
5443 else
5444 add_string(optarg, &register_assignments);
5445 break;
5446 case 'f':
5447 default_family = symbol(optarg);
5448 fflag = 1;
5449 break;
5450 case 'q':
5451 case 's':
5452 case 't':
5453 // silently ignore these
5454 break;
5455 case 'U':
5456 safer_flag = 0; // unsafe behaviour
5457 break;
5458 case '?':
5459 usage(argv[0]);
5460 default:
5461 assert(0);
5463 set_string(".T", device);
5464 init_charset_table();
5465 if (!font::load_desc())
5466 fatal("sorry, I can't continue");
5467 units_per_inch = font::res;
5468 hresolution = font::hor;
5469 vresolution = font::vert;
5470 sizescale = font::sizescale;
5471 tcommand_flag = font::tcommand;
5472 if (!fflag && font::family != 0 && *font::family != '\0')
5473 default_family = symbol(font::family);
5474 font_size::init_size_table(font::sizes);
5475 int i;
5476 int j = 1;
5477 if (font::style_table) {
5478 for (i = 0; font::style_table[i]; i++)
5479 mount_style(j++, symbol(font::style_table[i]));
5481 for (i = 0; font::font_name_table[i]; i++, j++)
5482 // In the DESC file a font name of 0 (zero) means leave this
5483 // position empty.
5484 if (strcmp(font::font_name_table[i], "0") != 0)
5485 mount_font(j, symbol(font::font_name_table[i]));
5486 curdiv = topdiv = new top_level_diversion;
5487 if (nflag)
5488 topdiv->set_next_page_number(next_page_number);
5489 init_input_requests();
5490 init_env_requests();
5491 init_div_requests();
5492 #ifdef COLUMN
5493 init_column_requests();
5494 #endif /* COLUMN */
5495 init_node_requests();
5496 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
5497 init_registers();
5498 init_reg_requests();
5499 init_hyphen_requests();
5500 init_environments();
5501 while (string_assignments) {
5502 do_string_assignment(string_assignments->s);
5503 string_list *tem = string_assignments;
5504 string_assignments = string_assignments->next;
5505 delete tem;
5507 while (register_assignments) {
5508 do_register_assignment(register_assignments->s);
5509 string_list *tem = register_assignments;
5510 register_assignments = register_assignments->next;
5511 delete tem;
5513 if (!no_rc)
5514 process_startup_file();
5515 if (safer_flag)
5516 prepend_string("safer", &macros);
5517 while (macros) {
5518 process_macro_file(macros->s);
5519 string_list *tem = macros;
5520 macros = macros->next;
5521 delete tem;
5523 for (i = optind; i < argc; i++)
5524 process_input_file(argv[i]);
5525 if (optind >= argc || iflag)
5526 process_input_file("-");
5527 exit_troff();
5528 return 0; // not reached
5531 void warn_request()
5533 int n;
5534 if (has_arg() && get_integer(&n)) {
5535 if (n & ~WARN_TOTAL) {
5536 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
5537 n &= WARN_TOTAL;
5539 warning_mask = n;
5541 else
5542 warning_mask = WARN_TOTAL;
5543 skip_line();
5546 static void init_registers()
5548 #ifdef LONG_FOR_TIME_T
5549 long
5550 #else /* not LONG_FOR_TIME_T */
5551 time_t
5552 #endif /* not LONG_FOR_TIME_T */
5553 t = time(0);
5554 // Use struct here to work around misfeature in old versions of g++.
5555 struct tm *tt = localtime(&t);
5556 set_number_reg("dw", int(tt->tm_wday + 1));
5557 set_number_reg("dy", int(tt->tm_mday));
5558 set_number_reg("mo", int(tt->tm_mon + 1));
5559 set_number_reg("yr", int(tt->tm_year));
5560 set_number_reg("$$", getpid());
5561 number_reg_dictionary.define(".A",
5562 new constant_reg(ascii_output_flag
5563 ? "1"
5564 : "0"));
5567 void init_input_requests()
5569 init_request("ds", define_string);
5570 init_request("as", append_string);
5571 init_request("de", define_macro);
5572 init_request("am", append_macro);
5573 init_request("ig", ignore);
5574 init_request("rm", remove_macro);
5575 init_request("rn", rename_macro);
5576 init_request("if", if_request);
5577 init_request("ie", if_else_request);
5578 init_request("el", else_request);
5579 init_request("so", source);
5580 init_request("nx", next_file);
5581 init_request("pm", print_macros);
5582 init_request("eo", escape_off);
5583 init_request("ec", set_escape_char);
5584 init_request("pc", set_page_character);
5585 init_request("tm", terminal);
5586 init_request("ex", exit_request);
5587 init_request("em", end_macro);
5588 init_request("blm", blank_line_macro);
5589 init_request("tr", translate);
5590 init_request("trnt", translate_no_transparent);
5591 init_request("ab", abort_request);
5592 init_request("pi", pipe_output);
5593 init_request("cf", copy_file);
5594 init_request("sy", system_request);
5595 init_request("lf", line_file);
5596 init_request("cflags", char_flags);
5597 init_request("shift", shift);
5598 init_request("rd", read_request);
5599 init_request("cp", compatible);
5600 init_request("char", define_character);
5601 init_request("rchar", remove_character);
5602 init_request("hcode", hyphenation_code);
5603 init_request("while", while_request);
5604 init_request("break", while_break_request);
5605 init_request("continue", while_continue_request);
5606 init_request("als", alias_macro);
5607 init_request("backtrace", backtrace_request);
5608 init_request("chop", chop_macro);
5609 init_request("substring", substring_macro);
5610 init_request("asciify", asciify_macro);
5611 init_request("warn", warn_request);
5612 init_request("open", open_request);
5613 init_request("opena", opena_request);
5614 init_request("close", close_request);
5615 init_request("write", write_request);
5616 init_request("writem", write_macro_request);
5617 init_request("trf", transparent_file);
5618 #ifdef WIDOW_CONTROL
5619 init_request("fpl", flush_pending_lines);
5620 #endif /* WIDOW_CONTROL */
5621 init_request("nroff", nroff_request);
5622 init_request("troff", troff_request);
5623 #ifdef COLUMN
5624 init_request("vj", vjustify);
5625 #endif /* COLUMN */
5626 init_request("mso", macro_source);
5627 init_request("do", do_request);
5628 #ifndef POPEN_MISSING
5629 init_request("pso", pipe_source);
5630 #endif /* not POPEN_MISSING */
5631 number_reg_dictionary.define("systat", new variable_reg(&system_status));
5632 number_reg_dictionary.define("slimit",
5633 new variable_reg(&input_stack::limit));
5634 number_reg_dictionary.define(".$", new nargs_reg);
5635 number_reg_dictionary.define(".c", new lineno_reg);
5636 number_reg_dictionary.define("c.", new writable_lineno_reg);
5637 number_reg_dictionary.define(".F", new filename_reg);
5638 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
5639 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
5640 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
5641 number_reg_dictionary.define(".R", new constant_reg("10000"));
5642 extern const char *major_version;
5643 number_reg_dictionary.define(".x", new constant_reg(major_version));
5644 extern const char *minor_version;
5645 number_reg_dictionary.define(".y", new constant_reg(minor_version));
5646 number_reg_dictionary.define(".g", new constant_reg("1"));
5647 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
5650 object_dictionary request_dictionary(501);
5652 void init_request(const char *s, REQUEST_FUNCP f)
5654 request_dictionary.define(s, new request(f));
5657 static request_or_macro *lookup_request(symbol nm)
5659 assert(!nm.is_null());
5660 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
5661 if (p == 0) {
5662 warning(WARN_MAC, "`%1' not defined", nm.contents());
5663 p = new macro;
5664 request_dictionary.define(nm, p);
5666 return p;
5670 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
5672 // Don't interpret character definitions in compatible mode.
5673 int old_compatible_flag = compatible_flag;
5674 compatible_flag = 0;
5675 int old_escape_char = escape_char;
5676 escape_char = '\\';
5677 macro *mac = ci->set_macro(0);
5678 assert(mac != 0);
5679 environment *oldenv = curenv;
5680 environment env(envp);
5681 curenv = &env;
5682 curenv->set_composite();
5683 token old_tok = tok;
5684 input_stack::add_boundary();
5685 string_iterator *si = new string_iterator(*mac, "composite character", ci->nm);
5686 input_stack::push(si);
5687 // we don't use process_input_stack, because we don't want to recognise
5688 // requests
5689 for (;;) {
5690 tok.next();
5691 if (tok.eof())
5692 break;
5693 if (tok.newline()) {
5694 error("composite character mustn't contain newline");
5695 while (!tok.eof())
5696 tok.next();
5697 break;
5699 else
5700 tok.process();
5702 node *n = curenv->extract_output_line();
5703 input_stack::remove_boundary();
5704 ci->set_macro(mac);
5705 tok = old_tok;
5706 curenv = oldenv;
5707 compatible_flag = old_compatible_flag;
5708 escape_char = old_escape_char;
5709 return n;
5712 static node *read_draw_node()
5714 token start;
5715 start.next();
5716 if (!start.delimiter(1)){
5717 do {
5718 tok.next();
5719 } while (tok != start && !tok.newline() && !tok.eof());
5721 else {
5722 tok.next();
5723 if (tok == start)
5724 error("missing argument");
5725 else {
5726 unsigned char type = tok.ch();
5727 tok.next();
5728 int maxpoints = 10;
5729 hvpair *point = new hvpair[maxpoints];
5730 int npoints = 0;
5731 int no_last_v = 0;
5732 int err = 0;
5733 int i;
5734 for (i = 0; tok != start; i++) {
5735 if (i == maxpoints) {
5736 hvpair *oldpoint = point;
5737 point = new hvpair[maxpoints*2];
5738 for (int j = 0; j < maxpoints; j++)
5739 point[j] = oldpoint[j];
5740 maxpoints *= 2;
5741 a_delete oldpoint;
5743 if (!get_hunits(&point[i].h,
5744 type == 'f' || type == 't' ? 'u' : 'm')) {
5745 err = 1;
5746 break;
5748 ++npoints;
5749 tok.skip();
5750 point[i].v = V0;
5751 if (tok == start) {
5752 no_last_v = 1;
5753 break;
5755 if (!get_vunits(&point[i].v, 'v')) {
5756 err = 1;
5757 break;
5759 tok.skip();
5761 while (tok != start && !tok.newline() && !tok.eof())
5762 tok.next();
5763 if (!err) {
5764 switch (type) {
5765 case 'l':
5766 if (npoints != 1 || no_last_v) {
5767 error("two arguments needed for line");
5768 npoints = 1;
5770 break;
5771 case 'c':
5772 if (npoints != 1 || !no_last_v) {
5773 error("one argument needed for circle");
5774 npoints = 1;
5775 point[0].v = V0;
5777 break;
5778 case 'e':
5779 if (npoints != 1 || no_last_v) {
5780 error("two arguments needed for ellipse");
5781 npoints = 1;
5783 break;
5784 case 'a':
5785 if (npoints != 2 || no_last_v) {
5786 error("four arguments needed for arc");
5787 npoints = 2;
5789 break;
5790 case '~':
5791 if (no_last_v)
5792 error("even number of arguments needed for spline");
5793 break;
5794 default:
5795 // silently pass it through
5796 break;
5798 draw_node *dn = new draw_node(type, point, npoints,
5799 curenv->get_font_size());
5800 a_delete point;
5801 return dn;
5803 else {
5804 a_delete point;
5808 return 0;
5811 static struct {
5812 const char *name;
5813 int mask;
5814 } warning_table[] = {
5815 { "char", WARN_CHAR },
5816 { "range", WARN_RANGE },
5817 { "break", WARN_BREAK },
5818 { "delim", WARN_DELIM },
5819 { "el", WARN_EL },
5820 { "scale", WARN_SCALE },
5821 { "number", WARN_NUMBER },
5822 { "syntax", WARN_SYNTAX },
5823 { "tab", WARN_TAB },
5824 { "right-brace", WARN_RIGHT_BRACE },
5825 { "missing", WARN_MISSING },
5826 { "input", WARN_INPUT },
5827 { "escape", WARN_ESCAPE },
5828 { "space", WARN_SPACE },
5829 { "font", WARN_FONT },
5830 { "di", WARN_DI },
5831 { "mac", WARN_MAC },
5832 { "reg", WARN_REG },
5833 { "ig", WARN_IG },
5834 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
5835 { "w", WARN_TOTAL },
5836 { "default", DEFAULT_WARNING_MASK },
5839 static int lookup_warning(const char *name)
5841 for (int i = 0;
5842 i < sizeof(warning_table)/sizeof(warning_table[0]);
5843 i++)
5844 if (strcmp(name, warning_table[i].name) == 0)
5845 return warning_table[i].mask;
5846 return 0;
5849 static void enable_warning(const char *name)
5851 int mask = lookup_warning(name);
5852 if (mask)
5853 warning_mask |= mask;
5854 else
5855 error("unknown warning `%1'", name);
5858 static void disable_warning(const char *name)
5860 int mask = lookup_warning(name);
5861 if (mask)
5862 warning_mask &= ~mask;
5863 else
5864 error("unknown warning `%1'", name);
5867 static void copy_mode_error(const char *format,
5868 const errarg &arg1,
5869 const errarg &arg2,
5870 const errarg &arg3)
5872 if (ignoring) {
5873 static const char prefix[] = "(in ignored input) ";
5874 char *s = new char[sizeof(prefix) + strlen(format)];
5875 strcpy(s, prefix);
5876 strcat(s, format);
5877 warning(WARN_IG, s, arg1, arg2, arg3);
5878 a_delete s;
5880 else
5881 error(format, arg1, arg2, arg3);
5884 enum error_type { WARNING, ERROR, FATAL };
5886 static void do_error(error_type type,
5887 const char *format,
5888 const errarg &arg1,
5889 const errarg &arg2,
5890 const errarg &arg3)
5892 const char *filename;
5893 int lineno;
5894 if (inhibit_errors && type < FATAL)
5895 return;
5896 if (backtrace_flag)
5897 input_stack::backtrace();
5898 if (!get_file_line(&filename, &lineno))
5899 filename = 0;
5900 if (filename)
5901 errprint("%1:%2: ", filename, lineno);
5902 else if (program_name)
5903 fprintf(stderr, "%s: ", program_name);
5904 switch (type) {
5905 case FATAL:
5906 fputs("fatal error: ", stderr);
5907 break;
5908 case ERROR:
5909 break;
5910 case WARNING:
5911 fputs("warning: ", stderr);
5912 break;
5914 errprint(format, arg1, arg2, arg3);
5915 fputc('\n', stderr);
5916 fflush(stderr);
5917 if (type == FATAL)
5918 cleanup_and_exit(1);
5921 int warning(warning_type t,
5922 const char *format,
5923 const errarg &arg1,
5924 const errarg &arg2,
5925 const errarg &arg3)
5927 if ((t & warning_mask) != 0) {
5928 do_error(WARNING, format, arg1, arg2, arg3);
5929 return 1;
5931 else
5932 return 0;
5935 void error(const char *format,
5936 const errarg &arg1,
5937 const errarg &arg2,
5938 const errarg &arg3)
5940 do_error(ERROR, format, arg1, arg2, arg3);
5943 void fatal(const char *format,
5944 const errarg &arg1,
5945 const errarg &arg2,
5946 const errarg &arg3)
5948 do_error(FATAL, format, arg1, arg2, arg3);
5951 void fatal_with_file_and_line(const char *filename, int lineno,
5952 const char *format,
5953 const errarg &arg1,
5954 const errarg &arg2,
5955 const errarg &arg3)
5957 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
5958 errprint(format, arg1, arg2, arg3);
5959 fputc('\n', stderr);
5960 fflush(stderr);
5961 cleanup_and_exit(1);
5964 void error_with_file_and_line(const char *filename, int lineno,
5965 const char *format,
5966 const errarg &arg1,
5967 const errarg &arg2,
5968 const errarg &arg3)
5970 fprintf(stderr, "%s:%d: error: ", filename, lineno);
5971 errprint(format, arg1, arg2, arg3);
5972 fputc('\n', stderr);
5973 fflush(stderr);
5976 dictionary charinfo_dictionary(501);
5978 charinfo *get_charinfo(symbol nm)
5980 void *p = charinfo_dictionary.lookup(nm);
5981 if (p != 0)
5982 return (charinfo *)p;
5983 charinfo *cp = new charinfo(nm);
5984 (void)charinfo_dictionary.lookup(nm, cp);
5985 return cp;
5988 int charinfo::next_index = 0;
5990 charinfo::charinfo(symbol s)
5991 : nm(s), hyphenation_code(0), translation(0), flags(0), ascii_code(0),
5992 special_translation(TRANSLATE_NONE), mac(0), not_found(0),
5993 transparent_translate(1)
5995 index = next_index++;
5998 void charinfo::set_hyphenation_code(unsigned char c)
6000 hyphenation_code = c;
6003 void charinfo::set_translation(charinfo *ci, int tt)
6005 translation = ci;
6006 special_translation = TRANSLATE_NONE;
6007 transparent_translate = tt;
6010 void charinfo::set_special_translation(int c, int tt)
6012 special_translation = c;
6013 translation = 0;
6014 transparent_translate = tt;
6017 void charinfo::set_ascii_code(unsigned char c)
6019 ascii_code = c;
6022 macro *charinfo::set_macro(macro *m)
6024 macro *tem = mac;
6025 mac = m;
6026 return tem;
6029 void charinfo::set_number(int n)
6031 number = n;
6032 flags |= NUMBERED;
6035 int charinfo::get_number()
6037 assert(flags & NUMBERED);
6038 return number;
6041 symbol UNNAMED_SYMBOL("---");
6043 // For numbered characters not between 0 and 255, we make a symbol out
6044 // of the number and store them in this dictionary.
6046 dictionary numbered_charinfo_dictionary(11);
6048 charinfo *get_charinfo_by_number(int n)
6050 static charinfo *number_table[256];
6052 if (n >= 0 && n < 256) {
6053 charinfo *ci = number_table[n];
6054 if (!ci) {
6055 ci = new charinfo(UNNAMED_SYMBOL);
6056 ci->set_number(n);
6057 number_table[n] = ci;
6059 return ci;
6061 else {
6062 symbol ns(itoa(n));
6063 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
6064 if (!ci) {
6065 ci = new charinfo(UNNAMED_SYMBOL);
6066 ci->set_number(n);
6067 numbered_charinfo_dictionary.lookup(ns, ci);
6069 return ci;
6073 int font::name_to_index(const char *nm)
6075 charinfo *ci;
6076 if (nm[1] == 0)
6077 ci = charset_table[nm[0] & 0xff];
6078 else if (nm[0] == '\\' && nm[2] == 0)
6079 ci = get_charinfo(symbol(nm + 1));
6080 else
6081 ci = get_charinfo(symbol(nm));
6082 if (ci == 0)
6083 return -1;
6084 else
6085 return ci->get_index();
6088 int font::number_to_index(int n)
6090 return get_charinfo_by_number(n)->get_index();