* src/roff/troff/input.cc (read_rgb, read_cmy, read_cmyk): Call
[s-roff.git] / src / roff / troff / input.cc
blob534ef67fa955fe3f1fd4430fbda4199362e8084e
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 for more details.
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING. If not, write to the Free Software
20 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
22 #include "troff.h"
23 #include "symbol.h"
24 #include "dictionary.h"
25 #include "hvunits.h"
26 #include "env.h"
27 #include "request.h"
28 #include "node.h"
29 #include "reg.h"
30 #include "token.h"
31 #include "div.h"
32 #include "charinfo.h"
33 #include "stringclass.h"
34 #include "font.h"
35 #include "macropath.h"
36 #include "defs.h"
37 #include "input.h"
39 // Needed for getpid() and isatty()
40 #include "posix.h"
42 #include "nonposix.h"
44 #ifdef NEED_DECLARATION_PUTENV
45 extern "C" {
46 int putenv(const char *);
48 #endif /* NEED_DECLARATION_PUTENV */
50 #define MACRO_PREFIX "tmac."
51 #define MACRO_POSTFIX ".tmac"
52 #define INITIAL_STARTUP_FILE "troffrc"
53 #define FINAL_STARTUP_FILE "troffrc-end"
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 extern "C" const char *Version_string;
67 #ifdef COLUMN
68 void init_column_requests();
69 #endif /* COLUMN */
71 static node *read_draw_node();
72 void handle_first_page_transition();
73 static void push_token(const token &);
74 void copy_file();
75 #ifdef COLUMN
76 void vjustify();
77 #endif /* COLUMN */
78 void transparent_file();
79 void process_input_stack();
81 const char *program_name = 0;
82 token tok;
83 int break_flag = 0;
84 int color_flag = 1; // colors are on by default
85 static int backtrace_flag = 0;
86 #ifndef POPEN_MISSING
87 char *pipe_command = 0;
88 #endif
89 charinfo *charset_table[256];
90 unsigned char hpf_code_table[256];
92 static int warning_mask = DEFAULT_WARNING_MASK;
93 static int inhibit_errors = 0;
94 static int ignoring = 0;
96 static void enable_warning(const char *);
97 static void disable_warning(const char *);
99 static int escape_char = '\\';
100 static symbol end_macro_name;
101 static symbol blank_line_macro_name;
102 static int compatible_flag = 0;
103 int ascii_output_flag = 0;
104 int suppress_output_flag = 0;
105 int is_html = 0;
106 int begin_level = 0; // number of nested .begin requests
108 int have_input = 0; // whether \f, \H, \R, \s, or \S has
109 // been processed in token::next()
110 int tcommand_flag = 0;
111 int safer_flag = 1; // safer by default
113 int have_string_arg = 0; // whether we have \*[foo bar...]
115 double spread_limit = -3.0 - 1.0; // negative means deactivated
117 double warn_scale;
118 char warn_scaling_indicator;
120 search_path *mac_path = &safer_macro_path;
122 static int get_copy(node**, int = 0);
123 static void copy_mode_error(const char *,
124 const errarg & = empty_errarg,
125 const errarg & = empty_errarg,
126 const errarg & = empty_errarg);
128 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
129 static symbol read_escape_name(read_mode mode = NO_ARGS);
130 static symbol read_long_escape_name(read_mode mode = NO_ARGS);
131 static void interpolate_string(symbol);
132 static void interpolate_string_with_args(symbol);
133 static void interpolate_macro(symbol);
134 static void interpolate_number_format(symbol);
135 static void interpolate_environment_variable(symbol);
137 static void interpolate_arg(symbol);
138 static request_or_macro *lookup_request(symbol);
139 static int get_delim_number(units *, int);
140 static int get_delim_number(units *, int, units);
141 static int get_line_arg(units *res, int si, charinfo **cp);
142 static int read_size(int *);
143 static symbol get_delim_name();
144 static void init_registers();
145 static void trapping_blank_line();
147 struct input_iterator;
148 input_iterator *make_temp_iterator(const char *);
149 const char *input_char_description(int);
152 void set_escape_char()
154 if (has_arg()) {
155 if (tok.ch() == 0) {
156 error("bad escape character");
157 escape_char = '\\';
159 else
160 escape_char = tok.ch();
162 else
163 escape_char = '\\';
164 skip_line();
167 void escape_off()
169 escape_char = 0;
170 skip_line();
173 static int saved_escape_char = '\\';
175 void save_escape_char()
177 saved_escape_char = escape_char;
178 skip_line();
181 void restore_escape_char()
183 escape_char = saved_escape_char;
184 skip_line();
187 class input_iterator {
188 public:
189 input_iterator();
190 virtual ~input_iterator() {}
191 int get(node **);
192 friend class input_stack;
193 protected:
194 const unsigned char *ptr;
195 const unsigned char *eptr;
196 input_iterator *next;
197 private:
198 virtual int fill(node **);
199 virtual int peek();
200 virtual int has_args() { return 0; }
201 virtual int nargs() { return 0; }
202 virtual input_iterator *get_arg(int) { return 0; }
203 virtual int get_location(int, const char **, int *) { return 0; }
204 virtual void backtrace() {}
205 virtual int set_location(const char *, int) { return 0; }
206 virtual int next_file(FILE *, const char *) { return 0; }
207 virtual void shift(int) {}
208 virtual int is_boundary() {return 0; }
209 virtual int internal_level() { return 0; }
210 virtual int is_file() { return 0; }
211 virtual int is_macro() { return 0; }
212 virtual void save_compatible_flag(int) {}
213 virtual int get_compatible_flag() { return 0; }
216 input_iterator::input_iterator()
217 : ptr(0), eptr(0)
221 int input_iterator::fill(node **)
223 return EOF;
226 int input_iterator::peek()
228 return EOF;
231 inline int input_iterator::get(node **p)
233 return ptr < eptr ? *ptr++ : fill(p);
236 class input_boundary : public input_iterator {
237 public:
238 int is_boundary() { return 1; }
241 class input_return_boundary : public input_iterator {
242 public:
243 int is_boundary() { return 2; }
246 class file_iterator : public input_iterator {
247 FILE *fp;
248 int lineno;
249 const char *filename;
250 int popened;
251 int newline_flag;
252 int seen_escape;
253 enum { BUF_SIZE = 512 };
254 unsigned char buf[BUF_SIZE];
255 void close();
256 public:
257 file_iterator(FILE *, const char *, int = 0);
258 ~file_iterator();
259 int fill(node **);
260 int peek();
261 int get_location(int, const char **, int *);
262 void backtrace();
263 int set_location(const char *, int);
264 int next_file(FILE *, const char *);
265 int is_file();
268 file_iterator::file_iterator(FILE *f, const char *fn, int po)
269 : fp(f), lineno(1), filename(fn), popened(po),
270 newline_flag(0), seen_escape(0)
272 if ((font::use_charnames_in_special) && (fn != 0)) {
273 if (!the_output)
274 init_output();
275 the_output->put_filename(fn);
279 file_iterator::~file_iterator()
281 close();
284 void file_iterator::close()
286 if (fp == stdin)
287 clearerr(stdin);
288 #ifndef POPEN_MISSING
289 else if (popened)
290 pclose(fp);
291 #endif /* not POPEN_MISSING */
292 else
293 fclose(fp);
296 int file_iterator::is_file()
298 return 1;
301 int file_iterator::next_file(FILE *f, const char *s)
303 close();
304 filename = s;
305 fp = f;
306 lineno = 1;
307 newline_flag = 0;
308 seen_escape = 0;
309 popened = 0;
310 ptr = 0;
311 eptr = 0;
312 return 1;
315 int file_iterator::fill(node **)
317 if (newline_flag)
318 lineno++;
319 newline_flag = 0;
320 unsigned char *p = buf;
321 ptr = p;
322 unsigned char *e = p + BUF_SIZE;
323 while (p < e) {
324 int c = getc(fp);
325 if (c == EOF)
326 break;
327 if (invalid_input_char(c))
328 warning(WARN_INPUT, "invalid input character code %1", int(c));
329 else {
330 *p++ = c;
331 if (c == '\n') {
332 seen_escape = 0;
333 newline_flag = 1;
334 break;
336 seen_escape = (c == '\\');
339 if (p > buf) {
340 eptr = p;
341 return *ptr++;
343 else {
344 eptr = p;
345 return EOF;
349 int file_iterator::peek()
351 int c = getc(fp);
352 while (invalid_input_char(c)) {
353 warning(WARN_INPUT, "invalid input character code %1", int(c));
354 c = getc(fp);
356 if (c != EOF)
357 ungetc(c, fp);
358 return c;
361 int file_iterator::get_location(int /*allow_macro*/,
362 const char **filenamep, int *linenop)
364 *linenop = lineno;
365 if (filename != 0 && strcmp(filename, "-") == 0)
366 *filenamep = "<standard input>";
367 else
368 *filenamep = filename;
369 return 1;
372 void file_iterator::backtrace()
374 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
375 popened ? "process" : "file");
378 int file_iterator::set_location(const char *f, int ln)
380 if (f) {
381 filename = f;
382 if (!the_output)
383 init_output();
384 the_output->put_filename(f);
386 lineno = ln;
387 return 1;
390 input_iterator nil_iterator;
392 class input_stack {
393 public:
394 static int get(node **);
395 static int peek();
396 static void push(input_iterator *);
397 static input_iterator *get_arg(int);
398 static int nargs();
399 static int get_location(int, const char **, int *);
400 static int set_location(const char *, int);
401 static void backtrace();
402 static void backtrace_all();
403 static void next_file(FILE *, const char *);
404 static void end_file();
405 static void shift(int n);
406 static void add_boundary();
407 static void add_return_boundary();
408 static int is_return_boundary();
409 static void remove_boundary();
410 static int get_level();
411 static void clear();
412 static void pop_macro();
413 static void save_compatible_flag(int);
414 static int get_compatible_flag();
416 static int limit;
417 private:
418 static input_iterator *top;
419 static int level;
421 static int finish_get(node **);
422 static int finish_peek();
425 input_iterator *input_stack::top = &nil_iterator;
426 int input_stack::level = 0;
427 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
429 inline int input_stack::get_level()
431 return level + top->internal_level();
434 inline int input_stack::get(node **np)
436 return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
439 int input_stack::finish_get(node **np)
441 for (;;) {
442 int c = top->fill(np);
443 if (c != EOF || top->is_boundary())
444 return c;
445 if (top == &nil_iterator)
446 break;
447 input_iterator *tem = top;
448 top = top->next;
449 level--;
450 delete tem;
451 if (top->ptr < top->eptr)
452 return *top->ptr++;
454 assert(level == 0);
455 return EOF;
458 inline int input_stack::peek()
460 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
463 int input_stack::finish_peek()
465 for (;;) {
466 int c = top->peek();
467 if (c != EOF || top->is_boundary())
468 return c;
469 if (top == &nil_iterator)
470 break;
471 input_iterator *tem = top;
472 top = top->next;
473 level--;
474 delete tem;
475 if (top->ptr < top->eptr)
476 return *top->ptr;
478 assert(level == 0);
479 return EOF;
482 void input_stack::add_boundary()
484 push(new input_boundary);
487 void input_stack::add_return_boundary()
489 push(new input_return_boundary);
492 int input_stack::is_return_boundary()
494 return top->is_boundary() == 2;
497 void input_stack::remove_boundary()
499 assert(top->is_boundary());
500 input_iterator *temp = top->next;
501 delete top;
502 top = temp;
503 level--;
506 void input_stack::push(input_iterator *in)
508 if (in == 0)
509 return;
510 if (++level > limit && limit > 0)
511 fatal("input stack limit exceeded (probable infinite loop)");
512 in->next = top;
513 top = in;
516 input_iterator *input_stack::get_arg(int i)
518 input_iterator *p;
519 for (p = top; p != 0; p = p->next)
520 if (p->has_args())
521 return p->get_arg(i);
522 return 0;
525 void input_stack::shift(int n)
527 for (input_iterator *p = top; p; p = p->next)
528 if (p->has_args()) {
529 p->shift(n);
530 return;
534 int input_stack::nargs()
536 for (input_iterator *p =top; p != 0; p = p->next)
537 if (p->has_args())
538 return p->nargs();
539 return 0;
542 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
544 for (input_iterator *p = top; p; p = p->next)
545 if (p->get_location(allow_macro, filenamep, linenop))
546 return 1;
547 return 0;
550 void input_stack::backtrace()
552 const char *f;
553 int n;
554 // only backtrace down to (not including) the topmost file
555 for (input_iterator *p = top;
556 p && !p->get_location(0, &f, &n);
557 p = p->next)
558 p->backtrace();
561 void input_stack::backtrace_all()
563 for (input_iterator *p = top; p; p = p->next)
564 p->backtrace();
567 int input_stack::set_location(const char *filename, int lineno)
569 for (input_iterator *p = top; p; p = p->next)
570 if (p->set_location(filename, lineno))
571 return 1;
572 return 0;
575 void input_stack::next_file(FILE *fp, const char *s)
577 input_iterator **pp;
578 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
579 if ((*pp)->next_file(fp, s))
580 return;
581 if (++level > limit && limit > 0)
582 fatal("input stack limit exceeded");
583 *pp = new file_iterator(fp, s);
584 (*pp)->next = &nil_iterator;
587 void input_stack::end_file()
589 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
590 if ((*pp)->is_file()) {
591 input_iterator *tem = *pp;
592 *pp = (*pp)->next;
593 delete tem;
594 level--;
595 return;
599 void input_stack::clear()
601 int nboundaries = 0;
602 while (top != &nil_iterator) {
603 if (top->is_boundary())
604 nboundaries++;
605 input_iterator *tem = top;
606 top = top->next;
607 level--;
608 delete tem;
610 // Keep while_request happy.
611 for (; nboundaries > 0; --nboundaries)
612 add_return_boundary();
615 void input_stack::pop_macro()
617 int nboundaries = 0;
618 int is_macro = 0;
619 do {
620 if (top->next == &nil_iterator)
621 break;
622 if (top->is_boundary())
623 nboundaries++;
624 is_macro = top->is_macro();
625 input_iterator *tem = top;
626 top = top->next;
627 level--;
628 delete tem;
629 } while (!is_macro);
630 // Keep while_request happy.
631 for (; nboundaries > 0; --nboundaries)
632 add_return_boundary();
635 inline void input_stack::save_compatible_flag(int f)
637 top->save_compatible_flag(f);
640 inline int input_stack::get_compatible_flag()
642 return top->get_compatible_flag();
645 void backtrace_request()
647 input_stack::backtrace_all();
648 fflush(stderr);
649 skip_line();
652 void next_file()
654 symbol nm = get_long_name(0);
655 while (!tok.newline() && !tok.eof())
656 tok.next();
657 if (nm.is_null())
658 input_stack::end_file();
659 else {
660 errno = 0;
661 FILE *fp = fopen(nm.contents(), "r");
662 if (!fp)
663 error("can't open `%1': %2", nm.contents(), strerror(errno));
664 else
665 input_stack::next_file(fp, nm.contents());
667 tok.next();
670 void shift()
672 int n;
673 if (!has_arg() || !get_integer(&n))
674 n = 1;
675 input_stack::shift(n);
676 skip_line();
679 static int get_char_for_escape_name(int allow_space = 0)
681 int c = get_copy(0);
682 switch (c) {
683 case EOF:
684 copy_mode_error("end of input in escape name");
685 return '\0';
686 default:
687 if (!invalid_input_char(c))
688 break;
689 // fall through
690 case '\n':
691 if (c == '\n')
692 input_stack::push(make_temp_iterator("\n"));
693 // fall through
694 case ' ':
695 if (c == ' ' && allow_space)
696 break;
697 // fall through
698 case '\t':
699 case '\001':
700 case '\b':
701 copy_mode_error("%1 is not allowed in an escape name",
702 input_char_description(c));
703 return '\0';
705 return c;
708 static symbol read_two_char_escape_name()
710 char buf[3];
711 buf[0] = get_char_for_escape_name();
712 if (buf[0] != '\0') {
713 buf[1] = get_char_for_escape_name();
714 if (buf[1] == '\0')
715 buf[0] = 0;
716 else
717 buf[2] = 0;
719 return symbol(buf);
722 static symbol read_long_escape_name(read_mode mode)
724 int start_level = input_stack::get_level();
725 char abuf[ABUF_SIZE];
726 char *buf = abuf;
727 int buf_size = ABUF_SIZE;
728 int i = 0;
729 int c;
730 int have_char = 0;
731 for (;;) {
732 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
733 if (c == 0) {
734 if (buf != abuf)
735 a_delete buf;
736 return NULL_SYMBOL;
738 have_char = 1;
739 if (mode == WITH_ARGS && c == ' ')
740 break;
741 if (i + 2 > buf_size) {
742 if (buf == abuf) {
743 buf = new char[ABUF_SIZE*2];
744 memcpy(buf, abuf, buf_size);
745 buf_size = ABUF_SIZE*2;
747 else {
748 char *old_buf = buf;
749 buf = new char[buf_size*2];
750 memcpy(buf, old_buf, buf_size);
751 buf_size *= 2;
752 a_delete old_buf;
755 if (c == ']' && input_stack::get_level() == start_level)
756 break;
757 buf[i++] = c;
759 buf[i] = 0;
760 if (c == ' ')
761 have_string_arg = 1;
762 if (buf == abuf) {
763 if (i == 0) {
764 if (mode != ALLOW_EMPTY)
765 copy_mode_error("empty escape name");
766 return EMPTY_SYMBOL;
768 return symbol(abuf);
770 else {
771 symbol s(buf);
772 a_delete buf;
773 return s;
777 static symbol read_escape_name(read_mode mode)
779 int c = get_char_for_escape_name();
780 if (c == 0)
781 return NULL_SYMBOL;
782 if (c == '(')
783 return read_two_char_escape_name();
784 if (c == '[' && !compatible_flag)
785 return read_long_escape_name(mode);
786 char buf[2];
787 buf[0] = c;
788 buf[1] = '\0';
789 return symbol(buf);
792 static symbol read_increment_and_escape_name(int *incp)
794 int c = get_char_for_escape_name();
795 switch (c) {
796 case 0:
797 *incp = 0;
798 return NULL_SYMBOL;
799 case '(':
800 *incp = 0;
801 return read_two_char_escape_name();
802 case '+':
803 *incp = 1;
804 return read_escape_name();
805 case '-':
806 *incp = -1;
807 return read_escape_name();
808 case '[':
809 if (!compatible_flag) {
810 *incp = 0;
811 return read_long_escape_name();
813 break;
815 *incp = 0;
816 char buf[2];
817 buf[0] = c;
818 buf[1] = '\0';
819 return symbol(buf);
822 static int get_copy(node **nd, int defining)
824 for (;;) {
825 int c = input_stack::get(nd);
826 if (c == ESCAPE_NEWLINE) {
827 if (defining)
828 return c;
829 do {
830 c = input_stack::get(nd);
831 } while (c == ESCAPE_NEWLINE);
833 if (c != escape_char || escape_char <= 0)
834 return c;
835 c = input_stack::peek();
836 switch(c) {
837 case 0:
838 return escape_char;
839 case '"':
840 (void)input_stack::get(0);
841 while ((c = input_stack::get(0)) != '\n' && c != EOF)
843 return c;
844 case '#': // Like \" but newline is ignored.
845 (void)input_stack::get(0);
846 while ((c = input_stack::get(0)) != '\n')
847 if (c == EOF)
848 return EOF;
849 break;
850 case '$':
852 (void)input_stack::get(0);
853 symbol s = read_escape_name();
854 if (!(s.is_null() || s.is_empty()))
855 interpolate_arg(s);
856 break;
858 case '*':
860 (void)input_stack::get(0);
861 symbol s = read_escape_name(WITH_ARGS);
862 if (!(s.is_null() || s.is_empty())) {
863 if (have_string_arg) {
864 have_string_arg = 0;
865 interpolate_string_with_args(s);
867 else
868 interpolate_string(s);
870 break;
872 case 'a':
873 (void)input_stack::get(0);
874 return '\001';
875 case 'e':
876 (void)input_stack::get(0);
877 return ESCAPE_e;
878 case 'E':
879 (void)input_stack::get(0);
880 return ESCAPE_E;
881 case 'n':
883 (void)input_stack::get(0);
884 int inc;
885 symbol s = read_increment_and_escape_name(&inc);
886 if (!(s.is_null() || s.is_empty()))
887 interpolate_number_reg(s, inc);
888 break;
890 case 'g':
892 (void)input_stack::get(0);
893 symbol s = read_escape_name();
894 if (!(s.is_null() || s.is_empty()))
895 interpolate_number_format(s);
896 break;
898 case 't':
899 (void)input_stack::get(0);
900 return '\t';
901 case 'V':
903 (void)input_stack::get(0);
904 symbol s = read_escape_name();
905 if (!(s.is_null() || s.is_empty()))
906 interpolate_environment_variable(s);
907 break;
909 case '\n':
910 (void)input_stack::get(0);
911 if (defining)
912 return ESCAPE_NEWLINE;
913 break;
914 case ' ':
915 (void)input_stack::get(0);
916 return ESCAPE_SPACE;
917 case '~':
918 (void)input_stack::get(0);
919 return ESCAPE_TILDE;
920 case ':':
921 (void)input_stack::get(0);
922 return ESCAPE_COLON;
923 case '|':
924 (void)input_stack::get(0);
925 return ESCAPE_BAR;
926 case '^':
927 (void)input_stack::get(0);
928 return ESCAPE_CIRCUMFLEX;
929 case '{':
930 (void)input_stack::get(0);
931 return ESCAPE_LEFT_BRACE;
932 case '}':
933 (void)input_stack::get(0);
934 return ESCAPE_RIGHT_BRACE;
935 case '`':
936 (void)input_stack::get(0);
937 return ESCAPE_LEFT_QUOTE;
938 case '\'':
939 (void)input_stack::get(0);
940 return ESCAPE_RIGHT_QUOTE;
941 case '-':
942 (void)input_stack::get(0);
943 return ESCAPE_HYPHEN;
944 case '_':
945 (void)input_stack::get(0);
946 return ESCAPE_UNDERSCORE;
947 case 'c':
948 (void)input_stack::get(0);
949 return ESCAPE_c;
950 case '!':
951 (void)input_stack::get(0);
952 return ESCAPE_BANG;
953 case '?':
954 (void)input_stack::get(0);
955 return ESCAPE_QUESTION;
956 case '&':
957 (void)input_stack::get(0);
958 return ESCAPE_AMPERSAND;
959 case ')':
960 (void)input_stack::get(0);
961 return ESCAPE_RIGHT_PARENTHESIS;
962 case '.':
963 (void)input_stack::get(0);
964 return c;
965 case '%':
966 (void)input_stack::get(0);
967 return ESCAPE_PERCENT;
968 default:
969 if (c == escape_char) {
970 (void)input_stack::get(0);
971 return c;
973 else
974 return escape_char;
979 class non_interpreted_char_node : public node {
980 unsigned char c;
981 public:
982 non_interpreted_char_node(unsigned char);
983 node *copy();
984 int interpret(macro *);
985 int same(node *);
986 const char *type();
987 int force_tprint();
990 int non_interpreted_char_node::same(node *nd)
992 return c == ((non_interpreted_char_node *)nd)->c;
995 const char *non_interpreted_char_node::type()
997 return "non_interpreted_char_node";
1000 int non_interpreted_char_node::force_tprint()
1002 return 0;
1005 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1007 assert(n != 0);
1010 node *non_interpreted_char_node::copy()
1012 return new non_interpreted_char_node(c);
1015 int non_interpreted_char_node::interpret(macro *mac)
1017 mac->append(c);
1018 return 1;
1021 static void do_width();
1022 static node *do_non_interpreted();
1023 static node *do_special();
1024 static node *do_suppress(symbol nm);
1025 static void do_register();
1027 dictionary color_dictionary(501);
1028 static symbol default_symbol("default");
1030 static color *lookup_color(symbol nm)
1032 assert(!nm.is_null());
1033 if (nm == default_symbol)
1034 return &default_color;
1035 color *c = (color *)color_dictionary.lookup(nm);
1036 if (c == 0)
1037 warning(WARN_COLOR, "`%1' not defined", nm.contents());
1038 return c;
1041 static node *do_glyph_color(symbol nm)
1043 if (nm.is_null())
1044 return 0;
1045 if (nm.is_empty())
1046 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1047 else {
1048 color *tem = lookup_color(nm);
1049 if (tem)
1050 curenv->set_glyph_color(tem);
1051 else
1052 (void)color_dictionary.lookup(nm, new color);
1054 return new glyph_color_node(curenv->get_glyph_color());
1057 static node *do_fill_color(symbol nm)
1059 if (nm.is_null())
1060 return 0;
1061 if (nm.is_empty())
1062 curenv->set_fill_color(curenv->get_prev_fill_color());
1063 else {
1064 color *tem = lookup_color(nm);
1065 if (tem)
1066 curenv->set_fill_color(tem);
1067 else
1068 (void)color_dictionary.lookup(nm, new color);
1070 return new fill_color_node(curenv->get_fill_color());
1073 static unsigned int get_color_element(const char *scheme, const char *col)
1075 units val;
1076 if (!get_number(&val, 'f')) {
1077 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1078 tok.next();
1079 return 0;
1081 if (val < 0) {
1082 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1083 return 0;
1085 if (val > color::MAX_COLOR_VAL+1) {
1086 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1087 // we change 0x10000 to 0xffff
1088 return color::MAX_COLOR_VAL;
1090 return (unsigned int)val;
1093 static color *read_rgb()
1095 symbol component = get_long_name(0);
1096 if (component.is_null()) {
1097 warning(WARN_COLOR, "missing rgb color values");
1098 return 0;
1100 const char *s = component.contents();
1101 color *col = new color;
1102 if (*s == '#') {
1103 if (!col->read_rgb(s)) {
1104 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1105 delete col;
1106 return 0;
1109 else {
1110 input_stack::push(make_temp_iterator(" "));
1111 input_stack::push(make_temp_iterator(s));
1112 tok.next();
1113 unsigned int r = get_color_element("rgb color", "red component");
1114 unsigned int g = get_color_element("rgb color", "green component");
1115 unsigned int b = get_color_element("rgb color", "blue component");
1116 col->set_rgb(r, g, b);
1118 return col;
1121 static color *read_cmy()
1123 symbol component = get_long_name(0);
1124 if (component.is_null()) {
1125 warning(WARN_COLOR, "missing cmy color values");
1126 return 0;
1128 const char *s = component.contents();
1129 color *col = new color;
1130 if (*s == '#') {
1131 if (!col->read_cmy(s)) {
1132 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1133 delete col;
1134 return 0;
1137 else {
1138 input_stack::push(make_temp_iterator(" "));
1139 input_stack::push(make_temp_iterator(s));
1140 tok.next();
1141 unsigned int c = get_color_element("cmy color", "cyan component");
1142 unsigned int m = get_color_element("cmy color", "magenta component");
1143 unsigned int y = get_color_element("cmy color", "yellow component");
1144 col->set_cmy(c, m, y);
1146 return col;
1149 static color *read_cmyk()
1151 symbol component = get_long_name(0);
1152 if (component.is_null()) {
1153 warning(WARN_COLOR, "missing cmyk color values");
1154 return 0;
1156 const char *s = component.contents();
1157 color *col = new color;
1158 if (*s == '#') {
1159 if (!col->read_cmyk(s)) {
1160 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1161 delete col;
1162 return 0;
1165 else {
1166 input_stack::push(make_temp_iterator(" "));
1167 input_stack::push(make_temp_iterator(s));
1168 tok.next();
1169 unsigned int c = get_color_element("cmyk color", "cyan component");
1170 unsigned int m = get_color_element("cmyk color", "magenta component");
1171 unsigned int y = get_color_element("cmyk color", "yellow component");
1172 unsigned int k = get_color_element("cmyk color", "black component");
1173 col->set_cmyk(c, m, y, k);
1175 return col;
1178 static color *read_gray()
1180 symbol component = get_long_name(0);
1181 if (component.is_null()) {
1182 warning(WARN_COLOR, "missing gray values");
1183 return 0;
1185 const char *s = component.contents();
1186 color *col = new color;
1187 if (*s == '#') {
1188 if (!col->read_gray(s)) {
1189 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1190 delete col;
1191 return 0;
1194 else {
1195 input_stack::push(make_temp_iterator("\n"));
1196 input_stack::push(make_temp_iterator(s));
1197 tok.next();
1198 unsigned int g = get_color_element("gray", "gray value");
1199 col->set_gray(g);
1201 return col;
1204 static void activate_color()
1206 int n;
1207 if (has_arg() && get_integer(&n))
1208 color_flag = n != 0;
1209 else
1210 color_flag = 1;
1211 skip_line();
1214 static void define_color()
1216 symbol color_name = get_long_name(1);
1217 if (color_name.is_null()) {
1218 skip_line();
1219 return;
1221 if (color_name == default_symbol) {
1222 warning(WARN_COLOR, "default color can't be redefined");
1223 skip_line();
1224 return;
1226 symbol style = get_long_name(1);
1227 if (style.is_null()) {
1228 skip_line();
1229 return;
1231 color *col;
1232 if (strcmp(style.contents(), "rgb") == 0)
1233 col = read_rgb();
1234 else if (strcmp(style.contents(), "cmyk") == 0)
1235 col = read_cmyk();
1236 else if (strcmp(style.contents(), "gray") == 0)
1237 col = read_gray();
1238 else if (strcmp(style.contents(), "grey") == 0)
1239 col = read_gray();
1240 else if (strcmp(style.contents(), "cmy") == 0)
1241 col = read_cmy();
1242 else {
1243 warning(WARN_COLOR,
1244 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1245 style.contents());
1246 skip_line();
1247 return;
1249 if (col)
1250 (void)color_dictionary.lookup(color_name, col);
1251 skip_line();
1254 static node *do_overstrike()
1256 token start;
1257 overstrike_node *on = new overstrike_node;
1258 int start_level = input_stack::get_level();
1259 start.next();
1260 for (;;) {
1261 tok.next();
1262 if (tok.newline() || tok.eof()) {
1263 warning(WARN_DELIM, "missing closing delimiter");
1264 break;
1266 if (tok == start
1267 && (compatible_flag || input_stack::get_level() == start_level))
1268 break;
1269 charinfo *ci = tok.get_char(1);
1270 if (ci) {
1271 node *n = curenv->make_char_node(ci);
1272 if (n)
1273 on->overstrike(n);
1276 return on;
1279 static node *do_bracket()
1281 token start;
1282 bracket_node *bn = new bracket_node;
1283 start.next();
1284 int start_level = input_stack::get_level();
1285 for (;;) {
1286 tok.next();
1287 if (tok.eof()) {
1288 warning(WARN_DELIM, "missing closing delimiter");
1289 break;
1291 if (tok.newline()) {
1292 warning(WARN_DELIM, "missing closing delimiter");
1293 input_stack::push(make_temp_iterator("\n"));
1294 break;
1296 if (tok == start
1297 && (compatible_flag || input_stack::get_level() == start_level))
1298 break;
1299 charinfo *ci = tok.get_char(1);
1300 if (ci) {
1301 node *n = curenv->make_char_node(ci);
1302 if (n)
1303 bn->bracket(n);
1306 return bn;
1309 static int do_name_test()
1311 token start;
1312 start.next();
1313 int start_level = input_stack::get_level();
1314 int bad_char = 0;
1315 int some_char = 0;
1316 for (;;) {
1317 tok.next();
1318 if (tok.newline() || tok.eof()) {
1319 warning(WARN_DELIM, "missing closing delimiter");
1320 break;
1322 if (tok == start
1323 && (compatible_flag || input_stack::get_level() == start_level))
1324 break;
1325 if (!tok.ch())
1326 bad_char = 1;
1327 some_char = 1;
1329 return some_char && !bad_char;
1332 static int do_expr_test()
1334 token start;
1335 start.next();
1336 int start_level = input_stack::get_level();
1337 if (!start.delimiter(1))
1338 return 0;
1339 tok.next();
1340 // disable all warning and error messages temporarily
1341 int saved_warning_mask = warning_mask;
1342 int saved_inhibit_errors = inhibit_errors;
1343 warning_mask = 0;
1344 inhibit_errors = 1;
1345 int dummy;
1346 int result = get_number_rigidly(&dummy, 'u');
1347 warning_mask = saved_warning_mask;
1348 inhibit_errors = saved_inhibit_errors;
1349 if (tok == start && input_stack::get_level() == start_level)
1350 return result;
1351 // ignore everything up to the delimiter in case we aren't right there
1352 for (;;) {
1353 tok.next();
1354 if (tok.newline() || tok.eof()) {
1355 warning(WARN_DELIM, "missing closing delimiter");
1356 break;
1358 if (tok == start && input_stack::get_level() == start_level)
1359 break;
1361 return 0;
1364 #if 0
1365 static node *do_zero_width()
1367 token start;
1368 start.next();
1369 int start_level = input_stack::get_level();
1370 environment env(curenv);
1371 environment *oldenv = curenv;
1372 curenv = &env;
1373 for (;;) {
1374 tok.next();
1375 if (tok.newline() || tok.eof()) {
1376 error("missing closing delimiter");
1377 break;
1379 if (tok == start
1380 && (compatible_flag || input_stack::get_level() == start_level))
1381 break;
1382 tok.process();
1384 curenv = oldenv;
1385 node *rev = env.extract_output_line();
1386 node *n = 0;
1387 while (rev) {
1388 node *tem = rev;
1389 rev = rev->next;
1390 tem->next = n;
1391 n = tem;
1393 return new zero_width_node(n);
1396 #else
1398 // It's undesirable for \Z to change environments, because then
1399 // \n(.w won't work as expected.
1401 static node *do_zero_width()
1403 node *rev = new dummy_node;
1404 token start;
1405 start.next();
1406 int start_level = input_stack::get_level();
1407 for (;;) {
1408 tok.next();
1409 if (tok.newline() || tok.eof()) {
1410 warning(WARN_DELIM, "missing closing delimiter");
1411 break;
1413 if (tok == start
1414 && (compatible_flag || input_stack::get_level() == start_level))
1415 break;
1416 if (!tok.add_to_node_list(&rev))
1417 error("invalid token in argument to \\Z");
1419 node *n = 0;
1420 while (rev) {
1421 node *tem = rev;
1422 rev = rev->next;
1423 tem->next = n;
1424 n = tem;
1426 return new zero_width_node(n);
1429 #endif
1431 token_node *node::get_token_node()
1433 return 0;
1436 class token_node : public node {
1437 public:
1438 token tk;
1439 token_node(const token &t);
1440 node *copy();
1441 token_node *get_token_node();
1442 int same(node *);
1443 const char *type();
1444 int force_tprint();
1447 token_node::token_node(const token &t) : tk(t)
1451 node *token_node::copy()
1453 return new token_node(tk);
1456 token_node *token_node::get_token_node()
1458 return this;
1461 int token_node::same(node *nd)
1463 return tk == ((token_node *)nd)->tk;
1466 const char *token_node::type()
1468 return "token_node";
1471 int token_node::force_tprint()
1473 return 0;
1476 token::token() : nd(0), type(TOKEN_EMPTY)
1480 token::~token()
1482 delete nd;
1485 token::token(const token &t)
1486 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1488 // Use two statements to work around bug in SGI C++.
1489 node *tem = t.nd;
1490 nd = tem ? tem->copy() : 0;
1493 void token::operator=(const token &t)
1495 delete nd;
1496 nm = t.nm;
1497 // Use two statements to work around bug in SGI C++.
1498 node *tem = t.nd;
1499 nd = tem ? tem->copy() : 0;
1500 c = t.c;
1501 val = t.val;
1502 dim = t.dim;
1503 type = t.type;
1506 void token::skip()
1508 while (space())
1509 next();
1512 int has_arg()
1514 while (tok.space())
1515 tok.next();
1516 return !tok.newline();
1519 void token::make_space()
1521 type = TOKEN_SPACE;
1524 void token::make_newline()
1526 type = TOKEN_NEWLINE;
1529 void token::next()
1531 if (nd) {
1532 delete nd;
1533 nd = 0;
1535 units x;
1536 for (;;) {
1537 node *n;
1538 int cc = input_stack::get(&n);
1539 if (cc != escape_char || escape_char == 0) {
1540 handle_normal_char:
1541 switch(cc) {
1542 case COMPATIBLE_SAVE:
1543 input_stack::save_compatible_flag(compatible_flag);
1544 compatible_flag = 0;
1545 continue;
1546 case COMPATIBLE_RESTORE:
1547 compatible_flag = input_stack::get_compatible_flag();
1548 continue;
1549 case EOF:
1550 type = TOKEN_EOF;
1551 return;
1552 case TRANSPARENT_FILE_REQUEST:
1553 case TITLE_REQUEST:
1554 case COPY_FILE_REQUEST:
1555 #ifdef COLUMN
1556 case VJUSTIFY_REQUEST:
1557 #endif /* COLUMN */
1558 type = TOKEN_REQUEST;
1559 c = cc;
1560 return;
1561 case BEGIN_TRAP:
1562 type = TOKEN_BEGIN_TRAP;
1563 return;
1564 case END_TRAP:
1565 type = TOKEN_END_TRAP;
1566 return;
1567 case LAST_PAGE_EJECTOR:
1568 seen_last_page_ejector = 1;
1569 // fall through
1570 case PAGE_EJECTOR:
1571 type = TOKEN_PAGE_EJECTOR;
1572 return;
1573 case ESCAPE_PERCENT:
1574 ESCAPE_PERCENT:
1575 type = TOKEN_HYPHEN_INDICATOR;
1576 return;
1577 case ESCAPE_SPACE:
1578 ESCAPE_SPACE:
1579 type = TOKEN_UNSTRETCHABLE_SPACE;
1580 return;
1581 case ESCAPE_TILDE:
1582 ESCAPE_TILDE:
1583 type = TOKEN_STRETCHABLE_SPACE;
1584 return;
1585 case ESCAPE_COLON:
1586 ESCAPE_COLON:
1587 type = TOKEN_ZERO_WIDTH_BREAK;
1588 return;
1589 case ESCAPE_e:
1590 ESCAPE_e:
1591 type = TOKEN_ESCAPE;
1592 return;
1593 case ESCAPE_E:
1594 goto handle_escape_char;
1595 case ESCAPE_BAR:
1596 ESCAPE_BAR:
1597 type = TOKEN_NODE;
1598 nd = new hmotion_node(curenv->get_narrow_space_width());
1599 return;
1600 case ESCAPE_CIRCUMFLEX:
1601 ESCAPE_CIRCUMFLEX:
1602 type = TOKEN_NODE;
1603 nd = new hmotion_node(curenv->get_half_narrow_space_width());
1604 return;
1605 case ESCAPE_NEWLINE:
1606 break;
1607 case ESCAPE_LEFT_BRACE:
1608 ESCAPE_LEFT_BRACE:
1609 type = TOKEN_LEFT_BRACE;
1610 return;
1611 case ESCAPE_RIGHT_BRACE:
1612 ESCAPE_RIGHT_BRACE:
1613 type = TOKEN_RIGHT_BRACE;
1614 return;
1615 case ESCAPE_LEFT_QUOTE:
1616 ESCAPE_LEFT_QUOTE:
1617 type = TOKEN_SPECIAL;
1618 nm = symbol("ga");
1619 return;
1620 case ESCAPE_RIGHT_QUOTE:
1621 ESCAPE_RIGHT_QUOTE:
1622 type = TOKEN_SPECIAL;
1623 nm = symbol("aa");
1624 return;
1625 case ESCAPE_HYPHEN:
1626 ESCAPE_HYPHEN:
1627 type = TOKEN_SPECIAL;
1628 nm = symbol("-");
1629 return;
1630 case ESCAPE_UNDERSCORE:
1631 ESCAPE_UNDERSCORE:
1632 type = TOKEN_SPECIAL;
1633 nm = symbol("ul");
1634 return;
1635 case ESCAPE_c:
1636 ESCAPE_c:
1637 type = TOKEN_INTERRUPT;
1638 return;
1639 case ESCAPE_BANG:
1640 ESCAPE_BANG:
1641 type = TOKEN_TRANSPARENT;
1642 return;
1643 case ESCAPE_QUESTION:
1644 ESCAPE_QUESTION:
1645 nd = do_non_interpreted();
1646 if (nd) {
1647 type = TOKEN_NODE;
1648 return;
1650 break;
1651 case ESCAPE_AMPERSAND:
1652 ESCAPE_AMPERSAND:
1653 type = TOKEN_DUMMY;
1654 return;
1655 case ESCAPE_RIGHT_PARENTHESIS:
1656 ESCAPE_RIGHT_PARENTHESIS:
1657 type = TOKEN_TRANSPARENT_DUMMY;
1658 return;
1659 case '\b':
1660 type = TOKEN_BACKSPACE;
1661 return;
1662 case ' ':
1663 type = TOKEN_SPACE;
1664 return;
1665 case '\t':
1666 type = TOKEN_TAB;
1667 return;
1668 case '\n':
1669 type = TOKEN_NEWLINE;
1670 return;
1671 case '\001':
1672 type = TOKEN_LEADER;
1673 return;
1674 case 0:
1676 assert(n != 0);
1677 token_node *tn = n->get_token_node();
1678 if (tn) {
1679 *this = tn->tk;
1680 delete tn;
1682 else {
1683 nd = n;
1684 type = TOKEN_NODE;
1687 return;
1688 default:
1689 type = TOKEN_CHAR;
1690 c = cc;
1691 return;
1694 else {
1695 handle_escape_char:
1696 cc = input_stack::get(0);
1697 switch(cc) {
1698 case '(':
1699 nm = read_two_char_escape_name();
1700 type = TOKEN_SPECIAL;
1701 return;
1702 case EOF:
1703 type = TOKEN_EOF;
1704 error("end of input after escape character");
1705 return;
1706 case '`':
1707 goto ESCAPE_LEFT_QUOTE;
1708 case '\'':
1709 goto ESCAPE_RIGHT_QUOTE;
1710 case '-':
1711 goto ESCAPE_HYPHEN;
1712 case '_':
1713 goto ESCAPE_UNDERSCORE;
1714 case '%':
1715 goto ESCAPE_PERCENT;
1716 case ' ':
1717 goto ESCAPE_SPACE;
1718 case '0':
1719 nd = new hmotion_node(curenv->get_digit_width());
1720 type = TOKEN_NODE;
1721 return;
1722 case '|':
1723 goto ESCAPE_BAR;
1724 case '^':
1725 goto ESCAPE_CIRCUMFLEX;
1726 case '/':
1727 type = TOKEN_ITALIC_CORRECTION;
1728 return;
1729 case ',':
1730 type = TOKEN_NODE;
1731 nd = new left_italic_corrected_node;
1732 return;
1733 case '&':
1734 goto ESCAPE_AMPERSAND;
1735 case ')':
1736 goto ESCAPE_RIGHT_PARENTHESIS;
1737 case '!':
1738 goto ESCAPE_BANG;
1739 case '?':
1740 goto ESCAPE_QUESTION;
1741 case '~':
1742 goto ESCAPE_TILDE;
1743 case ':':
1744 goto ESCAPE_COLON;
1745 case '"':
1746 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1748 if (cc == '\n')
1749 type = TOKEN_NEWLINE;
1750 else
1751 type = TOKEN_EOF;
1752 return;
1753 case '#': // Like \" but newline is ignored.
1754 while ((cc = input_stack::get(0)) != '\n')
1755 if (cc == EOF) {
1756 type = TOKEN_EOF;
1757 return;
1759 break;
1760 case '$':
1762 symbol nm = read_escape_name();
1763 if (!(nm.is_null() || nm.is_empty()))
1764 interpolate_arg(nm);
1765 break;
1767 case '*':
1769 symbol nm = read_escape_name(WITH_ARGS);
1770 if (!(nm.is_null() || nm.is_empty())) {
1771 if (have_string_arg) {
1772 have_string_arg = 0;
1773 interpolate_string_with_args(nm);
1775 else
1776 interpolate_string(nm);
1778 break;
1780 case 'a':
1781 nd = new non_interpreted_char_node('\001');
1782 type = TOKEN_NODE;
1783 return;
1784 case 'A':
1785 c = '0' + do_name_test();
1786 type = TOKEN_CHAR;
1787 return;
1788 case 'b':
1789 nd = do_bracket();
1790 type = TOKEN_NODE;
1791 return;
1792 case 'B':
1793 c = '0' + do_expr_test();
1794 type = TOKEN_CHAR;
1795 return;
1796 case 'c':
1797 goto ESCAPE_c;
1798 case 'C':
1799 nm = get_delim_name();
1800 if (nm.is_null())
1801 break;
1802 type = TOKEN_SPECIAL;
1803 return;
1804 case 'd':
1805 type = TOKEN_NODE;
1806 nd = new vmotion_node(curenv->get_size()/2);
1807 return;
1808 case 'D':
1809 nd = read_draw_node();
1810 if (!nd)
1811 break;
1812 type = TOKEN_NODE;
1813 return;
1814 case 'e':
1815 goto ESCAPE_e;
1816 case 'E':
1817 goto handle_escape_char;
1818 case 'f':
1820 symbol s = read_escape_name(ALLOW_EMPTY);
1821 if (s.is_null())
1822 break;
1823 const char *p;
1824 for (p = s.contents(); *p != '\0'; p++)
1825 if (!csdigit(*p))
1826 break;
1827 if (*p || s.is_empty())
1828 curenv->set_font(s);
1829 else
1830 curenv->set_font(atoi(s.contents()));
1831 if (!compatible_flag)
1832 have_input = 1;
1833 break;
1835 case 'F':
1837 symbol s = read_escape_name(ALLOW_EMPTY);
1838 if (s.is_null())
1839 break;
1840 curenv->set_family(s);
1841 break;
1843 case 'g':
1845 symbol s = read_escape_name();
1846 if (!(s.is_null() || s.is_empty()))
1847 interpolate_number_format(s);
1848 break;
1850 case 'h':
1851 if (!get_delim_number(&x, 'm'))
1852 break;
1853 type = TOKEN_NODE;
1854 nd = new hmotion_node(x);
1855 return;
1856 case 'H':
1857 // don't take height increments relative to previous height if
1858 // in compatibility mode
1859 if (!compatible_flag && curenv->get_char_height())
1861 if (get_delim_number(&x, 'z', curenv->get_char_height()))
1862 curenv->set_char_height(x);
1864 else
1866 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1867 curenv->set_char_height(x);
1869 if (!compatible_flag)
1870 have_input = 1;
1871 break;
1872 case 'k':
1873 nm = read_escape_name();
1874 if (nm.is_null() || nm.is_empty())
1875 break;
1876 type = TOKEN_MARK_INPUT;
1877 return;
1878 case 'l':
1879 case 'L':
1881 charinfo *s = 0;
1882 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1883 break;
1884 if (s == 0)
1885 s = get_charinfo(cc == 'l' ? "ru" : "br");
1886 type = TOKEN_NODE;
1887 node *n = curenv->make_char_node(s);
1888 if (cc == 'l')
1889 nd = new hline_node(x, n);
1890 else
1891 nd = new vline_node(x, n);
1892 return;
1894 case 'm':
1895 nd = do_glyph_color(read_escape_name(ALLOW_EMPTY));
1896 if (!nd)
1897 break;
1898 type = TOKEN_NODE;
1899 return;
1900 case 'M':
1901 nd = do_fill_color(read_escape_name(ALLOW_EMPTY));
1902 if (!nd)
1903 break;
1904 type = TOKEN_NODE;
1905 return;
1906 case 'n':
1908 int inc;
1909 symbol nm = read_increment_and_escape_name(&inc);
1910 if (!(nm.is_null() || nm.is_empty()))
1911 interpolate_number_reg(nm, inc);
1912 break;
1914 case 'N':
1915 if (!get_delim_number(&val, 0))
1916 break;
1917 type = TOKEN_NUMBERED_CHAR;
1918 return;
1919 case 'o':
1920 nd = do_overstrike();
1921 type = TOKEN_NODE;
1922 return;
1923 case 'O':
1924 nd = do_suppress(read_escape_name());
1925 if (!nd)
1926 break;
1927 type = TOKEN_NODE;
1928 return;
1929 case 'p':
1930 type = TOKEN_SPREAD;
1931 return;
1932 case 'r':
1933 type = TOKEN_NODE;
1934 nd = new vmotion_node(-curenv->get_size());
1935 return;
1936 case 'R':
1937 do_register();
1938 if (!compatible_flag)
1939 have_input = 1;
1940 break;
1941 case 's':
1942 if (read_size(&x))
1943 curenv->set_size(x);
1944 if (!compatible_flag)
1945 have_input = 1;
1946 break;
1947 case 'S':
1948 if (get_delim_number(&x, 0))
1949 curenv->set_char_slant(x);
1950 if (!compatible_flag)
1951 have_input = 1;
1952 break;
1953 case 't':
1954 type = TOKEN_NODE;
1955 nd = new non_interpreted_char_node('\t');
1956 return;
1957 case 'u':
1958 type = TOKEN_NODE;
1959 nd = new vmotion_node(-curenv->get_size()/2);
1960 return;
1961 case 'v':
1962 if (!get_delim_number(&x, 'v'))
1963 break;
1964 type = TOKEN_NODE;
1965 nd = new vmotion_node(x);
1966 return;
1967 case 'V':
1969 symbol nm = read_escape_name();
1970 if (!(nm.is_null() || nm.is_empty()))
1971 interpolate_environment_variable(nm);
1972 break;
1974 case 'w':
1975 do_width();
1976 break;
1977 case 'x':
1978 if (!get_delim_number(&x, 'v'))
1979 break;
1980 type = TOKEN_NODE;
1981 nd = new extra_size_node(x);
1982 return;
1983 case 'X':
1984 nd = do_special();
1985 if (!nd)
1986 break;
1987 type = TOKEN_NODE;
1988 return;
1989 case 'Y':
1991 symbol s = read_escape_name();
1992 if (s.is_null() || s.is_empty())
1993 break;
1994 request_or_macro *p = lookup_request(s);
1995 macro *m = p->to_macro();
1996 if (!m) {
1997 error("can't transparently throughput a request");
1998 break;
2000 nd = new special_node(*m);
2001 type = TOKEN_NODE;
2002 return;
2004 case 'z':
2006 next();
2007 if (type == TOKEN_NODE)
2008 nd = new zero_width_node(nd);
2009 else {
2010 charinfo *ci = get_char(1);
2011 if (ci == 0)
2012 break;
2013 node *gn = curenv->make_char_node(ci);
2014 if (gn == 0)
2015 break;
2016 nd = new zero_width_node(gn);
2017 type = TOKEN_NODE;
2019 return;
2021 case 'Z':
2022 nd = do_zero_width();
2023 if (nd == 0)
2024 break;
2025 type = TOKEN_NODE;
2026 return;
2027 case '{':
2028 goto ESCAPE_LEFT_BRACE;
2029 case '}':
2030 goto ESCAPE_RIGHT_BRACE;
2031 case '\n':
2032 break;
2033 case '[':
2034 if (!compatible_flag) {
2035 nm = read_long_escape_name();
2036 if (nm.is_null() || nm.is_empty())
2037 break;
2038 type = TOKEN_SPECIAL;
2039 return;
2041 goto handle_normal_char;
2042 default:
2043 if (cc != escape_char && cc != '.')
2044 warning(WARN_ESCAPE, "escape character ignored before %1",
2045 input_char_description(cc));
2046 goto handle_normal_char;
2052 int token::operator==(const token &t)
2054 if (type != t.type)
2055 return 0;
2056 switch(type) {
2057 case TOKEN_CHAR:
2058 return c == t.c;
2059 case TOKEN_SPECIAL:
2060 return nm == t.nm;
2061 case TOKEN_NUMBERED_CHAR:
2062 return val == t.val;
2063 default:
2064 return 1;
2068 int token::operator!=(const token &t)
2070 return !(*this == t);
2073 // is token a suitable delimiter (like ')?
2075 int token::delimiter(int err)
2077 switch(type) {
2078 case TOKEN_CHAR:
2079 switch(c) {
2080 case '0':
2081 case '1':
2082 case '2':
2083 case '3':
2084 case '4':
2085 case '5':
2086 case '6':
2087 case '7':
2088 case '8':
2089 case '9':
2090 case '+':
2091 case '-':
2092 case '/':
2093 case '*':
2094 case '%':
2095 case '<':
2096 case '>':
2097 case '=':
2098 case '&':
2099 case ':':
2100 case '(':
2101 case ')':
2102 case '.':
2103 if (err)
2104 error("cannot use character `%1' as a starting delimiter", char(c));
2105 return 0;
2106 default:
2107 return 1;
2109 case TOKEN_NODE:
2110 case TOKEN_SPACE:
2111 case TOKEN_STRETCHABLE_SPACE:
2112 case TOKEN_UNSTRETCHABLE_SPACE:
2113 case TOKEN_TAB:
2114 case TOKEN_NEWLINE:
2115 if (err)
2116 error("cannot use %1 as a starting delimiter", description());
2117 return 0;
2118 default:
2119 return 1;
2123 const char *token::description()
2125 static char buf[4];
2126 switch (type) {
2127 case TOKEN_BACKSPACE:
2128 return "a backspace character";
2129 case TOKEN_CHAR:
2130 buf[0] = '`';
2131 buf[1] = c;
2132 buf[2] = '\'';
2133 buf[3] = '\0';
2134 return buf;
2135 case TOKEN_DUMMY:
2136 return "`\\&'";
2137 case TOKEN_ESCAPE:
2138 return "`\\e'";
2139 case TOKEN_HYPHEN_INDICATOR:
2140 return "`\\%'";
2141 case TOKEN_INTERRUPT:
2142 return "`\\c'";
2143 case TOKEN_ITALIC_CORRECTION:
2144 return "`\\/'";
2145 case TOKEN_LEADER:
2146 return "a leader character";
2147 case TOKEN_LEFT_BRACE:
2148 return "`\\{'";
2149 case TOKEN_MARK_INPUT:
2150 return "`\\k'";
2151 case TOKEN_NEWLINE:
2152 return "newline";
2153 case TOKEN_NODE:
2154 return "a node";
2155 case TOKEN_NUMBERED_CHAR:
2156 return "`\\N'";
2157 case TOKEN_RIGHT_BRACE:
2158 return "`\\}'";
2159 case TOKEN_SPACE:
2160 return "a space";
2161 case TOKEN_SPECIAL:
2162 return "a special character";
2163 case TOKEN_SPREAD:
2164 return "`\\p'";
2165 case TOKEN_STRETCHABLE_SPACE:
2166 return "`\\~'";
2167 case TOKEN_UNSTRETCHABLE_SPACE:
2168 return "`\\ '";
2169 case TOKEN_TAB:
2170 return "a tab character";
2171 case TOKEN_TRANSPARENT:
2172 return "`\\!'";
2173 case TOKEN_TRANSPARENT_DUMMY:
2174 return "`\\)'";
2175 case TOKEN_ZERO_WIDTH_BREAK:
2176 return "`\\:'";
2177 case TOKEN_EOF:
2178 return "end of input";
2179 default:
2180 break;
2182 return "a magic token";
2185 void skip_line()
2187 while (!tok.newline())
2188 if (tok.eof())
2189 return;
2190 else
2191 tok.next();
2192 tok.next();
2195 void compatible()
2197 int n;
2198 if (has_arg() && get_integer(&n))
2199 compatible_flag = n != 0;
2200 else
2201 compatible_flag = 1;
2202 skip_line();
2205 static void empty_name_warning(int required)
2207 if (tok.newline() || tok.eof()) {
2208 if (required)
2209 warning(WARN_MISSING, "missing name");
2211 else if (tok.right_brace() || tok.tab()) {
2212 const char *start = tok.description();
2213 do {
2214 tok.next();
2215 } while (tok.space() || tok.right_brace() || tok.tab());
2216 if (!tok.newline() && !tok.eof())
2217 error("%1 is not allowed before an argument", start);
2218 else if (required)
2219 warning(WARN_MISSING, "missing name");
2221 else if (required)
2222 error("name expected (got %1)", tok.description());
2223 else
2224 error("name expected (got %1): treated as missing", tok.description());
2227 static void non_empty_name_warning()
2229 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2230 && !tok.right_brace()
2231 // We don't want to give a warning for .el\{
2232 && !tok.left_brace())
2233 error("%1 is not allowed in a name", tok.description());
2236 symbol get_name(int required)
2238 if (compatible_flag) {
2239 char buf[3];
2240 tok.skip();
2241 if ((buf[0] = tok.ch()) != 0) {
2242 tok.next();
2243 if ((buf[1] = tok.ch()) != 0) {
2244 buf[2] = 0;
2245 tok.make_space();
2247 else
2248 non_empty_name_warning();
2249 return symbol(buf);
2251 else {
2252 empty_name_warning(required);
2253 return NULL_SYMBOL;
2256 else
2257 return get_long_name(required);
2260 symbol get_long_name(int required)
2262 while (tok.space())
2263 tok.next();
2264 char abuf[ABUF_SIZE];
2265 char *buf = abuf;
2266 int buf_size = ABUF_SIZE;
2267 int i = 0;
2268 for (;;) {
2269 if (i + 1 > buf_size) {
2270 if (buf == abuf) {
2271 buf = new char[ABUF_SIZE*2];
2272 memcpy(buf, abuf, buf_size);
2273 buf_size = ABUF_SIZE*2;
2275 else {
2276 char *old_buf = buf;
2277 buf = new char[buf_size*2];
2278 memcpy(buf, old_buf, buf_size);
2279 buf_size *= 2;
2280 a_delete old_buf;
2283 if ((buf[i] = tok.ch()) == 0)
2284 break;
2285 i++;
2286 tok.next();
2288 if (i == 0) {
2289 empty_name_warning(required);
2290 return NULL_SYMBOL;
2292 non_empty_name_warning();
2293 if (buf == abuf)
2294 return symbol(buf);
2295 else {
2296 symbol s(buf);
2297 a_delete buf;
2298 return s;
2302 void exit_troff()
2304 exit_started = 1;
2305 topdiv->set_last_page();
2306 if (!end_macro_name.is_null()) {
2307 spring_trap(end_macro_name);
2308 tok.next();
2309 process_input_stack();
2311 curenv->final_break();
2312 tok.next();
2313 process_input_stack();
2314 end_diversions();
2315 if (topdiv->get_page_length() > 0) {
2316 done_end_macro = 1;
2317 topdiv->set_ejecting();
2318 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2319 input_stack::push(make_temp_iterator((char *)buf));
2320 topdiv->space(topdiv->get_page_length(), 1);
2321 tok.next();
2322 process_input_stack();
2323 seen_last_page_ejector = 1; // should be set already
2324 topdiv->set_ejecting();
2325 push_page_ejector();
2326 topdiv->space(topdiv->get_page_length(), 1);
2327 tok.next();
2328 process_input_stack();
2330 // This will only happen if a trap-invoked macro starts a diversion,
2331 // or if vertical position traps have been disabled.
2332 cleanup_and_exit(0);
2335 // This implements .ex. The input stack must be cleared before calling
2336 // exit_troff().
2338 void exit_request()
2340 input_stack::clear();
2341 if (exit_started)
2342 tok.next();
2343 else
2344 exit_troff();
2347 void return_macro_request()
2349 input_stack::pop_macro();
2350 tok.next();
2353 void end_macro()
2355 end_macro_name = get_name();
2356 skip_line();
2359 void blank_line_macro()
2361 blank_line_macro_name = get_name();
2362 skip_line();
2365 static void trapping_blank_line()
2367 if (!blank_line_macro_name.is_null())
2368 spring_trap(blank_line_macro_name);
2369 else
2370 blank_line();
2373 void do_request()
2375 int old_compatible_flag = compatible_flag;
2376 compatible_flag = 0;
2377 symbol nm = get_name();
2378 if (nm.is_null())
2379 skip_line();
2380 else
2381 interpolate_macro(nm);
2382 compatible_flag = old_compatible_flag;
2385 inline int possibly_handle_first_page_transition()
2387 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2388 handle_first_page_transition();
2389 return 1;
2391 else
2392 return 0;
2395 static int transparent_translate(int cc)
2397 if (!invalid_input_char(cc)) {
2398 charinfo *ci = charset_table[cc];
2399 switch (ci->get_special_translation(1)) {
2400 case charinfo::TRANSLATE_SPACE:
2401 return ' ';
2402 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2403 return ESCAPE_TILDE;
2404 case charinfo::TRANSLATE_DUMMY:
2405 return ESCAPE_AMPERSAND;
2406 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2407 return ESCAPE_PERCENT;
2409 // This is really ugly.
2410 ci = ci->get_translation(1);
2411 if (ci) {
2412 int c = ci->get_ascii_code();
2413 if (c != '\0')
2414 return c;
2415 error("can't translate %1 to special character `%2'"
2416 " in transparent throughput",
2417 input_char_description(cc),
2418 ci->nm.contents());
2421 return cc;
2424 class int_stack {
2425 struct int_stack_element {
2426 int n;
2427 int_stack_element *next;
2428 } *top;
2429 public:
2430 int_stack();
2431 ~int_stack();
2432 void push(int);
2433 int is_empty();
2434 int pop();
2437 int_stack::int_stack()
2439 top = 0;
2442 int_stack::~int_stack()
2444 while (top != 0) {
2445 int_stack_element *temp = top;
2446 top = top->next;
2447 delete temp;
2451 int int_stack::is_empty()
2453 return top == 0;
2456 void int_stack::push(int n)
2458 int_stack_element *p = new int_stack_element;
2459 p->next = top;
2460 p->n = n;
2461 top = p;
2464 int int_stack::pop()
2466 assert(top != 0);
2467 int_stack_element *p = top;
2468 top = top->next;
2469 int n = p->n;
2470 delete p;
2471 return n;
2474 int node::reread(int *)
2476 return 0;
2479 int diverted_space_node::reread(int *bolp)
2481 if (curenv->get_fill())
2482 trapping_blank_line();
2483 else
2484 curdiv->space(n);
2485 *bolp = 1;
2486 return 1;
2489 int diverted_copy_file_node::reread(int *bolp)
2491 curdiv->copy_file(filename.contents());
2492 *bolp = 1;
2493 return 1;
2496 int word_space_node::reread(int *bolp)
2498 if (unformat) {
2499 for (width_list *w = orig_width; w; w = w->next)
2500 curenv->space(w->width, w->sentence_width);
2501 unformat = 0;
2502 return 1;
2504 return 0;
2507 int unbreakable_space_node::reread(int *)
2509 return 0;
2512 int hmotion_node::reread(int *bolp)
2514 if (unformat && was_tab) {
2515 curenv->handle_tab(0);
2516 unformat = 0;
2517 return 1;
2519 return 0;
2522 void process_input_stack()
2524 int_stack trap_bol_stack;
2525 int bol = 1;
2526 for (;;) {
2527 int suppress_next = 0;
2528 switch (tok.type) {
2529 case token::TOKEN_CHAR:
2531 unsigned char ch = tok.c;
2532 if (bol && !have_input
2533 && (ch == curenv->control_char
2534 || ch == curenv->no_break_control_char)) {
2535 break_flag = ch == curenv->control_char;
2536 // skip tabs as well as spaces here
2537 do {
2538 tok.next();
2539 } while (tok.white_space());
2540 symbol nm = get_name();
2541 if (nm.is_null())
2542 skip_line();
2543 else
2544 interpolate_macro(nm);
2545 suppress_next = 1;
2546 have_input = 0;
2548 else {
2549 if (possibly_handle_first_page_transition())
2551 else {
2552 for (;;) {
2553 curenv->add_char(charset_table[ch]);
2554 tok.next();
2555 if (tok.type != token::TOKEN_CHAR)
2556 break;
2557 ch = tok.c;
2559 suppress_next = 1;
2560 bol = 0;
2563 break;
2565 case token::TOKEN_TRANSPARENT:
2567 if (bol) {
2568 if (possibly_handle_first_page_transition())
2570 else {
2571 int cc;
2572 do {
2573 node *n;
2574 cc = get_copy(&n);
2575 if (cc != EOF)
2576 if (cc != '\0')
2577 curdiv->transparent_output(transparent_translate(cc));
2578 else
2579 curdiv->transparent_output(n);
2580 } while (cc != '\n' && cc != EOF);
2581 if (cc == EOF)
2582 curdiv->transparent_output('\n');
2585 break;
2587 case token::TOKEN_NEWLINE:
2589 if (bol && !have_input
2590 && !curenv->get_prev_line_interrupted())
2591 trapping_blank_line();
2592 else {
2593 curenv->newline();
2594 bol = 1;
2595 have_input = 0;
2597 break;
2599 case token::TOKEN_REQUEST:
2601 int request_code = tok.c;
2602 tok.next();
2603 switch (request_code) {
2604 case TITLE_REQUEST:
2605 title();
2606 break;
2607 case COPY_FILE_REQUEST:
2608 copy_file();
2609 break;
2610 case TRANSPARENT_FILE_REQUEST:
2611 transparent_file();
2612 break;
2613 #ifdef COLUMN
2614 case VJUSTIFY_REQUEST:
2615 vjustify();
2616 break;
2617 #endif /* COLUMN */
2618 default:
2619 assert(0);
2620 break;
2622 suppress_next = 1;
2623 have_input = 0;
2624 break;
2626 case token::TOKEN_SPACE:
2628 if (possibly_handle_first_page_transition())
2630 else if (bol && !curenv->get_prev_line_interrupted()) {
2631 int nspaces = 0;
2632 // save space_width now so that it isn't changed by \f or \s
2633 // which we wouldn't notice here
2634 hunits space_width = curenv->get_space_width();
2635 do {
2636 nspaces += tok.nspaces();
2637 tok.next();
2638 } while (tok.space());
2639 if (tok.newline())
2640 trapping_blank_line();
2641 else {
2642 push_token(tok);
2643 curenv->do_break();
2644 curenv->add_node(new hmotion_node(space_width * nspaces));
2645 bol = 0;
2648 else {
2649 curenv->space();
2650 bol = 0;
2652 break;
2654 case token::TOKEN_EOF:
2655 return;
2656 case token::TOKEN_NODE:
2658 if (possibly_handle_first_page_transition())
2660 else if (tok.nd->reread(&bol)) {
2661 delete tok.nd;
2662 tok.nd = 0;
2664 else {
2665 curenv->add_node(tok.nd);
2666 tok.nd = 0;
2667 bol = 0;
2668 curenv->possibly_break_line(1);
2670 break;
2672 case token::TOKEN_PAGE_EJECTOR:
2674 continue_page_eject();
2675 // I think we just want to preserve bol.
2676 // bol = 1;
2677 break;
2679 case token::TOKEN_BEGIN_TRAP:
2681 trap_bol_stack.push(bol);
2682 bol = 1;
2683 have_input = 0;
2684 break;
2686 case token::TOKEN_END_TRAP:
2688 if (trap_bol_stack.is_empty())
2689 error("spurious end trap token detected!");
2690 else
2691 bol = trap_bol_stack.pop();
2693 /* I'm not totally happy about this. But I can't think of any other
2694 way to do it. Doing an output_pending_lines() whenever a
2695 TOKEN_END_TRAP is detected doesn't work: for example,
2697 .wh -1i x
2698 .de x
2701 .wh -.5i y
2702 .de y
2703 .tl ''-%-''
2706 .ll .5i
2707 .sp |\n(.pu-1i-.5v
2708 a\%very\%very\%long\%word
2710 will print all but the first lines from the word immediately
2711 after the footer, rather than on the next page. */
2713 if (trap_bol_stack.is_empty())
2714 curenv->output_pending_lines();
2715 break;
2717 default:
2719 bol = 0;
2720 tok.process();
2721 break;
2724 if (!suppress_next)
2725 tok.next();
2726 trap_sprung_flag = 0;
2730 #ifdef WIDOW_CONTROL
2732 void flush_pending_lines()
2734 while (!tok.newline() && !tok.eof())
2735 tok.next();
2736 curenv->output_pending_lines();
2737 tok.next();
2740 #endif /* WIDOW_CONTROL */
2742 request_or_macro::request_or_macro()
2746 macro *request_or_macro::to_macro()
2748 return 0;
2751 request::request(REQUEST_FUNCP pp) : p(pp)
2755 void request::invoke(symbol)
2757 (*p)();
2760 struct char_block {
2761 enum { SIZE = 128 };
2762 unsigned char s[SIZE];
2763 char_block *next;
2764 char_block();
2767 char_block::char_block()
2768 : next(0)
2772 class char_list {
2773 public:
2774 char_list();
2775 ~char_list();
2776 void append(unsigned char);
2777 void set(unsigned char, int);
2778 unsigned char get(int);
2779 int length();
2780 private:
2781 unsigned char *ptr;
2782 int len;
2783 char_block *head;
2784 char_block *tail;
2785 friend class macro_header;
2786 friend class string_iterator;
2789 char_list::char_list()
2790 : ptr(0), len(0), head(0), tail(0)
2794 char_list::~char_list()
2796 while (head != 0) {
2797 char_block *tem = head;
2798 head = head->next;
2799 delete tem;
2803 int char_list::length()
2805 return len;
2808 void char_list::append(unsigned char c)
2810 if (tail == 0) {
2811 head = tail = new char_block;
2812 ptr = tail->s;
2814 else {
2815 if (ptr >= tail->s + char_block::SIZE) {
2816 tail->next = new char_block;
2817 tail = tail->next;
2818 ptr = tail->s;
2821 *ptr++ = c;
2822 len++;
2825 void char_list::set(unsigned char c, int offset)
2827 assert(len > offset);
2828 // optimization for access at the end
2829 int boundary = len - len % char_block::SIZE;
2830 if (offset >= boundary) {
2831 *(tail->s + offset - boundary) = c;
2832 return;
2834 char_block *tem = head;
2835 int l = 0;
2836 for (;;) {
2837 l += char_block::SIZE;
2838 if (l > offset) {
2839 *(tem->s + offset % char_block::SIZE) = c;
2840 return;
2842 tem = tem->next;
2846 unsigned char char_list::get(int offset)
2848 assert(len > offset);
2849 // optimization for access at the end
2850 int boundary = len - len % char_block::SIZE;
2851 if (offset >= boundary)
2852 return *(tail->s + offset - boundary);
2853 char_block *tem = head;
2854 int l = 0;
2855 for (;;) {
2856 l += char_block::SIZE;
2857 if (l > offset)
2858 return *(tem->s + offset % char_block::SIZE);
2859 tem = tem->next;
2863 class node_list {
2864 node *head;
2865 node *tail;
2866 public:
2867 node_list();
2868 ~node_list();
2869 void append(node *);
2870 int length();
2871 node *extract();
2873 friend class macro_header;
2874 friend class string_iterator;
2877 void node_list::append(node *n)
2879 if (head == 0) {
2880 n->next = 0;
2881 head = tail = n;
2883 else {
2884 n->next = 0;
2885 tail = tail->next = n;
2889 int node_list::length()
2891 int total = 0;
2892 for (node *n = head; n != 0; n = n->next)
2893 ++total;
2894 return total;
2897 node_list::node_list()
2899 head = tail = 0;
2902 node *node_list::extract()
2904 node *temp = head;
2905 head = tail = 0;
2906 return temp;
2909 node_list::~node_list()
2911 delete_node_list(head);
2914 struct macro_header {
2915 public:
2916 int count;
2917 char_list cl;
2918 node_list nl;
2919 macro_header() { count = 1; }
2920 macro_header *copy(int);
2923 macro::~macro()
2925 if (p != 0 && --(p->count) <= 0)
2926 delete p;
2929 macro::macro()
2931 if (!input_stack::get_location(1, &filename, &lineno)) {
2932 filename = 0;
2933 lineno = 0;
2935 len = 0;
2936 empty_macro = 1;
2937 p = 0;
2940 macro::macro(const macro &m)
2941 : p(m.p), filename(m.filename), lineno(m.lineno), len(m.len),
2942 empty_macro(m.empty_macro)
2944 if (p != 0)
2945 p->count++;
2948 macro &macro::operator=(const macro &m)
2950 // don't assign object
2951 if (m.p != 0)
2952 m.p->count++;
2953 if (p != 0 && --(p->count) <= 0)
2954 delete p;
2955 p = m.p;
2956 filename = m.filename;
2957 lineno = m.lineno;
2958 len = m.len;
2959 empty_macro = m.empty_macro;
2960 return *this;
2963 void macro::append(unsigned char c)
2965 assert(c != 0);
2966 if (p == 0)
2967 p = new macro_header;
2968 if (p->cl.length() != len) {
2969 macro_header *tem = p->copy(len);
2970 if (--(p->count) <= 0)
2971 delete p;
2972 p = tem;
2974 p->cl.append(c);
2975 ++len;
2976 if (c != COMPATIBLE_SAVE && c != COMPATIBLE_RESTORE)
2977 empty_macro = 0;
2980 void macro::set(unsigned char c, int offset)
2982 assert(p != 0);
2983 assert(c != 0);
2984 p->cl.set(c, offset);
2987 unsigned char macro::get(int offset)
2989 assert(p != 0);
2990 return p->cl.get(offset);
2993 int macro::length()
2995 return len;
2998 void macro::append_str(const char *s)
3000 int i = 0;
3002 if (s) {
3003 while (s[i] != (char)0) {
3004 append(s[i]);
3005 i++;
3010 void macro::append(node *n)
3012 assert(n != 0);
3013 if (p == 0)
3014 p = new macro_header;
3015 if (p->cl.length() != len) {
3016 macro_header *tem = p->copy(len);
3017 if (--(p->count) <= 0)
3018 delete p;
3019 p = tem;
3021 p->cl.append(0);
3022 p->nl.append(n);
3023 ++len;
3024 empty_macro = 0;
3027 void macro::append_unsigned(unsigned int i)
3029 unsigned int j = i / 10;
3030 if (j != 0)
3031 append_unsigned(j);
3032 append(((unsigned char)(((int)'0') + i % 10)));
3035 void macro::append_int(int i)
3037 if (i < 0) {
3038 append('-');
3039 i = -i;
3041 append_unsigned((unsigned int)i);
3044 void macro::print_size()
3046 errprint("%1", len);
3049 // make a copy of the first n bytes
3051 macro_header *macro_header::copy(int n)
3053 macro_header *p = new macro_header;
3054 char_block *bp = cl.head;
3055 unsigned char *ptr = bp->s;
3056 node *nd = nl.head;
3057 while (--n >= 0) {
3058 if (ptr >= bp->s + char_block::SIZE) {
3059 bp = bp->next;
3060 ptr = bp->s;
3062 int c = *ptr++;
3063 p->cl.append(c);
3064 if (c == 0) {
3065 p->nl.append(nd->copy());
3066 nd = nd->next;
3069 return p;
3072 void print_macros()
3074 object_dictionary_iterator iter(request_dictionary);
3075 request_or_macro *rm;
3076 symbol s;
3077 while (iter.get(&s, (object **)&rm)) {
3078 assert(!s.is_null());
3079 macro *m = rm->to_macro();
3080 if (m) {
3081 errprint("%1\t", s.contents());
3082 m->print_size();
3083 errprint("\n");
3086 fflush(stderr);
3087 skip_line();
3090 class string_iterator : public input_iterator {
3091 macro mac;
3092 const char *how_invoked;
3093 int newline_flag;
3094 int lineno;
3095 char_block *bp;
3096 int count; // of characters remaining
3097 node *nd;
3098 int saved_compatible_flag;
3099 protected:
3100 symbol nm;
3101 string_iterator();
3102 public:
3103 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
3104 int fill(node **);
3105 int peek();
3106 int get_location(int, const char **, int *);
3107 void backtrace();
3108 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3109 int get_compatible_flag() { return saved_compatible_flag; }
3112 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3113 : mac(m), how_invoked(p),
3114 newline_flag(0), lineno(1), nm(s)
3116 count = mac.len;
3117 if (count != 0) {
3118 bp = mac.p->cl.head;
3119 nd = mac.p->nl.head;
3120 ptr = eptr = bp->s;
3122 else {
3123 bp = 0;
3124 nd = 0;
3125 ptr = eptr = 0;
3129 string_iterator::string_iterator()
3131 bp = 0;
3132 nd = 0;
3133 ptr = eptr = 0;
3134 newline_flag = 0;
3135 how_invoked = 0;
3136 lineno = 1;
3137 count = 0;
3140 int string_iterator::fill(node **np)
3142 if (newline_flag)
3143 lineno++;
3144 newline_flag = 0;
3145 if (count <= 0)
3146 return EOF;
3147 const unsigned char *p = eptr;
3148 if (p >= bp->s + char_block::SIZE) {
3149 bp = bp->next;
3150 p = bp->s;
3152 if (*p == '\0') {
3153 if (np)
3154 *np = nd->copy();
3155 nd = nd->next;
3156 eptr = ptr = p + 1;
3157 count--;
3158 return 0;
3160 const unsigned char *e = bp->s + char_block::SIZE;
3161 if (e - p > count)
3162 e = p + count;
3163 ptr = p;
3164 while (p < e) {
3165 unsigned char c = *p;
3166 if (c == '\n' || c == ESCAPE_NEWLINE) {
3167 newline_flag = 1;
3168 p++;
3169 break;
3171 if (c == '\0')
3172 break;
3173 p++;
3175 eptr = p;
3176 count -= p - ptr;
3177 return *ptr++;
3180 int string_iterator::peek()
3182 if (count <= 0)
3183 return EOF;
3184 const unsigned char *p = eptr;
3185 if (p >= bp->s + char_block::SIZE) {
3186 p = bp->next->s;
3188 return *p;
3191 int string_iterator::get_location(int allow_macro,
3192 const char **filep, int *linep)
3194 if (!allow_macro)
3195 return 0;
3196 if (mac.filename == 0)
3197 return 0;
3198 *filep = mac.filename;
3199 *linep = mac.lineno + lineno - 1;
3200 return 1;
3203 void string_iterator::backtrace()
3205 if (mac.filename) {
3206 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3207 if (how_invoked) {
3208 if (!nm.is_null())
3209 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3210 else
3211 errprint(": %1\n", how_invoked);
3213 else
3214 errprint("\n");
3218 class temp_iterator : public input_iterator {
3219 unsigned char *base;
3220 temp_iterator(const char *, int len);
3221 public:
3222 ~temp_iterator();
3223 friend input_iterator *make_temp_iterator(const char *);
3226 #ifdef __GNUG__
3227 inline
3228 #endif
3229 temp_iterator::temp_iterator(const char *s, int len)
3231 base = new unsigned char[len];
3232 memcpy(base, s, len);
3233 ptr = base;
3234 eptr = base + len;
3237 temp_iterator::~temp_iterator()
3239 a_delete base;
3242 class small_temp_iterator : public input_iterator {
3243 private:
3244 small_temp_iterator(const char *, int);
3245 ~small_temp_iterator();
3246 enum { BLOCK = 16 };
3247 static small_temp_iterator *free_list;
3248 void *operator new(size_t);
3249 void operator delete(void *);
3250 enum { SIZE = 12 };
3251 unsigned char buf[SIZE];
3252 friend input_iterator *make_temp_iterator(const char *);
3255 small_temp_iterator *small_temp_iterator::free_list = 0;
3257 void *small_temp_iterator::operator new(size_t n)
3259 assert(n == sizeof(small_temp_iterator));
3260 if (!free_list) {
3261 free_list =
3262 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3263 for (int i = 0; i < BLOCK - 1; i++)
3264 free_list[i].next = free_list + i + 1;
3265 free_list[BLOCK-1].next = 0;
3267 small_temp_iterator *p = free_list;
3268 free_list = (small_temp_iterator *)(free_list->next);
3269 p->next = 0;
3270 return p;
3273 #ifdef __GNUG__
3274 inline
3275 #endif
3276 void small_temp_iterator::operator delete(void *p)
3278 if (p) {
3279 ((small_temp_iterator *)p)->next = free_list;
3280 free_list = (small_temp_iterator *)p;
3284 small_temp_iterator::~small_temp_iterator()
3288 #ifdef __GNUG__
3289 inline
3290 #endif
3291 small_temp_iterator::small_temp_iterator(const char *s, int len)
3293 for (int i = 0; i < len; i++)
3294 buf[i] = s[i];
3295 ptr = buf;
3296 eptr = buf + len;
3299 input_iterator *make_temp_iterator(const char *s)
3301 if (s == 0)
3302 return new small_temp_iterator(s, 0);
3303 else {
3304 int n = strlen(s);
3305 if (n <= small_temp_iterator::SIZE)
3306 return new small_temp_iterator(s, n);
3307 else
3308 return new temp_iterator(s, n);
3312 // this is used when macros with arguments are interpolated
3314 struct arg_list {
3315 macro mac;
3316 arg_list *next;
3317 arg_list(const macro &);
3318 ~arg_list();
3321 arg_list::arg_list(const macro &m) : mac(m), next(0)
3325 arg_list::~arg_list()
3329 class macro_iterator : public string_iterator {
3330 arg_list *args;
3331 int argc;
3332 public:
3333 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
3334 macro_iterator();
3335 ~macro_iterator();
3336 int has_args() { return 1; }
3337 input_iterator *get_arg(int i);
3338 int nargs() { return argc; }
3339 void add_arg(const macro &m);
3340 void shift(int n);
3341 int is_macro() { return 1; }
3344 input_iterator *macro_iterator::get_arg(int i)
3346 if (i == 0)
3347 return make_temp_iterator(nm.contents());
3348 if (i > 0 && i <= argc) {
3349 arg_list *p = args;
3350 for (int j = 1; j < i; j++) {
3351 assert(p != 0);
3352 p = p->next;
3354 return new string_iterator(p->mac);
3356 else
3357 return 0;
3360 void macro_iterator::add_arg(const macro &m)
3362 arg_list **p;
3363 for (p = &args; *p; p = &((*p)->next))
3365 *p = new arg_list(m);
3366 ++argc;
3369 void macro_iterator::shift(int n)
3371 while (n > 0 && argc > 0) {
3372 arg_list *tem = args;
3373 args = args->next;
3374 delete tem;
3375 --argc;
3376 --n;
3380 // This gets used by eg .if '\?xxx\?''.
3382 int operator==(const macro &m1, const macro &m2)
3384 if (m1.len != m2.len)
3385 return 0;
3386 string_iterator iter1(m1);
3387 string_iterator iter2(m2);
3388 int n = m1.len;
3389 while (--n >= 0) {
3390 node *nd1 = 0;
3391 int c1 = iter1.get(&nd1);
3392 assert(c1 != EOF);
3393 node *nd2 = 0;
3394 int c2 = iter2.get(&nd2);
3395 assert(c2 != EOF);
3396 if (c1 != c2) {
3397 if (c1 == 0)
3398 delete nd1;
3399 else if (c2 == 0)
3400 delete nd2;
3401 return 0;
3403 if (c1 == 0) {
3404 assert(nd1 != 0);
3405 assert(nd2 != 0);
3406 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3407 delete nd1;
3408 delete nd2;
3409 if (!are_same)
3410 return 0;
3413 return 1;
3416 static void interpolate_macro(symbol nm)
3418 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3419 if (p == 0) {
3420 int warned = 0;
3421 const char *s = nm.contents();
3422 if (strlen(s) > 2) {
3423 request_or_macro *r;
3424 char buf[3];
3425 buf[0] = s[0];
3426 buf[1] = s[1];
3427 buf[2] = '\0';
3428 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3429 if (r) {
3430 macro *m = r->to_macro();
3431 if (!m || !m->empty())
3432 warned = warning(WARN_SPACE,
3433 "`%1' not defined (probable missing space after `%2')",
3434 nm.contents(), buf);
3437 if (!warned) {
3438 warning(WARN_MAC, "`%1' not defined", nm.contents());
3439 p = new macro;
3440 request_dictionary.define(nm, p);
3443 if (p)
3444 p->invoke(nm);
3445 else {
3446 skip_line();
3447 return;
3451 static void decode_args(macro_iterator *mi)
3453 if (!tok.newline() && !tok.eof()) {
3454 node *n;
3455 int c = get_copy(&n);
3456 for (;;) {
3457 while (c == ' ')
3458 c = get_copy(&n);
3459 if (c == '\n' || c == EOF)
3460 break;
3461 macro arg;
3462 int quote_input_level = 0;
3463 int done_tab_warning = 0;
3464 if (c == '\"') {
3465 quote_input_level = input_stack::get_level();
3466 c = get_copy(&n);
3468 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3469 if (quote_input_level > 0 && c == '\"'
3470 && (compatible_flag
3471 || input_stack::get_level() == quote_input_level)) {
3472 c = get_copy(&n);
3473 if (c == '"') {
3474 arg.append(c);
3475 c = get_copy(&n);
3477 else
3478 break;
3480 else {
3481 if (c == 0)
3482 arg.append(n);
3483 else {
3484 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3485 warning(WARN_TAB, "tab character in unquoted macro argument");
3486 done_tab_warning = 1;
3488 arg.append(c);
3490 c = get_copy(&n);
3493 mi->add_arg(arg);
3498 static void decode_string_args(macro_iterator *mi)
3500 node *n;
3501 int c = get_copy(&n);
3502 for (;;) {
3503 while (c == ' ')
3504 c = get_copy(&n);
3505 if (c == '\n' || c == EOF) {
3506 error("missing `]'");
3507 break;
3509 if (c == ']')
3510 break;
3511 macro arg;
3512 int quote_input_level = 0;
3513 int done_tab_warning = 0;
3514 if (c == '\"') {
3515 quote_input_level = input_stack::get_level();
3516 c = get_copy(&n);
3518 while (c != EOF && c != '\n'
3519 && !(c == ']' && quote_input_level == 0)
3520 && !(c == ' ' && quote_input_level == 0)) {
3521 if (quote_input_level > 0 && c == '\"'
3522 && input_stack::get_level() == quote_input_level) {
3523 c = get_copy(&n);
3524 if (c == '"') {
3525 arg.append(c);
3526 c = get_copy(&n);
3528 else
3529 break;
3531 else {
3532 if (c == 0)
3533 arg.append(n);
3534 else {
3535 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3536 warning(WARN_TAB, "tab character in unquoted string argument");
3537 done_tab_warning = 1;
3539 arg.append(c);
3541 c = get_copy(&n);
3544 mi->add_arg(arg);
3548 void macro::invoke(symbol nm)
3550 macro_iterator *mi = new macro_iterator(nm, *this);
3551 decode_args(mi);
3552 input_stack::push(mi);
3553 tok.next();
3556 macro *macro::to_macro()
3558 return this;
3561 int macro::empty()
3563 return empty_macro == 1;
3566 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
3567 : string_iterator(m, how_invoked, s), args(0), argc(0)
3571 macro_iterator::macro_iterator() : args(0), argc(0)
3575 macro_iterator::~macro_iterator()
3577 while (args != 0) {
3578 arg_list *tem = args;
3579 args = args->next;
3580 delete tem;
3584 int trap_sprung_flag = 0;
3585 int postpone_traps_flag = 0;
3586 symbol postponed_trap;
3588 void spring_trap(symbol nm)
3590 assert(!nm.is_null());
3591 trap_sprung_flag = 1;
3592 if (postpone_traps_flag) {
3593 postponed_trap = nm;
3594 return;
3596 static char buf[2] = { BEGIN_TRAP, 0 };
3597 static char buf2[2] = { END_TRAP, '\0' };
3598 input_stack::push(make_temp_iterator(buf2));
3599 request_or_macro *p = lookup_request(nm);
3600 macro *m = p->to_macro();
3601 if (m)
3602 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
3603 else
3604 error("you can't invoke a request with a trap");
3605 input_stack::push(make_temp_iterator(buf));
3608 void postpone_traps()
3610 postpone_traps_flag = 1;
3613 int unpostpone_traps()
3615 postpone_traps_flag = 0;
3616 if (!postponed_trap.is_null()) {
3617 spring_trap(postponed_trap);
3618 postponed_trap = NULL_SYMBOL;
3619 return 1;
3621 else
3622 return 0;
3625 void read_request()
3627 macro_iterator *mi = new macro_iterator;
3628 int reading_from_terminal = isatty(fileno(stdin));
3629 int had_prompt = 0;
3630 if (!tok.newline() && !tok.eof()) {
3631 int c = get_copy(0);
3632 while (c == ' ')
3633 c = get_copy(0);
3634 while (c != EOF && c != '\n' && c != ' ') {
3635 if (!invalid_input_char(c)) {
3636 if (reading_from_terminal)
3637 fputc(c, stderr);
3638 had_prompt = 1;
3640 c = get_copy(0);
3642 if (c == ' ') {
3643 tok.make_space();
3644 decode_args(mi);
3647 if (reading_from_terminal) {
3648 fputc(had_prompt ? ':' : '\a', stderr);
3649 fflush(stderr);
3651 input_stack::push(mi);
3652 macro mac;
3653 int nl = 0;
3654 int c;
3655 while ((c = getchar()) != EOF) {
3656 if (invalid_input_char(c))
3657 warning(WARN_INPUT, "invalid input character code %1", int(c));
3658 else {
3659 if (c == '\n') {
3660 if (nl)
3661 break;
3662 else
3663 nl = 1;
3665 else
3666 nl = 0;
3667 mac.append(c);
3670 if (reading_from_terminal)
3671 clearerr(stdin);
3672 input_stack::push(new string_iterator(mac));
3673 tok.next();
3676 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3677 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT, CALLING_DISABLE_COMP };
3679 void do_define_string(define_mode mode, calling_mode calling)
3681 symbol nm;
3682 node *n;
3683 int c;
3684 nm = get_name(1);
3685 if (nm.is_null()) {
3686 skip_line();
3687 return;
3689 if (tok.newline())
3690 c = '\n';
3691 else if (tok.tab())
3692 c = '\t';
3693 else if (!tok.space()) {
3694 error("bad string definition");
3695 skip_line();
3696 return;
3698 else
3699 c = get_copy(&n);
3700 while (c == ' ')
3701 c = get_copy(&n);
3702 if (c == '"')
3703 c = get_copy(&n);
3704 macro mac;
3705 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3706 macro *mm = rm ? rm->to_macro() : 0;
3707 if (mode == DEFINE_APPEND && mm)
3708 mac = *mm;
3709 if (calling == CALLING_DISABLE_COMP)
3710 mac.append(COMPATIBLE_SAVE);
3711 while (c != '\n' && c != EOF) {
3712 if (c == 0)
3713 mac.append(n);
3714 else
3715 mac.append((unsigned char)c);
3716 c = get_copy(&n);
3718 if (!mm) {
3719 mm = new macro;
3720 request_dictionary.define(nm, mm);
3722 if (calling == CALLING_DISABLE_COMP)
3723 mac.append(COMPATIBLE_RESTORE);
3724 *mm = mac;
3725 tok.next();
3728 void define_string()
3730 do_define_string(DEFINE_NORMAL, CALLING_NORMAL);
3733 void define_nocomp_string()
3735 do_define_string(DEFINE_NORMAL, CALLING_DISABLE_COMP);
3738 void append_string()
3740 do_define_string(DEFINE_APPEND, CALLING_NORMAL);
3743 void append_nocomp_string()
3745 do_define_string(DEFINE_APPEND, CALLING_DISABLE_COMP);
3748 void do_define_character(int fallback)
3750 node *n;
3751 int c;
3752 tok.skip();
3753 charinfo *ci = tok.get_char(1);
3754 if (ci == 0) {
3755 skip_line();
3756 return;
3758 tok.next();
3759 if (tok.newline())
3760 c = '\n';
3761 else if (tok.tab())
3762 c = '\t';
3763 else if (!tok.space()) {
3764 error("bad character definition");
3765 skip_line();
3766 return;
3768 else
3769 c = get_copy(&n);
3770 while (c == ' ' || c == '\t')
3771 c = get_copy(&n);
3772 if (c == '"')
3773 c = get_copy(&n);
3774 macro *m = new macro;
3775 while (c != '\n' && c != EOF) {
3776 if (c == 0)
3777 m->append(n);
3778 else
3779 m->append((unsigned char)c);
3780 c = get_copy(&n);
3782 m = ci->set_macro(m, fallback);
3783 if (m)
3784 delete m;
3785 tok.next();
3788 void define_character()
3790 do_define_character(0);
3793 void define_fallback_character()
3795 do_define_character(1);
3798 static void remove_character()
3800 tok.skip();
3801 while (!tok.newline() && !tok.eof()) {
3802 if (!tok.space() && !tok.tab()) {
3803 charinfo *ci = tok.get_char(1);
3804 if (!ci)
3805 break;
3806 macro *m = ci->set_macro(0);
3807 if (m)
3808 delete m;
3810 tok.next();
3812 skip_line();
3815 static void interpolate_string(symbol nm)
3817 request_or_macro *p = lookup_request(nm);
3818 macro *m = p->to_macro();
3819 if (!m)
3820 error("you can only invoke a string or macro using \\*");
3821 else {
3822 string_iterator *si = new string_iterator(*m, "string", nm);
3823 input_stack::push(si);
3827 static void interpolate_string_with_args(symbol s)
3829 request_or_macro *p = lookup_request(s);
3830 macro *m = p->to_macro();
3831 if (!m)
3832 error("you can only invoke a string or macro using \\*");
3833 else {
3834 macro_iterator *mi = new macro_iterator(s, *m);
3835 decode_string_args(mi);
3836 input_stack::push(mi);
3840 /* This class is used for the implementation of \$@. It is used for
3841 each of the closing double quotes. It artificially increases the
3842 input level by 2, so that the closing double quote will appear to have
3843 the same input level as the opening quote. */
3845 class end_quote_iterator : public input_iterator {
3846 unsigned char buf[1];
3847 public:
3848 end_quote_iterator();
3849 ~end_quote_iterator() { }
3850 int internal_level() { return 2; }
3853 end_quote_iterator::end_quote_iterator()
3855 buf[0] = '"';
3856 ptr = buf;
3857 eptr = buf + 1;
3860 static void interpolate_arg(symbol nm)
3862 const char *s = nm.contents();
3863 if (!s || *s == '\0')
3864 copy_mode_error("missing argument name");
3865 else if (s[1] == 0 && csdigit(s[0]))
3866 input_stack::push(input_stack::get_arg(s[0] - '0'));
3867 else if (s[0] == '*' && s[1] == '\0') {
3868 for (int i = input_stack::nargs(); i > 0; i--) {
3869 input_stack::push(input_stack::get_arg(i));
3870 if (i != 1)
3871 input_stack::push(make_temp_iterator(" "));
3874 else if (s[0] == '@' && s[1] == '\0') {
3875 for (int i = input_stack::nargs(); i > 0; i--) {
3876 input_stack::push(new end_quote_iterator);
3877 input_stack::push(input_stack::get_arg(i));
3878 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
3881 else {
3882 const char *p;
3883 for (p = s; *p && csdigit(*p); p++)
3885 if (*p)
3886 copy_mode_error("bad argument name `%1'", s);
3887 else
3888 input_stack::push(input_stack::get_arg(atoi(s)));
3892 void handle_first_page_transition()
3894 push_token(tok);
3895 topdiv->begin_page();
3898 // We push back a token by wrapping it up in a token_node, and
3899 // wrapping that up in a string_iterator.
3901 static void push_token(const token &t)
3903 macro m;
3904 m.append(new token_node(t));
3905 input_stack::push(new string_iterator(m));
3908 void push_page_ejector()
3910 static char buf[2] = { PAGE_EJECTOR, '\0' };
3911 input_stack::push(make_temp_iterator(buf));
3914 void handle_initial_request(unsigned char code)
3916 char buf[2];
3917 buf[0] = code;
3918 buf[1] = '\0';
3919 macro mac;
3920 mac.append(new token_node(tok));
3921 input_stack::push(new string_iterator(mac));
3922 input_stack::push(make_temp_iterator(buf));
3923 topdiv->begin_page();
3924 tok.next();
3927 void handle_initial_title()
3929 handle_initial_request(TITLE_REQUEST);
3932 // this should be local to define_macro, but cfront 1.2 doesn't support that
3933 static symbol dot_symbol(".");
3935 void do_define_macro(define_mode mode, calling_mode calling)
3937 symbol nm, term;
3938 if (calling == CALLING_INDIRECT) {
3939 symbol temp1 = get_name(1);
3940 if (temp1.is_null()) {
3941 skip_line();
3942 return;
3944 symbol temp2 = get_name();
3945 input_stack::push(make_temp_iterator("\n"));
3946 if (!temp2.is_null()) {
3947 interpolate_string(temp2);
3948 input_stack::push(make_temp_iterator(" "));
3950 interpolate_string(temp1);
3951 input_stack::push(make_temp_iterator(" "));
3952 tok.next();
3954 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3955 nm = get_name(1);
3956 if (nm.is_null()) {
3957 skip_line();
3958 return;
3961 term = get_name(); // the request that terminates the definition
3962 if (term.is_null())
3963 term = dot_symbol;
3964 while (!tok.newline() && !tok.eof())
3965 tok.next();
3966 const char *start_filename;
3967 int start_lineno;
3968 int have_start_location = input_stack::get_location(0, &start_filename,
3969 &start_lineno);
3970 node *n;
3971 // doing this here makes the line numbers come out right
3972 int c = get_copy(&n, 1);
3973 macro mac;
3974 macro *mm = 0;
3975 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3976 request_or_macro *rm =
3977 (request_or_macro *)request_dictionary.lookup(nm);
3978 if (rm)
3979 mm = rm->to_macro();
3980 if (mm && mode == DEFINE_APPEND)
3981 mac = *mm;
3983 int bol = 1;
3984 if (calling == CALLING_DISABLE_COMP)
3985 mac.append(COMPATIBLE_SAVE);
3986 for (;;) {
3987 while (c == ESCAPE_NEWLINE) {
3988 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
3989 mac.append(c);
3990 c = get_copy(&n, 1);
3992 if (bol && c == '.') {
3993 const char *s = term.contents();
3994 int d = 0;
3995 // see if it matches term
3996 int i = 0;
3997 if (s[0] != 0) {
3998 while ((d = get_copy(&n)) == ' ' || d == '\t')
4000 if ((unsigned char)s[0] == d) {
4001 for (i = 1; s[i] != 0; i++) {
4002 d = get_copy(&n);
4003 if ((unsigned char)s[i] != d)
4004 break;
4008 if (s[i] == 0
4009 && ((i == 2 && compatible_flag)
4010 || (d = get_copy(&n)) == ' '
4011 || d == '\n')) { // we found it
4012 if (d == '\n')
4013 tok.make_newline();
4014 else
4015 tok.make_space();
4016 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4017 if (!mm) {
4018 mm = new macro;
4019 request_dictionary.define(nm, mm);
4021 if (calling == CALLING_DISABLE_COMP)
4022 mac.append(COMPATIBLE_RESTORE);
4023 *mm = mac;
4025 if (term != dot_symbol) {
4026 ignoring = 0;
4027 interpolate_macro(term);
4029 else
4030 skip_line();
4031 return;
4033 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4034 mac.append(c);
4035 for (int j = 0; j < i; j++)
4036 mac.append(s[j]);
4038 c = d;
4040 if (c == EOF) {
4041 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4042 if (have_start_location)
4043 error_with_file_and_line(start_filename, start_lineno,
4044 "end of file while defining macro `%1'",
4045 nm.contents());
4046 else
4047 error("end of file while defining macro `%1'", nm.contents());
4049 else {
4050 if (have_start_location)
4051 error_with_file_and_line(start_filename, start_lineno,
4052 "end of file while ignoring input lines");
4053 else
4054 error("end of file while ignoring input lines");
4056 tok.next();
4057 return;
4059 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4060 if (c == 0)
4061 mac.append(n);
4062 else
4063 mac.append(c);
4065 bol = (c == '\n');
4066 c = get_copy(&n, 1);
4070 void define_macro()
4072 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL);
4075 void define_nocomp_macro()
4077 do_define_macro(DEFINE_NORMAL, CALLING_DISABLE_COMP);
4080 void define_indirect_macro()
4082 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT);
4085 void append_macro()
4087 do_define_macro(DEFINE_APPEND, CALLING_NORMAL);
4090 void append_indirect_macro()
4092 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT);
4095 void append_nocomp_macro()
4097 do_define_macro(DEFINE_APPEND, CALLING_DISABLE_COMP);
4100 void ignore()
4102 ignoring = 1;
4103 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL);
4104 ignoring = 0;
4107 void remove_macro()
4109 for (;;) {
4110 symbol s = get_name();
4111 if (s.is_null())
4112 break;
4113 request_dictionary.remove(s);
4115 skip_line();
4118 void rename_macro()
4120 symbol s1 = get_name(1);
4121 if (!s1.is_null()) {
4122 symbol s2 = get_name(1);
4123 if (!s2.is_null())
4124 request_dictionary.rename(s1, s2);
4126 skip_line();
4129 void alias_macro()
4131 symbol s1 = get_name(1);
4132 if (!s1.is_null()) {
4133 symbol s2 = get_name(1);
4134 if (!s2.is_null()) {
4135 if (!request_dictionary.alias(s1, s2))
4136 warning(WARN_MAC, "`%1' not defined", s2.contents());
4139 skip_line();
4142 void chop_macro()
4144 symbol s = get_name(1);
4145 if (!s.is_null()) {
4146 request_or_macro *p = lookup_request(s);
4147 macro *m = p->to_macro();
4148 if (!m)
4149 error("cannot chop request");
4150 else if (m->empty())
4151 error("cannot chop empty macro");
4152 else {
4153 int have_restore = 0;
4154 // we have to check for additional save/restore pairs which could be
4155 // there due to empty am1 requests.
4156 for (;;) {
4157 if (m->get(m->len - 1) != COMPATIBLE_RESTORE)
4158 break;
4159 have_restore = 1;
4160 m->len -= 1;
4161 if (m->get(m->len - 1) != COMPATIBLE_SAVE)
4162 break;
4163 have_restore = 0;
4164 m->len -= 1;
4165 if (m->len == 0)
4166 break;
4168 if (m->len == 0)
4169 error("cannot chop empty macro");
4170 else {
4171 if (have_restore)
4172 m->set(COMPATIBLE_RESTORE, m->len - 1);
4173 else
4174 m->len -= 1;
4178 skip_line();
4181 void substring_request()
4183 int start; // 0, 1, ..., n-1 or -1, -2, ...
4184 symbol s = get_name(1);
4185 if (!s.is_null() && get_integer(&start)) {
4186 request_or_macro *p = lookup_request(s);
4187 macro *m = p->to_macro();
4188 if (!m)
4189 error("cannot apply `substring' on a request");
4190 else {
4191 int end = -1;
4192 if (!has_arg() || get_integer(&end)) {
4193 int real_length = 0; // 1, 2, ..., n
4194 string_iterator iter1(*m);
4195 for (int l = 0; l < m->len; l++) {
4196 int c = iter1.get(0);
4197 if (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4198 continue;
4199 if (c == EOF)
4200 break;
4201 real_length++;
4203 if (start < 0)
4204 start += real_length;
4205 if (end < 0)
4206 end += real_length;
4207 if (start > end) {
4208 int tem = start;
4209 start = end;
4210 end = tem;
4212 if (start >= real_length || end < 0) {
4213 warning(WARN_RANGE,
4214 "start and end index of substring out of range");
4215 m->len = 0;
4216 if (m->p) {
4217 if (--(m->p->count) <= 0)
4218 delete m->p;
4219 m->p = 0;
4221 skip_line();
4222 return;
4224 if (start < 0) {
4225 warning(WARN_RANGE,
4226 "start index of substring out of range, set to 0");
4227 start = 0;
4229 if (end >= real_length) {
4230 warning(WARN_RANGE,
4231 "end index of substring out of range, set to string length");
4232 end = real_length - 1;
4234 // now extract the substring
4235 string_iterator iter(*m);
4236 int i;
4237 for (i = 0; i < start; i++) {
4238 int c = iter.get(0);
4239 while (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4240 c = iter.get(0);
4241 if (c == EOF)
4242 break;
4244 macro mac;
4245 for (; i <= end; i++) {
4246 node *nd;
4247 int c = iter.get(&nd);
4248 while (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4249 c = iter.get(0);
4250 if (c == EOF)
4251 break;
4252 if (c == 0)
4253 mac.append(nd);
4254 else
4255 mac.append((unsigned char)c);
4257 *m = mac;
4261 skip_line();
4264 void length_request()
4266 symbol ret;
4267 ret = get_name(1);
4268 if (ret.is_null()) {
4269 skip_line();
4270 return;
4272 int c;
4273 node *n;
4274 if (tok.newline())
4275 c = '\n';
4276 else if (tok.tab())
4277 c = '\t';
4278 else if (!tok.space()) {
4279 error("bad string definition");
4280 skip_line();
4281 return;
4283 else
4284 c = get_copy(&n);
4285 while (c == ' ')
4286 c = get_copy(&n);
4287 if (c == '"')
4288 c = get_copy(&n);
4289 int len = 0;
4290 while (c != '\n' && c != EOF) {
4291 ++len;
4292 c = get_copy(&n);
4294 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4295 if (r)
4296 r->set_value(len);
4297 else
4298 set_number_reg(ret, len);
4299 tok.next();
4302 void asciify_macro()
4304 symbol s = get_name(1);
4305 if (!s.is_null()) {
4306 request_or_macro *p = lookup_request(s);
4307 macro *m = p->to_macro();
4308 if (!m)
4309 error("cannot asciify request");
4310 else {
4311 macro am;
4312 string_iterator iter(*m);
4313 for (;;) {
4314 node *nd;
4315 int c = iter.get(&nd);
4316 if (c == EOF)
4317 break;
4318 if (c != 0)
4319 am.append(c);
4320 else
4321 nd->asciify(&am);
4323 *m = am;
4326 skip_line();
4329 void unformat_macro()
4331 symbol s = get_name(1);
4332 if (!s.is_null()) {
4333 request_or_macro *p = lookup_request(s);
4334 macro *m = p->to_macro();
4335 if (!m)
4336 error("cannot unformat request");
4337 else {
4338 macro am;
4339 string_iterator iter(*m);
4340 for (;;) {
4341 node *nd;
4342 int c = iter.get(&nd);
4343 if (c == EOF)
4344 break;
4345 if (c != 0)
4346 am.append(c);
4347 else {
4348 if (nd->set_unformat_flag())
4349 am.append(nd);
4352 *m = am;
4355 skip_line();
4358 static void interpolate_environment_variable(symbol nm)
4360 const char *s = getenv(nm.contents());
4361 if (s && *s)
4362 input_stack::push(make_temp_iterator(s));
4365 void interpolate_number_reg(symbol nm, int inc)
4367 reg *r = lookup_number_reg(nm);
4368 if (inc < 0)
4369 r->decrement();
4370 else if (inc > 0)
4371 r->increment();
4372 input_stack::push(make_temp_iterator(r->get_string()));
4375 static void interpolate_number_format(symbol nm)
4377 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4378 if (r)
4379 input_stack::push(make_temp_iterator(r->get_format()));
4382 static int get_delim_number(units *n, int si, int prev_value)
4384 token start;
4385 start.next();
4386 if (start.delimiter(1)) {
4387 tok.next();
4388 if (get_number(n, si, prev_value)) {
4389 if (start != tok)
4390 warning(WARN_DELIM, "closing delimiter does not match");
4391 return 1;
4394 return 0;
4397 static int get_delim_number(units *n, int si)
4399 token start;
4400 start.next();
4401 if (start.delimiter(1)) {
4402 tok.next();
4403 if (get_number(n, si)) {
4404 if (start != tok)
4405 warning(WARN_DELIM, "closing delimiter does not match");
4406 return 1;
4409 return 0;
4412 static int get_line_arg(units *n, int si, charinfo **cp)
4414 token start;
4415 start.next();
4416 int start_level = input_stack::get_level();
4417 if (!start.delimiter(1))
4418 return 0;
4419 tok.next();
4420 if (get_number(n, si)) {
4421 if (tok.dummy() || tok.transparent_dummy())
4422 tok.next();
4423 if (!(start == tok && input_stack::get_level() == start_level)) {
4424 *cp = tok.get_char(1);
4425 tok.next();
4427 if (!(start == tok && input_stack::get_level() == start_level))
4428 warning(WARN_DELIM, "closing delimiter does not match");
4429 return 1;
4431 return 0;
4434 static int read_size(int *x)
4436 tok.next();
4437 int c = tok.ch();
4438 int inc = 0;
4439 if (c == '-') {
4440 inc = -1;
4441 tok.next();
4442 c = tok.ch();
4444 else if (c == '+') {
4445 inc = 1;
4446 tok.next();
4447 c = tok.ch();
4449 int val;
4450 int bad = 0;
4451 if (c == '(') {
4452 tok.next();
4453 c = tok.ch();
4454 if (!inc) {
4455 // allow an increment either before or after the left parenthesis
4456 if (c == '-') {
4457 inc = -1;
4458 tok.next();
4459 c = tok.ch();
4461 else if (c == '+') {
4462 inc = 1;
4463 tok.next();
4464 c = tok.ch();
4467 if (!csdigit(c))
4468 bad = 1;
4469 else {
4470 val = c - '0';
4471 tok.next();
4472 c = tok.ch();
4473 if (!csdigit(c))
4474 bad = 1;
4475 else {
4476 val = val*10 + (c - '0');
4477 val *= sizescale;
4481 else if (csdigit(c)) {
4482 val = c - '0';
4483 if (!inc && c != '0' && c < '4') {
4484 tok.next();
4485 c = tok.ch();
4486 if (!csdigit(c))
4487 bad = 1;
4488 else
4489 val = val*10 + (c - '0');
4491 val *= sizescale;
4493 else if (!tok.delimiter(1))
4494 return 0;
4495 else {
4496 token start(tok);
4497 tok.next();
4498 if (!(inc
4499 ? get_number(&val, 'z')
4500 : get_number(&val, 'z', curenv->get_requested_point_size())))
4501 return 0;
4502 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
4503 if (start.ch() == '[')
4504 error("missing `]'");
4505 else
4506 error("missing closing delimiter");
4507 return 0;
4510 if (!bad) {
4511 switch (inc) {
4512 case 0:
4513 if (val == 0) {
4514 // special case -- \s[0] and \s0 means to revert to previous size
4515 *x = 0;
4516 return 1;
4518 *x = val;
4519 break;
4520 case 1:
4521 *x = curenv->get_requested_point_size() + val;
4522 break;
4523 case -1:
4524 *x = curenv->get_requested_point_size() - val;
4525 break;
4526 default:
4527 assert(0);
4529 if (*x <= 0) {
4530 warning(WARN_RANGE,
4531 "\\s request results in non-positive point size; set to 1");
4532 *x = 1;
4534 return 1;
4536 else {
4537 error("bad digit in point size");
4538 return 0;
4542 static symbol get_delim_name()
4544 token start;
4545 start.next();
4546 if (start.eof()) {
4547 error("end of input at start of delimited name");
4548 return NULL_SYMBOL;
4550 if (start.newline()) {
4551 error("can't delimit name with a newline");
4552 return NULL_SYMBOL;
4554 int start_level = input_stack::get_level();
4555 char abuf[ABUF_SIZE];
4556 char *buf = abuf;
4557 int buf_size = ABUF_SIZE;
4558 int i = 0;
4559 for (;;) {
4560 if (i + 1 > buf_size) {
4561 if (buf == abuf) {
4562 buf = new char[ABUF_SIZE*2];
4563 memcpy(buf, abuf, buf_size);
4564 buf_size = ABUF_SIZE*2;
4566 else {
4567 char *old_buf = buf;
4568 buf = new char[buf_size*2];
4569 memcpy(buf, old_buf, buf_size);
4570 buf_size *= 2;
4571 a_delete old_buf;
4574 tok.next();
4575 if (tok == start
4576 && (compatible_flag || input_stack::get_level() == start_level))
4577 break;
4578 if ((buf[i] = tok.ch()) == 0) {
4579 error("missing delimiter (got %1)", tok.description());
4580 if (buf != abuf)
4581 a_delete buf;
4582 return NULL_SYMBOL;
4584 i++;
4586 buf[i] = '\0';
4587 if (buf == abuf) {
4588 if (i == 0) {
4589 error("empty delimited name");
4590 return NULL_SYMBOL;
4592 else
4593 return symbol(buf);
4595 else {
4596 symbol s(buf);
4597 a_delete buf;
4598 return s;
4602 // Implement \R
4604 static void do_register()
4606 token start;
4607 start.next();
4608 if (!start.delimiter(1))
4609 return;
4610 tok.next();
4611 symbol nm = get_long_name(1);
4612 if (nm.is_null())
4613 return;
4614 while (tok.space())
4615 tok.next();
4616 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4617 int prev_value;
4618 if (!r || !r->get_value(&prev_value))
4619 prev_value = 0;
4620 int val;
4621 if (!get_number(&val, 'u', prev_value))
4622 return;
4623 if (start != tok)
4624 warning(WARN_DELIM, "closing delimiter does not match");
4625 if (r)
4626 r->set_value(val);
4627 else
4628 set_number_reg(nm, val);
4631 // this implements the \w escape sequence
4633 static void do_width()
4635 token start;
4636 start.next();
4637 int start_level = input_stack::get_level();
4638 environment env(curenv);
4639 environment *oldenv = curenv;
4640 curenv = &env;
4641 for (;;) {
4642 tok.next();
4643 if (tok.eof()) {
4644 warning(WARN_DELIM, "missing closing delimiter");
4645 break;
4647 if (tok.newline()) {
4648 warning(WARN_DELIM, "missing closing delimiter");
4649 input_stack::push(make_temp_iterator("\n"));
4650 break;
4652 if (tok == start
4653 && (compatible_flag || input_stack::get_level() == start_level))
4654 break;
4655 tok.process();
4657 env.wrap_up_tab();
4658 units x = env.get_input_line_position().to_units();
4659 input_stack::push(make_temp_iterator(i_to_a(x)));
4660 env.width_registers();
4661 curenv = oldenv;
4664 charinfo *page_character;
4666 void set_page_character()
4668 page_character = get_optional_char();
4669 skip_line();
4672 static const symbol percent_symbol("%");
4674 void read_title_parts(node **part, hunits *part_width)
4676 tok.skip();
4677 if (tok.newline() || tok.eof())
4678 return;
4679 token start(tok);
4680 int start_level = input_stack::get_level();
4681 tok.next();
4682 for (int i = 0; i < 3; i++) {
4683 while (!tok.newline() && !tok.eof()) {
4684 if (tok == start
4685 && (compatible_flag || input_stack::get_level() == start_level)) {
4686 tok.next();
4687 break;
4689 if (page_character != 0 && tok.get_char() == page_character)
4690 interpolate_number_reg(percent_symbol, 0);
4691 else
4692 tok.process();
4693 tok.next();
4695 curenv->wrap_up_tab();
4696 part_width[i] = curenv->get_input_line_position();
4697 part[i] = curenv->extract_output_line();
4699 while (!tok.newline() && !tok.eof())
4700 tok.next();
4703 class non_interpreted_node : public node {
4704 macro mac;
4705 public:
4706 non_interpreted_node(const macro &);
4707 int interpret(macro *);
4708 node *copy();
4709 int same(node *);
4710 const char *type();
4711 int force_tprint();
4714 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
4718 int non_interpreted_node::same(node *nd)
4720 return mac == ((non_interpreted_node *)nd)->mac;
4723 const char *non_interpreted_node::type()
4725 return "non_interpreted_node";
4728 int non_interpreted_node::force_tprint()
4730 return 0;
4733 node *non_interpreted_node::copy()
4735 return new non_interpreted_node(mac);
4738 int non_interpreted_node::interpret(macro *m)
4740 string_iterator si(mac);
4741 node *n;
4742 for (;;) {
4743 int c = si.get(&n);
4744 if (c == EOF)
4745 break;
4746 if (c == 0)
4747 m->append(n);
4748 else
4749 m->append(c);
4751 return 1;
4754 static node *do_non_interpreted()
4756 node *n;
4757 int c;
4758 macro mac;
4759 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
4760 if (c == 0)
4761 mac.append(n);
4762 else
4763 mac.append(c);
4764 if (c == EOF || c == '\n') {
4765 error("missing \\?");
4766 return 0;
4768 return new non_interpreted_node(mac);
4771 static void encode_char(macro *mac, char c)
4773 if (c == '\0') {
4774 if ((font::use_charnames_in_special) && tok.special()) {
4775 charinfo *ci = tok.get_char(1);
4776 const char *s = ci->get_symbol()->contents();
4777 if (s[0] != (char)0) {
4778 mac->append('\\');
4779 mac->append('(');
4780 int i = 0;
4781 while (s[i] != (char)0) {
4782 mac->append(s[i]);
4783 i++;
4785 mac->append('\\');
4786 mac->append(')');
4789 else if (tok.stretchable_space()
4790 || tok.unstretchable_space())
4791 mac->append(' ');
4792 else if (!(tok.hyphen_indicator()
4793 || tok.dummy()
4794 || tok.transparent_dummy()
4795 || tok.zero_width_break()))
4796 error("%1 is invalid within \\X", tok.description());
4798 else {
4799 if ((font::use_charnames_in_special) && (c == '\\')) {
4801 * add escape escape sequence
4803 mac->append(c);
4805 mac->append(c);
4809 node *do_special()
4811 token start;
4812 start.next();
4813 int start_level = input_stack::get_level();
4814 macro mac;
4815 for (tok.next();
4816 tok != start || input_stack::get_level() != start_level;
4817 tok.next()) {
4818 if (tok.eof()) {
4819 warning(WARN_DELIM, "missing closing delimiter");
4820 return 0;
4822 if (tok.newline()) {
4823 input_stack::push(make_temp_iterator("\n"));
4824 warning(WARN_DELIM, "missing closing delimiter");
4825 break;
4827 unsigned char c;
4828 if (tok.space())
4829 c = ' ';
4830 else if (tok.tab())
4831 c = '\t';
4832 else if (tok.leader())
4833 c = '\001';
4834 else if (tok.backspace())
4835 c = '\b';
4836 else
4837 c = tok.ch();
4838 encode_char(&mac, c);
4840 return new special_node(mac);
4843 void output_request()
4845 if (!tok.newline() && !tok.eof()) {
4846 int c;
4847 for (;;) {
4848 c = get_copy(0);
4849 if (c == '"') {
4850 c = get_copy(0);
4851 break;
4853 if (c != ' ' && c != '\t')
4854 break;
4856 for (; c != '\n' && c != EOF; c = get_copy(0))
4857 topdiv->transparent_output(c);
4858 topdiv->transparent_output('\n');
4860 tok.next();
4863 extern int image_no; // from node.cc
4865 static node *do_suppress(symbol nm)
4867 if (nm.is_null() || nm.is_empty()) {
4868 error("expecting an argument to escape \\O");
4869 return 0;
4871 const char *s = nm.contents();
4872 switch (*s) {
4873 case '0':
4874 if (begin_level == 0)
4875 // suppress generation of glyphs
4876 return new suppress_node(0, 0);
4877 break;
4878 case '1':
4879 if (begin_level == 0)
4880 // enable generation of glyphs
4881 return new suppress_node(1, 0);
4882 break;
4883 case '2':
4884 if (begin_level == 0)
4885 return new suppress_node(1, 1);
4886 break;
4887 case '3':
4888 begin_level++;
4889 break;
4890 case '4':
4891 begin_level--;
4892 break;
4893 case '5':
4895 s++; // move over '5'
4896 char position = *s;
4897 if (*s == (char)0) {
4898 error("missing position and filename in \\O");
4899 return 0;
4901 if (!(position == 'l'
4902 || position == 'r'
4903 || position == 'c'
4904 || position == 'i')) {
4905 error("l, r, c, or i position expected (got %1 in \\O)", position);
4906 return 0;
4908 s++; // onto image name
4909 if (s == (char *)0) {
4910 error("missing image name for \\O");
4911 return 0;
4913 image_no++;
4914 if (begin_level == 0)
4915 return new suppress_node(symbol(s), position, image_no);
4917 break;
4918 default:
4919 error("`%1' is an invalid argument to \\O", *s);
4921 return 0;
4924 void special_node::tprint(troff_output_file *out)
4926 tprint_start(out);
4927 string_iterator iter(mac);
4928 for (;;) {
4929 int c = iter.get(0);
4930 if (c == EOF)
4931 break;
4932 for (const char *s = ::asciify(c); *s; s++)
4933 tprint_char(out, *s);
4935 tprint_end(out);
4938 int get_file_line(const char **filename, int *lineno)
4940 return input_stack::get_location(0, filename, lineno);
4943 void line_file()
4945 int n;
4946 if (get_integer(&n)) {
4947 const char *filename = 0;
4948 if (has_arg()) {
4949 symbol s = get_long_name();
4950 filename = s.contents();
4952 (void)input_stack::set_location(filename, n-1);
4954 skip_line();
4957 static int nroff_mode = 0;
4959 static void nroff_request()
4961 nroff_mode = 1;
4962 skip_line();
4965 static void troff_request()
4967 nroff_mode = 0;
4968 skip_line();
4971 static void skip_alternative()
4973 int level = 0;
4974 // ensure that ``.if 0\{'' works as expected
4975 if (tok.left_brace())
4976 level++;
4977 int c;
4978 for (;;) {
4979 c = input_stack::get(0);
4980 if (c == EOF)
4981 break;
4982 if (c == ESCAPE_LEFT_BRACE)
4983 ++level;
4984 else if (c == ESCAPE_RIGHT_BRACE)
4985 --level;
4986 else if (c == escape_char && escape_char > 0)
4987 switch(input_stack::get(0)) {
4988 case '{':
4989 ++level;
4990 break;
4991 case '}':
4992 --level;
4993 break;
4994 case '"':
4995 while ((c = input_stack::get(0)) != '\n' && c != EOF)
4999 Note that the level can properly be < 0, eg
5001 .if 1 \{\
5002 .if 0 \{\
5003 .\}\}
5005 So don't give an error message in this case.
5007 if (level <= 0 && c == '\n')
5008 break;
5010 tok.next();
5013 static void begin_alternative()
5015 while (tok.space() || tok.left_brace())
5016 tok.next();
5019 void nop_request()
5021 while (tok.space())
5022 tok.next();
5025 static int_stack if_else_stack;
5027 int do_if_request()
5029 int invert = 0;
5030 while (tok.space())
5031 tok.next();
5032 while (tok.ch() == '!') {
5033 tok.next();
5034 invert = !invert;
5036 int result;
5037 unsigned char c = tok.ch();
5038 if (c == 't') {
5039 tok.next();
5040 result = !nroff_mode;
5042 else if (c == 'n') {
5043 tok.next();
5044 result = nroff_mode;
5046 else if (c == 'v') {
5047 tok.next();
5048 result = 0;
5050 else if (c == 'o') {
5051 result = (topdiv->get_page_number() & 1);
5052 tok.next();
5054 else if (c == 'e') {
5055 result = !(topdiv->get_page_number() & 1);
5056 tok.next();
5058 else if (c == 'd' || c == 'r') {
5059 tok.next();
5060 symbol nm = get_name(1);
5061 if (nm.is_null()) {
5062 skip_alternative();
5063 return 0;
5065 result = (c == 'd'
5066 ? request_dictionary.lookup(nm) != 0
5067 : number_reg_dictionary.lookup(nm) != 0);
5069 else if (c == 'm') {
5070 tok.next();
5071 symbol nm = get_long_name(1);
5072 if (nm.is_null()) {
5073 skip_alternative();
5074 return 0;
5076 result = (nm == default_symbol
5077 || color_dictionary.lookup(nm) != 0);
5079 else if (c == 'c') {
5080 tok.next();
5081 tok.skip();
5082 charinfo *ci = tok.get_char(1);
5083 if (ci == 0) {
5084 skip_alternative();
5085 return 0;
5087 result = character_exists(ci, curenv);
5088 tok.next();
5090 else if (tok.space())
5091 result = 0;
5092 else if (tok.delimiter()) {
5093 token delim = tok;
5094 int delim_level = input_stack::get_level();
5095 environment env1(curenv);
5096 environment env2(curenv);
5097 environment *oldenv = curenv;
5098 curenv = &env1;
5099 for (int i = 0; i < 2; i++) {
5100 for (;;) {
5101 tok.next();
5102 if (tok.newline() || tok.eof()) {
5103 warning(WARN_DELIM, "missing closing delimiter");
5104 tok.next();
5105 curenv = oldenv;
5106 return 0;
5108 if (tok == delim
5109 && (compatible_flag || input_stack::get_level() == delim_level))
5110 break;
5111 tok.process();
5113 curenv = &env2;
5115 node *n1 = env1.extract_output_line();
5116 node *n2 = env2.extract_output_line();
5117 result = same_node_list(n1, n2);
5118 delete_node_list(n1);
5119 delete_node_list(n2);
5120 curenv = oldenv;
5121 tok.next();
5123 else {
5124 units n;
5125 if (!get_number(&n, 'u')) {
5126 skip_alternative();
5127 return 0;
5129 else
5130 result = n > 0;
5132 if (invert)
5133 result = !result;
5134 if (result)
5135 begin_alternative();
5136 else
5137 skip_alternative();
5138 return result;
5141 void if_else_request()
5143 if_else_stack.push(do_if_request());
5146 void if_request()
5148 do_if_request();
5151 void else_request()
5153 if (if_else_stack.is_empty()) {
5154 warning(WARN_EL, "unbalanced .el request");
5155 skip_alternative();
5157 else {
5158 if (if_else_stack.pop())
5159 skip_alternative();
5160 else
5161 begin_alternative();
5165 static int while_depth = 0;
5166 static int while_break_flag = 0;
5168 void while_request()
5170 macro mac;
5171 int escaped = 0;
5172 int level = 0;
5173 mac.append(new token_node(tok));
5174 for (;;) {
5175 node *n;
5176 int c = input_stack::get(&n);
5177 if (c == EOF)
5178 break;
5179 if (c == 0) {
5180 escaped = 0;
5181 mac.append(n);
5183 else if (escaped) {
5184 if (c == '{')
5185 level += 1;
5186 else if (c == '}')
5187 level -= 1;
5188 escaped = 0;
5189 mac.append(c);
5191 else {
5192 if (c == ESCAPE_LEFT_BRACE)
5193 level += 1;
5194 else if (c == ESCAPE_RIGHT_BRACE)
5195 level -= 1;
5196 else if (c == escape_char)
5197 escaped = 1;
5198 mac.append(c);
5199 if (c == '\n' && level <= 0)
5200 break;
5203 if (level != 0)
5204 error("unbalanced \\{ \\}");
5205 else {
5206 while_depth++;
5207 input_stack::add_boundary();
5208 for (;;) {
5209 input_stack::push(new string_iterator(mac, "while loop"));
5210 tok.next();
5211 if (!do_if_request()) {
5212 while (input_stack::get(0) != EOF)
5214 break;
5216 process_input_stack();
5217 if (while_break_flag || input_stack::is_return_boundary()) {
5218 while_break_flag = 0;
5219 break;
5222 input_stack::remove_boundary();
5223 while_depth--;
5225 tok.next();
5228 void while_break_request()
5230 if (!while_depth) {
5231 error("no while loop");
5232 skip_line();
5234 else {
5235 while_break_flag = 1;
5236 while (input_stack::get(0) != EOF)
5238 tok.next();
5242 void while_continue_request()
5244 if (!while_depth) {
5245 error("no while loop");
5246 skip_line();
5248 else {
5249 while (input_stack::get(0) != EOF)
5251 tok.next();
5255 // .so
5257 void source()
5259 symbol nm = get_long_name(1);
5260 if (nm.is_null())
5261 skip_line();
5262 else {
5263 while (!tok.newline() && !tok.eof())
5264 tok.next();
5265 errno = 0;
5266 FILE *fp = fopen(nm.contents(), "r");
5267 if (fp)
5268 input_stack::push(new file_iterator(fp, nm.contents()));
5269 else
5270 error("can't open `%1': %2", nm.contents(), strerror(errno));
5271 tok.next();
5275 // like .so but use popen()
5277 void pipe_source()
5279 if (safer_flag) {
5280 error(".pso request not allowed in safer mode");
5281 skip_line();
5283 else {
5284 #ifdef POPEN_MISSING
5285 error("pipes not available on this system");
5286 skip_line();
5287 #else /* not POPEN_MISSING */
5288 if (tok.newline() || tok.eof())
5289 error("missing command");
5290 else {
5291 int c;
5292 while ((c = get_copy(0)) == ' ' || c == '\t')
5294 int buf_size = 24;
5295 char *buf = new char[buf_size];
5296 int buf_used = 0;
5297 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5298 const char *s = asciify(c);
5299 int slen = strlen(s);
5300 if (buf_used + slen + 1> buf_size) {
5301 char *old_buf = buf;
5302 int old_buf_size = buf_size;
5303 buf_size *= 2;
5304 buf = new char[buf_size];
5305 memcpy(buf, old_buf, old_buf_size);
5306 a_delete old_buf;
5308 strcpy(buf + buf_used, s);
5309 buf_used += slen;
5311 buf[buf_used] = '\0';
5312 errno = 0;
5313 FILE *fp = popen(buf, POPEN_RT);
5314 if (fp)
5315 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5316 else
5317 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5318 a_delete buf;
5320 tok.next();
5321 #endif /* not POPEN_MISSING */
5325 // .psbb
5327 static int llx_reg_contents = 0;
5328 static int lly_reg_contents = 0;
5329 static int urx_reg_contents = 0;
5330 static int ury_reg_contents = 0;
5332 struct bounding_box {
5333 int llx, lly, urx, ury;
5336 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5337 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5339 int parse_bounding_box(char *p, bounding_box *bb)
5341 if (sscanf(p, "%d %d %d %d",
5342 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5343 return 1;
5344 else {
5345 /* The Document Structuring Conventions say that the numbers
5346 should be integers. Unfortunately some broken applications
5347 get this wrong. */
5348 double x1, x2, x3, x4;
5349 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5350 bb->llx = (int)x1;
5351 bb->lly = (int)x2;
5352 bb->urx = (int)x3;
5353 bb->ury = (int)x4;
5354 return 1;
5356 else {
5357 for (; *p == ' ' || *p == '\t'; p++)
5359 if (strncmp(p, "(atend)", 7) == 0) {
5360 return 2;
5364 bb->llx = bb->lly = bb->urx = bb->ury = 0;
5365 return 0;
5368 // This version is taken from psrm.cc
5370 #define PS_LINE_MAX 255
5371 cset white_space("\n\r \t");
5373 int ps_get_line(char *buf, FILE *fp, const char* filename)
5375 int c = getc(fp);
5376 if (c == EOF) {
5377 buf[0] = '\0';
5378 return 0;
5380 int i = 0;
5381 int err = 0;
5382 while (c != '\r' && c != '\n' && c != EOF) {
5383 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
5384 error("invalid input character code %1 in `%2'", int(c), filename);
5385 else if (i < PS_LINE_MAX)
5386 buf[i++] = c;
5387 else if (!err) {
5388 err = 1;
5389 error("PostScript file `%1' is non-conforming "
5390 "because length of line exceeds 255", filename);
5392 c = getc(fp);
5394 buf[i++] = '\n';
5395 buf[i] = '\0';
5396 if (c == '\r') {
5397 c = getc(fp);
5398 if (c != EOF && c != '\n')
5399 ungetc(c, fp);
5401 return 1;
5404 inline void assign_registers(int llx, int lly, int urx, int ury)
5406 llx_reg_contents = llx;
5407 lly_reg_contents = lly;
5408 urx_reg_contents = urx;
5409 ury_reg_contents = ury;
5412 void do_ps_file(FILE *fp, const char* filename)
5414 bounding_box bb;
5415 int bb_at_end = 0;
5416 char buf[PS_LINE_MAX];
5417 llx_reg_contents = lly_reg_contents =
5418 urx_reg_contents = ury_reg_contents = 0;
5419 if (!ps_get_line(buf, fp, filename)) {
5420 error("`%1' is empty", filename);
5421 return;
5423 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
5424 error("`%1' is not conforming to the Document Structuring Conventions",
5425 filename);
5426 return;
5428 while (ps_get_line(buf, fp, filename) != 0) {
5429 if (buf[0] != '%' || buf[1] != '%'
5430 || strncmp(buf + 2, "EndComments", 11) == 0)
5431 break;
5432 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5433 int res = parse_bounding_box(buf + 14, &bb);
5434 if (res == 1) {
5435 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5436 return;
5438 else if (res == 2) {
5439 bb_at_end = 1;
5440 break;
5442 else {
5443 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5444 filename);
5445 return;
5449 if (bb_at_end) {
5450 long offset;
5451 int last_try = 0;
5452 /* in the trailer, the last BoundingBox comment is significant */
5453 for (offset = 512; !last_try; offset *= 2) {
5454 int had_trailer = 0;
5455 int got_bb = 0;
5456 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
5457 last_try = 1;
5458 if (fseek(fp, 0L, 0) == -1)
5459 break;
5461 while (ps_get_line(buf, fp, filename) != 0) {
5462 if (buf[0] == '%' && buf[1] == '%') {
5463 if (!had_trailer) {
5464 if (strncmp(buf + 2, "Trailer", 7) == 0)
5465 had_trailer = 1;
5467 else {
5468 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5469 int res = parse_bounding_box(buf + 14, &bb);
5470 if (res == 1)
5471 got_bb = 1;
5472 else if (res == 2) {
5473 error("`(atend)' not allowed in trailer of `%1'", filename);
5474 return;
5476 else {
5477 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5478 filename);
5479 return;
5485 if (got_bb) {
5486 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5487 return;
5491 error("%%%%BoundingBox comment not found in `%1'", filename);
5494 void ps_bbox_request()
5496 symbol nm = get_long_name(1);
5497 if (nm.is_null())
5498 skip_line();
5499 else {
5500 while (!tok.newline() && !tok.eof())
5501 tok.next();
5502 errno = 0;
5503 // PS files might contain non-printable characters, such as ^Z
5504 // and CRs not followed by an LF, so open them in binary mode.
5505 FILE *fp = fopen(nm.contents(), FOPEN_RB);
5506 if (fp) {
5507 do_ps_file(fp, nm.contents());
5508 fclose(fp);
5510 else
5511 error("can't open `%1': %2", nm.contents(), strerror(errno));
5512 tok.next();
5516 const char *asciify(int c)
5518 static char buf[3];
5519 buf[0] = escape_char == '\0' ? '\\' : escape_char;
5520 buf[1] = buf[2] = '\0';
5521 switch (c) {
5522 case ESCAPE_QUESTION:
5523 buf[1] = '?';
5524 break;
5525 case ESCAPE_AMPERSAND:
5526 buf[1] = '&';
5527 break;
5528 case ESCAPE_RIGHT_PARENTHESIS:
5529 buf[1] = ')';
5530 break;
5531 case ESCAPE_UNDERSCORE:
5532 buf[1] = '_';
5533 break;
5534 case ESCAPE_BAR:
5535 buf[1] = '|';
5536 break;
5537 case ESCAPE_CIRCUMFLEX:
5538 buf[1] = '^';
5539 break;
5540 case ESCAPE_LEFT_BRACE:
5541 buf[1] = '{';
5542 break;
5543 case ESCAPE_RIGHT_BRACE:
5544 buf[1] = '}';
5545 break;
5546 case ESCAPE_LEFT_QUOTE:
5547 buf[1] = '`';
5548 break;
5549 case ESCAPE_RIGHT_QUOTE:
5550 buf[1] = '\'';
5551 break;
5552 case ESCAPE_HYPHEN:
5553 buf[1] = '-';
5554 break;
5555 case ESCAPE_BANG:
5556 buf[1] = '!';
5557 break;
5558 case ESCAPE_c:
5559 buf[1] = 'c';
5560 break;
5561 case ESCAPE_e:
5562 buf[1] = 'e';
5563 break;
5564 case ESCAPE_E:
5565 buf[1] = 'E';
5566 break;
5567 case ESCAPE_PERCENT:
5568 buf[1] = '%';
5569 break;
5570 case ESCAPE_SPACE:
5571 buf[1] = ' ';
5572 break;
5573 case ESCAPE_TILDE:
5574 buf[1] = '~';
5575 break;
5576 case ESCAPE_COLON:
5577 buf[1] = ':';
5578 break;
5579 case COMPATIBLE_SAVE:
5580 case COMPATIBLE_RESTORE:
5581 buf[0] = '\0';
5582 break;
5583 default:
5584 if (invalid_input_char(c))
5585 buf[0] = '\0';
5586 else
5587 buf[0] = c;
5588 break;
5590 return buf;
5593 const char *input_char_description(int c)
5595 switch (c) {
5596 case '\n':
5597 return "a newline character";
5598 case '\b':
5599 return "a backspace character";
5600 case '\001':
5601 return "a leader character";
5602 case '\t':
5603 return "a tab character";
5604 case ' ':
5605 return "a space character";
5606 case '\0':
5607 return "a node";
5609 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
5610 if (invalid_input_char(c)) {
5611 const char *s = asciify(c);
5612 if (*s) {
5613 buf[0] = '`';
5614 strcpy(buf + 1, s);
5615 strcat(buf, "'");
5616 return buf;
5618 sprintf(buf, "magic character code %d", c);
5619 return buf;
5621 if (csprint(c)) {
5622 buf[0] = '`';
5623 buf[1] = c;
5624 buf[2] = '\'';
5625 return buf;
5627 sprintf(buf, "character code %d", c);
5628 return buf;
5631 // .tm, .tm1, and .tmc
5633 void do_terminal(int newline, int string_like)
5635 if (!tok.newline() && !tok.eof()) {
5636 int c;
5637 for (;;) {
5638 c = get_copy(0);
5639 if (string_like && c == '"') {
5640 c = get_copy(0);
5641 break;
5643 if (c != ' ' && c != '\t')
5644 break;
5646 for (; c != '\n' && c != EOF; c = get_copy(0))
5647 fputs(asciify(c), stderr);
5649 if (newline)
5650 fputc('\n', stderr);
5651 fflush(stderr);
5652 tok.next();
5655 void terminal()
5657 do_terminal(1, 0);
5660 void terminal1()
5662 do_terminal(1, 1);
5665 void terminal_continue()
5667 do_terminal(0, 1);
5670 dictionary stream_dictionary(20);
5672 void do_open(int append)
5674 symbol stream = get_name(1);
5675 if (!stream.is_null()) {
5676 symbol filename = get_long_name(1);
5677 if (!filename.is_null()) {
5678 errno = 0;
5679 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
5680 if (!fp) {
5681 error("can't open `%1' for %2: %3",
5682 filename.contents(),
5683 append ? "appending" : "writing",
5684 strerror(errno));
5685 fp = (FILE *)stream_dictionary.remove(stream);
5687 else
5688 fp = (FILE *)stream_dictionary.lookup(stream, fp);
5689 if (fp)
5690 fclose(fp);
5693 skip_line();
5696 void open_request()
5698 if (safer_flag) {
5699 error(".open request not allowed in safer mode");
5700 skip_line();
5702 else
5703 do_open(0);
5706 void opena_request()
5708 if (safer_flag) {
5709 error(".opena request not allowed in safer mode");
5710 skip_line();
5712 else
5713 do_open(1);
5716 void close_request()
5718 symbol stream = get_name(1);
5719 if (!stream.is_null()) {
5720 FILE *fp = (FILE *)stream_dictionary.remove(stream);
5721 if (!fp)
5722 error("no stream named `%1'", stream.contents());
5723 else
5724 fclose(fp);
5726 skip_line();
5729 // .write and .writec
5731 void do_write_request(int newline)
5733 symbol stream = get_name(1);
5734 if (stream.is_null()) {
5735 skip_line();
5736 return;
5738 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5739 if (!fp) {
5740 error("no stream named `%1'", stream.contents());
5741 skip_line();
5742 return;
5744 int c;
5745 while ((c = get_copy(0)) == ' ')
5747 if (c == '"')
5748 c = get_copy(0);
5749 for (; c != '\n' && c != EOF; c = get_copy(0))
5750 fputs(asciify(c), fp);
5751 if (newline)
5752 fputc('\n', fp);
5753 fflush(fp);
5754 tok.next();
5757 void write_request()
5759 do_write_request(1);
5762 void write_request_continue()
5764 do_write_request(0);
5767 void write_macro_request()
5769 symbol stream = get_name(1);
5770 if (stream.is_null()) {
5771 skip_line();
5772 return;
5774 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5775 if (!fp) {
5776 error("no stream named `%1'", stream.contents());
5777 skip_line();
5778 return;
5780 symbol s = get_name(1);
5781 if (s.is_null()) {
5782 skip_line();
5783 return;
5785 request_or_macro *p = lookup_request(s);
5786 macro *m = p->to_macro();
5787 if (!m)
5788 error("cannot write request");
5789 else {
5790 string_iterator iter(*m);
5791 for (;;) {
5792 int c = iter.get(0);
5793 if (c == EOF)
5794 break;
5795 fputs(asciify(c), fp);
5797 fflush(fp);
5799 skip_line();
5802 void warnscale_request()
5804 if (has_arg()) {
5805 char c = tok.ch();
5806 if (c == 'u')
5807 warn_scale = 1.0;
5808 else if (c == 'i')
5809 warn_scale = (double)units_per_inch;
5810 else if (c == 'c')
5811 warn_scale = (double)units_per_inch / 2.54;
5812 else if (c == 'p')
5813 warn_scale = (double)units_per_inch / 72.0;
5814 else if (c == 'P')
5815 warn_scale = (double)units_per_inch / 6.0;
5816 else {
5817 warning(WARN_SCALE,
5818 "invalid scaling indicator `%1', using `i' instead", c);
5819 c = 'i';
5821 warn_scaling_indicator = c;
5823 skip_line();
5826 void spreadwarn_request()
5828 hunits n;
5829 if (has_arg() && get_hunits(&n, 'm')) {
5830 if (n < 0)
5831 n = 0;
5832 hunits em = curenv->get_size();
5833 spread_limit = (double)n.to_units()
5834 / (em.is_zero() ? hresolution : em.to_units());
5836 else
5837 spread_limit = -spread_limit - 1; // no arg toggles on/off without
5838 // changing value; we mirror at
5839 // -0.5 to make zero a valid value
5840 skip_line();
5843 static void init_charset_table()
5845 char buf[16];
5846 strcpy(buf, "char");
5847 for (int i = 0; i < 256; i++) {
5848 strcpy(buf + 4, i_to_a(i));
5849 charset_table[i] = get_charinfo(symbol(buf));
5850 charset_table[i]->set_ascii_code(i);
5851 if (csalpha(i))
5852 charset_table[i]->set_hyphenation_code(cmlower(i));
5854 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
5855 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
5856 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
5857 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
5858 charset_table['"']->set_flags(charinfo::TRANSPARENT);
5859 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
5860 charset_table[')']->set_flags(charinfo::TRANSPARENT);
5861 charset_table[']']->set_flags(charinfo::TRANSPARENT);
5862 charset_table['*']->set_flags(charinfo::TRANSPARENT);
5863 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
5864 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
5865 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
5866 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5867 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5868 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5869 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5870 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
5871 page_character = charset_table['%'];
5874 static void init_hpf_code_table()
5876 for (int i = 0; i < 256; i++)
5877 hpf_code_table[i] = i;
5880 static void do_translate(int translate_transparent, int translate_input)
5882 tok.skip();
5883 while (!tok.newline() && !tok.eof()) {
5884 if (tok.space()) {
5885 // This is a really bizarre troff feature.
5886 tok.next();
5887 translate_space_to_dummy = tok.dummy();
5888 if (tok.newline() || tok.eof())
5889 break;
5890 tok.next();
5891 continue;
5893 charinfo *ci1 = tok.get_char(1);
5894 if (ci1 == 0)
5895 break;
5896 tok.next();
5897 if (tok.newline() || tok.eof()) {
5898 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
5899 translate_transparent);
5900 break;
5902 if (tok.space())
5903 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
5904 translate_transparent);
5905 else if (tok.stretchable_space())
5906 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
5907 translate_transparent);
5908 else if (tok.dummy())
5909 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
5910 translate_transparent);
5911 else if (tok.hyphen_indicator())
5912 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
5913 translate_transparent);
5914 else {
5915 charinfo *ci2 = tok.get_char(1);
5916 if (ci2 == 0)
5917 break;
5918 if (ci1 == ci2)
5919 ci1->set_translation(0, translate_transparent, translate_input);
5920 else
5921 ci1->set_translation(ci2, translate_transparent, translate_input);
5923 tok.next();
5925 skip_line();
5928 void translate()
5930 do_translate(1, 0);
5933 void translate_no_transparent()
5935 do_translate(0, 0);
5938 void translate_input()
5940 do_translate(1, 1);
5943 void char_flags()
5945 int flags;
5946 if (get_integer(&flags))
5947 while (has_arg()) {
5948 charinfo *ci = tok.get_char(1);
5949 if (ci) {
5950 charinfo *tem = ci->get_translation();
5951 if (tem)
5952 ci = tem;
5953 ci->set_flags(flags);
5955 tok.next();
5957 skip_line();
5960 void hyphenation_code()
5962 tok.skip();
5963 while (!tok.newline() && !tok.eof()) {
5964 charinfo *ci = tok.get_char(1);
5965 if (ci == 0)
5966 break;
5967 tok.next();
5968 tok.skip();
5969 unsigned char c = tok.ch();
5970 if (c == 0) {
5971 error("hyphenation code must be ordinary character");
5972 break;
5974 if (csdigit(c)) {
5975 error("hyphenation code cannot be digit");
5976 break;
5978 ci->set_hyphenation_code(c);
5979 if (ci->get_translation()
5980 && ci->get_translation()->get_translation_input())
5981 ci->get_translation()->set_hyphenation_code(c);
5982 tok.next();
5983 tok.skip();
5985 skip_line();
5988 void hyphenation_patterns_file_code()
5990 tok.skip();
5991 while (!tok.newline() && !tok.eof()) {
5992 int n1, n2;
5993 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
5994 if (!has_arg()) {
5995 error("missing output hyphenation code");
5996 break;
5998 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
5999 hpf_code_table[n1] = n2;
6000 tok.skip();
6002 else {
6003 error("output hyphenation code must be integer in the range 0..255");
6004 break;
6007 else {
6008 error("input hyphenation code must be integer in the range 0..255");
6009 break;
6012 skip_line();
6015 charinfo *token::get_char(int required)
6017 if (type == TOKEN_CHAR)
6018 return charset_table[c];
6019 if (type == TOKEN_SPECIAL)
6020 return get_charinfo(nm);
6021 if (type == TOKEN_NUMBERED_CHAR)
6022 return get_charinfo_by_number(val);
6023 if (type == TOKEN_ESCAPE) {
6024 if (escape_char != 0)
6025 return charset_table[escape_char];
6026 else {
6027 error("`\\e' used while no current escape character");
6028 return 0;
6031 if (required) {
6032 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6033 warning(WARN_MISSING, "missing normal or special character");
6034 else
6035 error("normal or special character expected (got %1)", description());
6037 return 0;
6040 charinfo *get_optional_char()
6042 while (tok.space())
6043 tok.next();
6044 charinfo *ci = tok.get_char();
6045 if (!ci)
6046 check_missing_character();
6047 else
6048 tok.next();
6049 return ci;
6052 void check_missing_character()
6054 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6055 error("normal or special character expected (got %1): "
6056 "treated as missing",
6057 tok.description());
6060 // this is for \Z
6062 int token::add_to_node_list(node **pp)
6064 hunits w;
6065 int s;
6066 node *n = 0;
6067 switch (type) {
6068 case TOKEN_CHAR:
6069 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6070 break;
6071 case TOKEN_DUMMY:
6072 n = new dummy_node;
6073 break;
6074 case TOKEN_ESCAPE:
6075 if (escape_char != 0)
6076 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6077 break;
6078 case TOKEN_HYPHEN_INDICATOR:
6079 *pp = (*pp)->add_discretionary_hyphen();
6080 break;
6081 case TOKEN_ITALIC_CORRECTION:
6082 *pp = (*pp)->add_italic_correction(&w);
6083 break;
6084 case TOKEN_LEFT_BRACE:
6085 break;
6086 case TOKEN_MARK_INPUT:
6087 set_number_reg(nm, curenv->get_input_line_position().to_units());
6088 break;
6089 case TOKEN_NODE:
6090 n = nd;
6091 nd = 0;
6092 break;
6093 case TOKEN_NUMBERED_CHAR:
6094 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6095 break;
6096 case TOKEN_RIGHT_BRACE:
6097 break;
6098 case TOKEN_SPACE:
6099 n = new hmotion_node(curenv->get_space_width());
6100 break;
6101 case TOKEN_SPECIAL:
6102 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6103 break;
6104 case TOKEN_STRETCHABLE_SPACE:
6105 n = new unbreakable_space_node(curenv->get_space_width());
6106 break;
6107 case TOKEN_UNSTRETCHABLE_SPACE:
6108 n = new space_char_hmotion_node(curenv->get_space_width());
6109 break;
6110 case TOKEN_TRANSPARENT_DUMMY:
6111 n = new transparent_dummy_node;
6112 break;
6113 case TOKEN_ZERO_WIDTH_BREAK:
6114 n = new space_node(H0);
6115 n->freeze_space();
6116 n->is_escape_colon();
6117 break;
6118 default:
6119 return 0;
6121 if (n) {
6122 n->next = *pp;
6123 *pp = n;
6125 return 1;
6128 void token::process()
6130 if (possibly_handle_first_page_transition())
6131 return;
6132 switch (type) {
6133 case TOKEN_BACKSPACE:
6134 curenv->add_node(new hmotion_node(-curenv->get_space_width()));
6135 break;
6136 case TOKEN_CHAR:
6137 curenv->add_char(charset_table[c]);
6138 break;
6139 case TOKEN_DUMMY:
6140 curenv->add_node(new dummy_node);
6141 break;
6142 case TOKEN_EMPTY:
6143 assert(0);
6144 break;
6145 case TOKEN_EOF:
6146 assert(0);
6147 break;
6148 case TOKEN_ESCAPE:
6149 if (escape_char != 0)
6150 curenv->add_char(charset_table[escape_char]);
6151 break;
6152 case TOKEN_BEGIN_TRAP:
6153 case TOKEN_END_TRAP:
6154 case TOKEN_PAGE_EJECTOR:
6155 // these are all handled in process_input_stack()
6156 break;
6157 case TOKEN_HYPHEN_INDICATOR:
6158 curenv->add_hyphen_indicator();
6159 break;
6160 case TOKEN_INTERRUPT:
6161 curenv->interrupt();
6162 break;
6163 case TOKEN_ITALIC_CORRECTION:
6164 curenv->add_italic_correction();
6165 break;
6166 case TOKEN_LEADER:
6167 curenv->handle_tab(1);
6168 break;
6169 case TOKEN_LEFT_BRACE:
6170 break;
6171 case TOKEN_MARK_INPUT:
6172 set_number_reg(nm, curenv->get_input_line_position().to_units());
6173 break;
6174 case TOKEN_NEWLINE:
6175 curenv->newline();
6176 break;
6177 case TOKEN_NODE:
6178 curenv->add_node(nd);
6179 nd = 0;
6180 break;
6181 case TOKEN_NUMBERED_CHAR:
6182 curenv->add_char(get_charinfo_by_number(val));
6183 break;
6184 case TOKEN_REQUEST:
6185 // handled in process_input_stack()
6186 break;
6187 case TOKEN_RIGHT_BRACE:
6188 break;
6189 case TOKEN_SPACE:
6190 curenv->space();
6191 break;
6192 case TOKEN_SPECIAL:
6193 curenv->add_char(get_charinfo(nm));
6194 break;
6195 case TOKEN_SPREAD:
6196 curenv->spread();
6197 break;
6198 case TOKEN_STRETCHABLE_SPACE:
6199 curenv->add_node(new unbreakable_space_node(curenv->get_space_width()));
6200 break;
6201 case TOKEN_UNSTRETCHABLE_SPACE:
6202 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width()));
6203 break;
6204 case TOKEN_TAB:
6205 curenv->handle_tab(0);
6206 break;
6207 case TOKEN_TRANSPARENT:
6208 break;
6209 case TOKEN_TRANSPARENT_DUMMY:
6210 curenv->add_node(new transparent_dummy_node);
6211 break;
6212 case TOKEN_ZERO_WIDTH_BREAK:
6214 node *tmp = new space_node(H0);
6215 tmp->freeze_space();
6216 tmp->is_escape_colon();
6217 curenv->add_node(tmp);
6218 break;
6220 default:
6221 assert(0);
6225 class nargs_reg : public reg {
6226 public:
6227 const char *get_string();
6230 const char *nargs_reg::get_string()
6232 return i_to_a(input_stack::nargs());
6235 class lineno_reg : public reg {
6236 public:
6237 const char *get_string();
6240 const char *lineno_reg::get_string()
6242 int line;
6243 const char *file;
6244 if (!input_stack::get_location(0, &file, &line))
6245 line = 0;
6246 return i_to_a(line);
6249 class writable_lineno_reg : public general_reg {
6250 public:
6251 writable_lineno_reg();
6252 void set_value(units);
6253 int get_value(units *);
6256 writable_lineno_reg::writable_lineno_reg()
6260 int writable_lineno_reg::get_value(units *res)
6262 int line;
6263 const char *file;
6264 if (!input_stack::get_location(0, &file, &line))
6265 return 0;
6266 *res = line;
6267 return 1;
6270 void writable_lineno_reg::set_value(units n)
6272 input_stack::set_location(0, n);
6275 class filename_reg : public reg {
6276 public:
6277 const char *get_string();
6280 const char *filename_reg::get_string()
6282 int line;
6283 const char *file;
6284 if (input_stack::get_location(0, &file, &line))
6285 return file;
6286 else
6287 return 0;
6290 class constant_reg : public reg {
6291 const char *s;
6292 public:
6293 constant_reg(const char *);
6294 const char *get_string();
6297 constant_reg::constant_reg(const char *p) : s(p)
6301 const char *constant_reg::get_string()
6303 return s;
6306 constant_int_reg::constant_int_reg(int *q) : p(q)
6310 const char *constant_int_reg::get_string()
6312 return i_to_a(*p);
6315 void abort_request()
6317 int c;
6318 if (tok.eof())
6319 c = EOF;
6320 else if (tok.newline())
6321 c = '\n';
6322 else {
6323 while ((c = get_copy(0)) == ' ')
6326 if (c == EOF || c == '\n')
6327 fputs("User Abort.", stderr);
6328 else {
6329 for (; c != '\n' && c != EOF; c = get_copy(0))
6330 fputs(asciify(c), stderr);
6332 fputc('\n', stderr);
6333 cleanup_and_exit(1);
6336 char *read_string()
6338 int len = 256;
6339 char *s = new char[len];
6340 int c;
6341 while ((c = get_copy(0)) == ' ')
6343 int i = 0;
6344 while (c != '\n' && c != EOF) {
6345 if (!invalid_input_char(c)) {
6346 if (i + 2 > len) {
6347 char *tem = s;
6348 s = new char[len*2];
6349 memcpy(s, tem, len);
6350 len *= 2;
6351 a_delete tem;
6353 s[i++] = c;
6355 c = get_copy(0);
6357 s[i] = '\0';
6358 tok.next();
6359 if (i == 0) {
6360 a_delete s;
6361 return 0;
6363 return s;
6366 void pipe_output()
6368 if (safer_flag) {
6369 error(".pi request not allowed in safer mode");
6370 skip_line();
6372 else {
6373 #ifdef POPEN_MISSING
6374 error("pipes not available on this system");
6375 skip_line();
6376 #else /* not POPEN_MISSING */
6377 if (the_output) {
6378 error("can't pipe: output already started");
6379 skip_line();
6381 else {
6382 char *pc;
6383 if ((pc = read_string()) == 0)
6384 error("can't pipe to empty command");
6385 if (pipe_command) {
6386 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
6387 strcpy(s, pipe_command);
6388 strcat(s, "|");
6389 strcat(s, pc);
6390 a_delete pipe_command;
6391 a_delete pc;
6392 pipe_command = s;
6394 else
6395 pipe_command = pc;
6397 #endif /* not POPEN_MISSING */
6401 static int system_status;
6403 void system_request()
6405 if (safer_flag) {
6406 error(".sy request not allowed in safer mode");
6407 skip_line();
6409 else {
6410 char *command = read_string();
6411 if (!command)
6412 error("empty command");
6413 else {
6414 system_status = system(command);
6415 a_delete command;
6420 void copy_file()
6422 if (curdiv == topdiv && topdiv->before_first_page) {
6423 handle_initial_request(COPY_FILE_REQUEST);
6424 return;
6426 symbol filename = get_long_name(1);
6427 while (!tok.newline() && !tok.eof())
6428 tok.next();
6429 if (break_flag)
6430 curenv->do_break();
6431 if (!filename.is_null())
6432 curdiv->copy_file(filename.contents());
6433 tok.next();
6436 #ifdef COLUMN
6438 void vjustify()
6440 if (curdiv == topdiv && topdiv->before_first_page) {
6441 handle_initial_request(VJUSTIFY_REQUEST);
6442 return;
6444 symbol type = get_long_name(1);
6445 if (!type.is_null())
6446 curdiv->vjustify(type);
6447 skip_line();
6450 #endif /* COLUMN */
6452 void transparent_file()
6454 if (curdiv == topdiv && topdiv->before_first_page) {
6455 handle_initial_request(TRANSPARENT_FILE_REQUEST);
6456 return;
6458 symbol filename = get_long_name(1);
6459 while (!tok.newline() && !tok.eof())
6460 tok.next();
6461 if (break_flag)
6462 curenv->do_break();
6463 if (!filename.is_null()) {
6464 errno = 0;
6465 FILE *fp = fopen(filename.contents(), "r");
6466 if (!fp)
6467 error("can't open `%1': %2", filename.contents(), strerror(errno));
6468 else {
6469 int bol = 1;
6470 for (;;) {
6471 int c = getc(fp);
6472 if (c == EOF)
6473 break;
6474 if (invalid_input_char(c))
6475 warning(WARN_INPUT, "invalid input character code %1", int(c));
6476 else {
6477 curdiv->transparent_output(c);
6478 bol = c == '\n';
6481 if (!bol)
6482 curdiv->transparent_output('\n');
6483 fclose(fp);
6486 tok.next();
6489 class page_range {
6490 int first;
6491 int last;
6492 public:
6493 page_range *next;
6494 page_range(int, int, page_range *);
6495 int contains(int n);
6498 page_range::page_range(int i, int j, page_range *p)
6499 : first(i), last(j), next(p)
6503 int page_range::contains(int n)
6505 return n >= first && (last <= 0 || n <= last);
6508 page_range *output_page_list = 0;
6510 int in_output_page_list(int n)
6512 if (!output_page_list)
6513 return 1;
6514 for (page_range *p = output_page_list; p; p = p->next)
6515 if (p->contains(n))
6516 return 1;
6517 return 0;
6520 static void parse_output_page_list(char *p)
6522 for (;;) {
6523 int i;
6524 if (*p == '-')
6525 i = 1;
6526 else if (csdigit(*p)) {
6527 i = 0;
6529 i = i*10 + *p++ - '0';
6530 while (csdigit(*p));
6532 else
6533 break;
6534 int j;
6535 if (*p == '-') {
6536 p++;
6537 j = 0;
6538 if (csdigit(*p)) {
6540 j = j*10 + *p++ - '0';
6541 while (csdigit(*p));
6544 else
6545 j = i;
6546 if (j == 0)
6547 last_page_number = -1;
6548 else if (last_page_number >= 0 && j > last_page_number)
6549 last_page_number = j;
6550 output_page_list = new page_range(i, j, output_page_list);
6551 if (*p != ',')
6552 break;
6553 ++p;
6555 if (*p != '\0') {
6556 error("bad output page list");
6557 output_page_list = 0;
6561 static FILE *open_mac_file(const char *mac, char **path)
6563 // Try first FOOBAR.tmac, then tmac.FOOBAR
6564 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
6565 strcpy(s1, mac);
6566 strcat(s1, MACRO_POSTFIX);
6567 FILE *fp = mac_path->open_file(s1, path);
6568 a_delete s1;
6569 if (!fp) {
6570 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
6571 strcpy(s2, MACRO_PREFIX);
6572 strcat(s2, mac);
6573 fp = mac_path->open_file(s2, path);
6574 a_delete s2;
6576 return fp;
6579 static void process_macro_file(const char *mac)
6581 char *path;
6582 FILE *fp = open_mac_file(mac, &path);
6583 if (!fp)
6584 fatal("can't find macro file %1", mac);
6585 const char *s = symbol(path).contents();
6586 a_delete path;
6587 input_stack::push(new file_iterator(fp, s));
6588 tok.next();
6589 process_input_stack();
6592 static void process_startup_file(char *filename)
6594 char *path;
6595 search_path *orig_mac_path = mac_path;
6596 mac_path = &config_macro_path;
6597 FILE *fp = mac_path->open_file(filename, &path);
6598 if (fp) {
6599 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6600 a_delete path;
6601 tok.next();
6602 process_input_stack();
6604 mac_path = orig_mac_path;
6607 void macro_source()
6609 symbol nm = get_long_name(1);
6610 if (nm.is_null())
6611 skip_line();
6612 else {
6613 while (!tok.newline() && !tok.eof())
6614 tok.next();
6615 char *path;
6616 FILE *fp = mac_path->open_file(nm.contents(), &path);
6617 // .mso doesn't (and cannot) go through open_mac_file, so we
6618 // need to do it here manually: If we have tmac.FOOBAR, try
6619 // FOOBAR.tmac and vice versa
6620 if (!fp) {
6621 const char *fn = nm.contents();
6622 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
6623 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
6624 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
6625 strcat(s, MACRO_POSTFIX);
6626 fp = mac_path->open_file(s, &path);
6627 a_delete s;
6629 if (!fp) {
6630 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
6631 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
6632 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
6633 strcpy(s, MACRO_PREFIX);
6634 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
6635 fp = mac_path->open_file(s, &path);
6636 a_delete s;
6640 if (fp) {
6641 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6642 a_delete path;
6644 else
6645 error("can't find macro file `%1'", nm.contents());
6646 tok.next();
6650 static void process_input_file(const char *name)
6652 FILE *fp;
6653 if (strcmp(name, "-") == 0) {
6654 clearerr(stdin);
6655 fp = stdin;
6657 else {
6658 errno = 0;
6659 fp = fopen(name, "r");
6660 if (!fp)
6661 fatal("can't open `%1': %2", name, strerror(errno));
6663 input_stack::push(new file_iterator(fp, name));
6664 tok.next();
6665 process_input_stack();
6668 // make sure the_input is empty before calling this
6670 static int evaluate_expression(const char *expr, units *res)
6672 input_stack::push(make_temp_iterator(expr));
6673 tok.next();
6674 int success = get_number(res, 'u');
6675 while (input_stack::get(0) != EOF)
6677 return success;
6680 static void do_register_assignment(const char *s)
6682 const char *p = strchr(s, '=');
6683 if (!p) {
6684 char buf[2];
6685 buf[0] = s[0];
6686 buf[1] = 0;
6687 units n;
6688 if (evaluate_expression(s + 1, &n))
6689 set_number_reg(buf, n);
6691 else {
6692 char *buf = new char[p - s + 1];
6693 memcpy(buf, s, p - s);
6694 buf[p - s] = 0;
6695 units n;
6696 if (evaluate_expression(p + 1, &n))
6697 set_number_reg(buf, n);
6698 a_delete buf;
6702 static void set_string(const char *name, const char *value)
6704 macro *m = new macro;
6705 for (const char *p = value; *p; p++)
6706 if (!invalid_input_char((unsigned char)*p))
6707 m->append(*p);
6708 request_dictionary.define(name, m);
6711 static void do_string_assignment(const char *s)
6713 const char *p = strchr(s, '=');
6714 if (!p) {
6715 char buf[2];
6716 buf[0] = s[0];
6717 buf[1] = 0;
6718 set_string(buf, s + 1);
6720 else {
6721 char *buf = new char[p - s + 1];
6722 memcpy(buf, s, p - s);
6723 buf[p - s] = 0;
6724 set_string(buf, p + 1);
6725 a_delete buf;
6729 struct string_list {
6730 const char *s;
6731 string_list *next;
6732 string_list(const char *ss) : s(ss), next(0) {}
6735 #if 0
6736 static void prepend_string(const char *s, string_list **p)
6738 string_list *l = new string_list(s);
6739 l->next = *p;
6740 *p = l;
6742 #endif
6744 static void add_string(const char *s, string_list **p)
6746 while (*p)
6747 p = &((*p)->next);
6748 *p = new string_list(s);
6751 void usage(FILE *stream, const char *prog)
6753 fprintf(stream,
6754 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
6755 " -rcn -Tname -Fdir -Mdir [files...]\n",
6756 prog);
6759 int main(int argc, char **argv)
6761 program_name = argv[0];
6762 static char stderr_buf[BUFSIZ];
6763 setbuf(stderr, stderr_buf);
6764 int c;
6765 string_list *macros = 0;
6766 string_list *register_assignments = 0;
6767 string_list *string_assignments = 0;
6768 int iflag = 0;
6769 int tflag = 0;
6770 int fflag = 0;
6771 int nflag = 0;
6772 int no_rc = 0; // don't process troffrc and troffrc-end
6773 int next_page_number;
6774 opterr = 0;
6775 hresolution = vresolution = 1;
6776 // restore $PATH if called from groff
6777 char* groff_path = getenv("GROFF_PATH__");
6778 if (groff_path) {
6779 string e = "PATH";
6780 e += '=';
6781 if (*groff_path)
6782 e += groff_path;
6783 e += '\0';
6784 if (putenv(strsave(e.contents())))
6785 fatal("putenv failed");
6787 static const struct option long_options[] = {
6788 { "help", no_argument, 0, CHAR_MAX + 1 },
6789 { "version", no_argument, 0, 'v' },
6790 { 0, 0, 0, 0 }
6792 while ((c = getopt_long(argc, argv, "abcivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU",
6793 long_options, 0))
6794 != EOF)
6795 switch(c) {
6796 case 'v':
6798 printf("GNU troff (groff) version %s\n", Version_string);
6799 exit(0);
6800 break;
6802 case 'T':
6803 device = optarg;
6804 tflag = 1;
6805 is_html = (strcmp(device, "html") == 0);
6806 break;
6807 case 'C':
6808 compatible_flag = 1;
6809 // fall through
6810 case 'c':
6811 color_flag = 0;
6812 break;
6813 case 'M':
6814 macro_path.command_line_dir(optarg);
6815 safer_macro_path.command_line_dir(optarg);
6816 config_macro_path.command_line_dir(optarg);
6817 break;
6818 case 'F':
6819 font::command_line_font_dir(optarg);
6820 break;
6821 case 'm':
6822 add_string(optarg, &macros);
6823 break;
6824 case 'E':
6825 inhibit_errors = 1;
6826 break;
6827 case 'R':
6828 no_rc = 1;
6829 break;
6830 case 'w':
6831 enable_warning(optarg);
6832 break;
6833 case 'W':
6834 disable_warning(optarg);
6835 break;
6836 case 'i':
6837 iflag = 1;
6838 break;
6839 case 'b':
6840 backtrace_flag = 1;
6841 break;
6842 case 'a':
6843 ascii_output_flag = 1;
6844 break;
6845 case 'z':
6846 suppress_output_flag = 1;
6847 break;
6848 case 'n':
6849 if (sscanf(optarg, "%d", &next_page_number) == 1)
6850 nflag++;
6851 else
6852 error("bad page number");
6853 break;
6854 case 'o':
6855 parse_output_page_list(optarg);
6856 break;
6857 case 'd':
6858 if (*optarg == '\0')
6859 error("`-d' requires non-empty argument");
6860 else
6861 add_string(optarg, &string_assignments);
6862 break;
6863 case 'r':
6864 if (*optarg == '\0')
6865 error("`-r' requires non-empty argument");
6866 else
6867 add_string(optarg, &register_assignments);
6868 break;
6869 case 'f':
6870 default_family = symbol(optarg);
6871 fflag = 1;
6872 break;
6873 case 'q':
6874 case 's':
6875 case 't':
6876 // silently ignore these
6877 break;
6878 case 'U':
6879 safer_flag = 0; // unsafe behaviour
6880 break;
6881 case CHAR_MAX + 1: // --help
6882 usage(stdout, argv[0]);
6883 exit(0);
6884 break;
6885 case '?':
6886 usage(stderr, argv[0]);
6887 exit(1);
6888 break; // never reached
6889 default:
6890 assert(0);
6892 if (!safer_flag)
6893 mac_path = &macro_path;
6894 set_string(".T", device);
6895 init_charset_table();
6896 init_hpf_code_table();
6897 if (!font::load_desc())
6898 fatal("sorry, I can't continue");
6899 units_per_inch = font::res;
6900 hresolution = font::hor;
6901 vresolution = font::vert;
6902 sizescale = font::sizescale;
6903 tcommand_flag = font::tcommand;
6904 warn_scale = (double)units_per_inch;
6905 warn_scaling_indicator = 'i';
6906 if (!fflag && font::family != 0 && *font::family != '\0')
6907 default_family = symbol(font::family);
6908 font_size::init_size_table(font::sizes);
6909 int i;
6910 int j = 1;
6911 if (font::style_table) {
6912 for (i = 0; font::style_table[i]; i++)
6913 mount_style(j++, symbol(font::style_table[i]));
6915 for (i = 0; font::font_name_table[i]; i++, j++)
6916 // In the DESC file a font name of 0 (zero) means leave this
6917 // position empty.
6918 if (strcmp(font::font_name_table[i], "0") != 0)
6919 mount_font(j, symbol(font::font_name_table[i]));
6920 curdiv = topdiv = new top_level_diversion;
6921 if (nflag)
6922 topdiv->set_next_page_number(next_page_number);
6923 init_input_requests();
6924 init_env_requests();
6925 init_div_requests();
6926 #ifdef COLUMN
6927 init_column_requests();
6928 #endif /* COLUMN */
6929 init_node_requests();
6930 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
6931 init_registers();
6932 init_reg_requests();
6933 init_hyphen_requests();
6934 init_environments();
6935 while (string_assignments) {
6936 do_string_assignment(string_assignments->s);
6937 string_list *tem = string_assignments;
6938 string_assignments = string_assignments->next;
6939 delete tem;
6941 while (register_assignments) {
6942 do_register_assignment(register_assignments->s);
6943 string_list *tem = register_assignments;
6944 register_assignments = register_assignments->next;
6945 delete tem;
6947 if (!no_rc)
6948 process_startup_file(INITIAL_STARTUP_FILE);
6949 while (macros) {
6950 process_macro_file(macros->s);
6951 string_list *tem = macros;
6952 macros = macros->next;
6953 delete tem;
6955 if (!no_rc)
6956 process_startup_file(FINAL_STARTUP_FILE);
6957 for (i = optind; i < argc; i++)
6958 process_input_file(argv[i]);
6959 if (optind >= argc || iflag)
6960 process_input_file("-");
6961 exit_troff();
6962 return 0; // not reached
6965 void warn_request()
6967 int n;
6968 if (has_arg() && get_integer(&n)) {
6969 if (n & ~WARN_TOTAL) {
6970 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
6971 n &= WARN_TOTAL;
6973 warning_mask = n;
6975 else
6976 warning_mask = WARN_TOTAL;
6977 skip_line();
6980 static void init_registers()
6982 #ifdef LONG_FOR_TIME_T
6983 long
6984 #else /* not LONG_FOR_TIME_T */
6985 time_t
6986 #endif /* not LONG_FOR_TIME_T */
6987 t = time(0);
6988 // Use struct here to work around misfeature in old versions of g++.
6989 struct tm *tt = localtime(&t);
6990 set_number_reg("seconds", int(tt->tm_sec));
6991 set_number_reg("minutes", int(tt->tm_min));
6992 set_number_reg("hours", int(tt->tm_hour));
6993 set_number_reg("dw", int(tt->tm_wday + 1));
6994 set_number_reg("dy", int(tt->tm_mday));
6995 set_number_reg("mo", int(tt->tm_mon + 1));
6996 set_number_reg("year", int(1900 + tt->tm_year));
6997 set_number_reg("yr", int(tt->tm_year));
6998 set_number_reg("$$", getpid());
6999 number_reg_dictionary.define(".A",
7000 new constant_reg(ascii_output_flag
7001 ? "1"
7002 : "0"));
7006 * registers associated with \O
7009 static int output_reg_minx_contents = -1;
7010 static int output_reg_miny_contents = -1;
7011 static int output_reg_maxx_contents = -1;
7012 static int output_reg_maxy_contents = -1;
7014 void check_output_limits(int x, int y)
7016 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
7017 output_reg_minx_contents = x;
7018 if (x > output_reg_maxx_contents)
7019 output_reg_maxx_contents = x;
7020 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
7021 output_reg_miny_contents = y;
7022 if (y > output_reg_maxy_contents)
7023 output_reg_maxy_contents = y;
7026 void reset_output_registers(int miny)
7028 // fprintf(stderr, "reset_output_registers\n");
7029 output_reg_minx_contents = -1;
7030 output_reg_miny_contents = -1;
7031 output_reg_maxx_contents = -1;
7032 output_reg_maxy_contents = -1;
7035 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
7037 *minx = output_reg_minx_contents;
7038 *miny = output_reg_miny_contents;
7039 *maxx = output_reg_maxx_contents;
7040 *maxy = output_reg_maxy_contents;
7043 void init_input_requests()
7045 init_request("ab", abort_request);
7046 init_request("als", alias_macro);
7047 init_request("am", append_macro);
7048 init_request("am1", append_nocomp_macro);
7049 init_request("ami", append_indirect_macro);
7050 init_request("as", append_string);
7051 init_request("as1", append_nocomp_string);
7052 init_request("asciify", asciify_macro);
7053 init_request("backtrace", backtrace_request);
7054 init_request("blm", blank_line_macro);
7055 init_request("break", while_break_request);
7056 init_request("cf", copy_file);
7057 init_request("cflags", char_flags);
7058 init_request("char", define_character);
7059 init_request("chop", chop_macro);
7060 init_request("close", close_request);
7061 init_request("color", activate_color);
7062 init_request("continue", while_continue_request);
7063 init_request("cp", compatible);
7064 init_request("de", define_macro);
7065 init_request("de1", define_nocomp_macro);
7066 init_request("defcolor", define_color);
7067 init_request("dei", define_indirect_macro);
7068 init_request("do", do_request);
7069 init_request("ds", define_string);
7070 init_request("ds1", define_nocomp_string);
7071 init_request("ec", set_escape_char);
7072 init_request("ecr", restore_escape_char);
7073 init_request("ecs", save_escape_char);
7074 init_request("el", else_request);
7075 init_request("em", end_macro);
7076 init_request("eo", escape_off);
7077 init_request("ex", exit_request);
7078 init_request("fchar", define_fallback_character);
7079 #ifdef WIDOW_CONTROL
7080 init_request("fpl", flush_pending_lines);
7081 #endif /* WIDOW_CONTROL */
7082 init_request("hcode", hyphenation_code);
7083 init_request("hpfcode", hyphenation_patterns_file_code);
7084 init_request("ie", if_else_request);
7085 init_request("if", if_request);
7086 init_request("ig", ignore);
7087 init_request("length", length_request);
7088 init_request("lf", line_file);
7089 init_request("mso", macro_source);
7090 init_request("nop", nop_request);
7091 init_request("nx", next_file);
7092 init_request("open", open_request);
7093 init_request("opena", opena_request);
7094 init_request("output", output_request);
7095 init_request("pc", set_page_character);
7096 init_request("pi", pipe_output);
7097 init_request("pm", print_macros);
7098 init_request("psbb", ps_bbox_request);
7099 #ifndef POPEN_MISSING
7100 init_request("pso", pipe_source);
7101 #endif /* not POPEN_MISSING */
7102 init_request("rchar", remove_character);
7103 init_request("rd", read_request);
7104 init_request("return", return_macro_request);
7105 init_request("rm", remove_macro);
7106 init_request("rn", rename_macro);
7107 init_request("shift", shift);
7108 init_request("so", source);
7109 init_request("spreadwarn", spreadwarn_request);
7110 init_request("substring", substring_request);
7111 init_request("sy", system_request);
7112 init_request("tm", terminal);
7113 init_request("tm1", terminal1);
7114 init_request("tmc", terminal_continue);
7115 init_request("tr", translate);
7116 init_request("trf", transparent_file);
7117 init_request("trin", translate_input);
7118 init_request("trnt", translate_no_transparent);
7119 init_request("unformat", unformat_macro);
7120 init_request("warn", warn_request);
7121 init_request("while", while_request);
7122 init_request("write", write_request);
7123 init_request("writec", write_request_continue);
7124 init_request("writem", write_macro_request);
7125 init_request("nroff", nroff_request);
7126 init_request("troff", troff_request);
7127 #ifdef COLUMN
7128 init_request("vj", vjustify);
7129 #endif /* COLUMN */
7130 init_request("warnscale", warnscale_request);
7131 number_reg_dictionary.define(".$", new nargs_reg);
7132 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
7133 number_reg_dictionary.define(".F", new filename_reg);
7134 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
7135 number_reg_dictionary.define(".R", new constant_reg("10000"));
7136 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
7137 extern const char *revision;
7138 number_reg_dictionary.define(".Y", new constant_reg(revision));
7139 number_reg_dictionary.define(".c", new lineno_reg);
7140 number_reg_dictionary.define(".g", new constant_reg("1"));
7141 number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
7142 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
7143 extern const char *major_version;
7144 number_reg_dictionary.define(".x", new constant_reg(major_version));
7145 extern const char *minor_version;
7146 number_reg_dictionary.define(".y", new constant_reg(minor_version));
7147 number_reg_dictionary.define("c.", new writable_lineno_reg);
7148 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
7149 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
7150 number_reg_dictionary.define("opmaxx",
7151 new variable_reg(&output_reg_maxx_contents));
7152 number_reg_dictionary.define("opmaxy",
7153 new variable_reg(&output_reg_maxy_contents));
7154 number_reg_dictionary.define("opminx",
7155 new variable_reg(&output_reg_minx_contents));
7156 number_reg_dictionary.define("opminy",
7157 new variable_reg(&output_reg_miny_contents));
7158 number_reg_dictionary.define("slimit",
7159 new variable_reg(&input_stack::limit));
7160 number_reg_dictionary.define("systat", new variable_reg(&system_status));
7161 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
7162 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
7165 object_dictionary request_dictionary(501);
7167 void init_request(const char *s, REQUEST_FUNCP f)
7169 request_dictionary.define(s, new request(f));
7172 static request_or_macro *lookup_request(symbol nm)
7174 assert(!nm.is_null());
7175 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
7176 if (p == 0) {
7177 warning(WARN_MAC, "`%1' not defined", nm.contents());
7178 p = new macro;
7179 request_dictionary.define(nm, p);
7181 return p;
7184 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7186 // Don't interpret character definitions in compatible mode.
7187 int old_compatible_flag = compatible_flag;
7188 compatible_flag = 0;
7189 int old_escape_char = escape_char;
7190 escape_char = '\\';
7191 macro *mac = ci->set_macro(0);
7192 assert(mac != 0);
7193 environment *oldenv = curenv;
7194 environment env(envp);
7195 curenv = &env;
7196 curenv->set_composite();
7197 token old_tok = tok;
7198 input_stack::add_boundary();
7199 string_iterator *si =
7200 new string_iterator(*mac, "composite character", ci->nm);
7201 input_stack::push(si);
7202 // we don't use process_input_stack, because we don't want to recognise
7203 // requests
7204 for (;;) {
7205 tok.next();
7206 if (tok.eof())
7207 break;
7208 if (tok.newline()) {
7209 error("composite character mustn't contain newline");
7210 while (!tok.eof())
7211 tok.next();
7212 break;
7214 else
7215 tok.process();
7217 node *n = curenv->extract_output_line();
7218 input_stack::remove_boundary();
7219 ci->set_macro(mac);
7220 tok = old_tok;
7221 curenv = oldenv;
7222 compatible_flag = old_compatible_flag;
7223 escape_char = old_escape_char;
7224 return n;
7227 static node *read_draw_node()
7229 token start;
7230 start.next();
7231 if (!start.delimiter(1)){
7232 do {
7233 tok.next();
7234 } while (tok != start && !tok.newline() && !tok.eof());
7236 else {
7237 tok.next();
7238 if (tok == start)
7239 error("missing argument");
7240 else {
7241 unsigned char type = tok.ch();
7242 tok.next();
7243 int maxpoints = 10;
7244 hvpair *point = new hvpair[maxpoints];
7245 int npoints = 0;
7246 int no_last_v = 0;
7247 int err = 0;
7248 int i;
7249 for (i = 0; tok != start; i++) {
7250 if (i == maxpoints) {
7251 hvpair *oldpoint = point;
7252 point = new hvpair[maxpoints*2];
7253 for (int j = 0; j < maxpoints; j++)
7254 point[j] = oldpoint[j];
7255 maxpoints *= 2;
7256 a_delete oldpoint;
7258 if (!get_hunits(&point[i].h,
7259 type == 'f' || type == 't' ? 'u' : 'm')) {
7260 err = 1;
7261 break;
7263 ++npoints;
7264 tok.skip();
7265 point[i].v = V0;
7266 if (tok == start) {
7267 no_last_v = 1;
7268 break;
7270 if (!get_vunits(&point[i].v, 'v')) {
7271 err = 1;
7272 break;
7274 tok.skip();
7276 while (tok != start && !tok.newline() && !tok.eof())
7277 tok.next();
7278 if (!err) {
7279 switch (type) {
7280 case 'l':
7281 if (npoints != 1 || no_last_v) {
7282 error("two arguments needed for line");
7283 npoints = 1;
7285 break;
7286 case 'c':
7287 if (npoints != 1 || !no_last_v) {
7288 error("one argument needed for circle");
7289 npoints = 1;
7290 point[0].v = V0;
7292 break;
7293 case 'e':
7294 if (npoints != 1 || no_last_v) {
7295 error("two arguments needed for ellipse");
7296 npoints = 1;
7298 break;
7299 case 'a':
7300 if (npoints != 2 || no_last_v) {
7301 error("four arguments needed for arc");
7302 npoints = 2;
7304 break;
7305 case '~':
7306 if (no_last_v)
7307 error("even number of arguments needed for spline");
7308 break;
7309 case 'f':
7310 if (npoints != 1 || !no_last_v) {
7311 error("one argument needed for gray shade");
7312 npoints = 1;
7313 point[0].v = V0;
7315 default:
7316 // silently pass it through
7317 break;
7319 draw_node *dn = new draw_node(type, point, npoints,
7320 curenv->get_font_size());
7321 a_delete point;
7322 return dn;
7324 else {
7325 a_delete point;
7329 return 0;
7332 static struct {
7333 const char *name;
7334 int mask;
7335 } warning_table[] = {
7336 { "char", WARN_CHAR },
7337 { "range", WARN_RANGE },
7338 { "break", WARN_BREAK },
7339 { "delim", WARN_DELIM },
7340 { "el", WARN_EL },
7341 { "scale", WARN_SCALE },
7342 { "number", WARN_NUMBER },
7343 { "syntax", WARN_SYNTAX },
7344 { "tab", WARN_TAB },
7345 { "right-brace", WARN_RIGHT_BRACE },
7346 { "missing", WARN_MISSING },
7347 { "input", WARN_INPUT },
7348 { "escape", WARN_ESCAPE },
7349 { "space", WARN_SPACE },
7350 { "font", WARN_FONT },
7351 { "di", WARN_DI },
7352 { "mac", WARN_MAC },
7353 { "reg", WARN_REG },
7354 { "ig", WARN_IG },
7355 { "color", WARN_COLOR },
7356 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
7357 { "w", WARN_TOTAL },
7358 { "default", DEFAULT_WARNING_MASK },
7361 static int lookup_warning(const char *name)
7363 for (unsigned int i = 0;
7364 i < sizeof(warning_table)/sizeof(warning_table[0]);
7365 i++)
7366 if (strcmp(name, warning_table[i].name) == 0)
7367 return warning_table[i].mask;
7368 return 0;
7371 static void enable_warning(const char *name)
7373 int mask = lookup_warning(name);
7374 if (mask)
7375 warning_mask |= mask;
7376 else
7377 error("unknown warning `%1'", name);
7380 static void disable_warning(const char *name)
7382 int mask = lookup_warning(name);
7383 if (mask)
7384 warning_mask &= ~mask;
7385 else
7386 error("unknown warning `%1'", name);
7389 static void copy_mode_error(const char *format,
7390 const errarg &arg1,
7391 const errarg &arg2,
7392 const errarg &arg3)
7394 if (ignoring) {
7395 static const char prefix[] = "(in ignored input) ";
7396 char *s = new char[sizeof(prefix) + strlen(format)];
7397 strcpy(s, prefix);
7398 strcat(s, format);
7399 warning(WARN_IG, s, arg1, arg2, arg3);
7400 a_delete s;
7402 else
7403 error(format, arg1, arg2, arg3);
7406 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
7408 static void do_error(error_type type,
7409 const char *format,
7410 const errarg &arg1,
7411 const errarg &arg2,
7412 const errarg &arg3)
7414 const char *filename;
7415 int lineno;
7416 if (inhibit_errors && type < FATAL)
7417 return;
7418 if (backtrace_flag)
7419 input_stack::backtrace();
7420 if (!get_file_line(&filename, &lineno))
7421 filename = 0;
7422 if (filename)
7423 errprint("%1:%2: ", filename, lineno);
7424 else if (program_name)
7425 fprintf(stderr, "%s: ", program_name);
7426 switch (type) {
7427 case FATAL:
7428 fputs("fatal error: ", stderr);
7429 break;
7430 case ERROR:
7431 break;
7432 case WARNING:
7433 fputs("warning: ", stderr);
7434 break;
7435 case OUTPUT_WARNING:
7436 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
7437 fprintf(stderr, "warning [p %d, %.1f%c",
7438 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
7439 if (topdiv != curdiv) {
7440 double fromtop1 = curdiv->get_vertical_position().to_units()
7441 / warn_scale;
7442 fprintf(stderr, ", div `%s', %.1f%c",
7443 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
7445 fprintf(stderr, "]: ");
7446 break;
7448 errprint(format, arg1, arg2, arg3);
7449 fputc('\n', stderr);
7450 fflush(stderr);
7451 if (type == FATAL)
7452 cleanup_and_exit(1);
7455 int warning(warning_type t,
7456 const char *format,
7457 const errarg &arg1,
7458 const errarg &arg2,
7459 const errarg &arg3)
7461 if ((t & warning_mask) != 0) {
7462 do_error(WARNING, format, arg1, arg2, arg3);
7463 return 1;
7465 else
7466 return 0;
7469 int output_warning(warning_type t,
7470 const char *format,
7471 const errarg &arg1,
7472 const errarg &arg2,
7473 const errarg &arg3)
7475 if ((t & warning_mask) != 0) {
7476 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
7477 return 1;
7479 else
7480 return 0;
7483 void error(const char *format,
7484 const errarg &arg1,
7485 const errarg &arg2,
7486 const errarg &arg3)
7488 do_error(ERROR, format, arg1, arg2, arg3);
7491 void fatal(const char *format,
7492 const errarg &arg1,
7493 const errarg &arg2,
7494 const errarg &arg3)
7496 do_error(FATAL, format, arg1, arg2, arg3);
7499 void fatal_with_file_and_line(const char *filename, int lineno,
7500 const char *format,
7501 const errarg &arg1,
7502 const errarg &arg2,
7503 const errarg &arg3)
7505 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
7506 errprint(format, arg1, arg2, arg3);
7507 fputc('\n', stderr);
7508 fflush(stderr);
7509 cleanup_and_exit(1);
7512 void error_with_file_and_line(const char *filename, int lineno,
7513 const char *format,
7514 const errarg &arg1,
7515 const errarg &arg2,
7516 const errarg &arg3)
7518 fprintf(stderr, "%s:%d: error: ", filename, lineno);
7519 errprint(format, arg1, arg2, arg3);
7520 fputc('\n', stderr);
7521 fflush(stderr);
7524 dictionary charinfo_dictionary(501);
7526 charinfo *get_charinfo(symbol nm)
7528 void *p = charinfo_dictionary.lookup(nm);
7529 if (p != 0)
7530 return (charinfo *)p;
7531 charinfo *cp = new charinfo(nm);
7532 (void)charinfo_dictionary.lookup(nm, cp);
7533 return cp;
7536 int charinfo::next_index = 0;
7538 charinfo::charinfo(symbol s)
7539 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
7540 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
7541 not_found(0), transparent_translate(1), translate_input(0),
7542 fallback(0), nm(s)
7544 index = next_index++;
7547 void charinfo::set_hyphenation_code(unsigned char c)
7549 hyphenation_code = c;
7552 void charinfo::set_translation(charinfo *ci, int tt, int ti)
7554 translation = ci;
7555 if (ci && ti) {
7556 if (hyphenation_code != 0)
7557 ci->set_hyphenation_code(hyphenation_code);
7558 if (asciify_code != 0)
7559 ci->set_asciify_code(asciify_code);
7560 else if (ascii_code != 0)
7561 ci->set_asciify_code(ascii_code);
7562 ci->set_translation_input();
7564 special_translation = TRANSLATE_NONE;
7565 transparent_translate = tt;
7568 void charinfo::set_special_translation(int c, int tt)
7570 special_translation = c;
7571 translation = 0;
7572 transparent_translate = tt;
7575 void charinfo::set_ascii_code(unsigned char c)
7577 ascii_code = c;
7580 void charinfo::set_asciify_code(unsigned char c)
7582 asciify_code = c;
7585 macro *charinfo::set_macro(macro *m, int f)
7587 macro *tem = mac;
7588 mac = m;
7589 fallback = f;
7590 return tem;
7593 void charinfo::set_number(int n)
7595 number = n;
7596 flags |= NUMBERED;
7599 int charinfo::get_number()
7601 assert(flags & NUMBERED);
7602 return number;
7605 symbol UNNAMED_SYMBOL("---");
7607 // For numbered characters not between 0 and 255, we make a symbol out
7608 // of the number and store them in this dictionary.
7610 dictionary numbered_charinfo_dictionary(11);
7612 charinfo *get_charinfo_by_number(int n)
7614 static charinfo *number_table[256];
7616 if (n >= 0 && n < 256) {
7617 charinfo *ci = number_table[n];
7618 if (!ci) {
7619 ci = new charinfo(UNNAMED_SYMBOL);
7620 ci->set_number(n);
7621 number_table[n] = ci;
7623 return ci;
7625 else {
7626 symbol ns(i_to_a(n));
7627 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
7628 if (!ci) {
7629 ci = new charinfo(UNNAMED_SYMBOL);
7630 ci->set_number(n);
7631 numbered_charinfo_dictionary.lookup(ns, ci);
7633 return ci;
7637 int font::name_to_index(const char *nm)
7639 charinfo *ci;
7640 if (nm[1] == 0)
7641 ci = charset_table[nm[0] & 0xff];
7642 else if (nm[0] == '\\' && nm[2] == 0)
7643 ci = get_charinfo(symbol(nm + 1));
7644 else
7645 ci = get_charinfo(symbol(nm));
7646 if (ci == 0)
7647 return -1;
7648 else
7649 return ci->get_index();
7652 int font::number_to_index(int n)
7654 return get_charinfo_by_number(n)->get_index();