Add .output request, similar to \! at top-level.
[s-roff.git] / src / roff / troff / input.cc
blob4e32a3c1895f6ce224a6ee99cfdcde98eb0af359
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().
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 #ifdef ISATTY_MISSING
51 #undef isatty
52 #define isatty(n) (1)
53 #else /* not ISATTY_MISSING */
54 #ifndef isatty
55 extern "C" {
56 int isatty(int);
58 #endif /* not isatty */
59 #endif /* not ISATTY_MISSING */
61 #define MACRO_PREFIX "tmac."
62 #define MACRO_POSTFIX ".tmac"
63 #define INITIAL_STARTUP_FILE "troffrc"
64 #define FINAL_STARTUP_FILE "troffrc-end"
65 #define DEFAULT_INPUT_STACK_LIMIT 1000
67 #ifndef DEFAULT_WARNING_MASK
68 // warnings that are enabled by default
69 #define DEFAULT_WARNING_MASK \
70 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
71 #endif
73 // initial size of buffer for reading names; expanded as necessary
74 #define ABUF_SIZE 16
76 extern "C" const char *Version_string;
78 #ifdef COLUMN
79 void init_column_requests();
80 #endif /* COLUMN */
82 static node *read_draw_node();
83 void handle_first_page_transition();
84 static void push_token(const token &);
85 void copy_file();
86 #ifdef COLUMN
87 void vjustify();
88 #endif /* COLUMN */
89 void transparent_file();
90 void process_input_stack();
92 const char *program_name = 0;
93 token tok;
94 int break_flag = 0;
95 int disable_color_flag = 0;
96 static int backtrace_flag = 0;
97 #ifndef POPEN_MISSING
98 char *pipe_command = 0;
99 #endif
100 charinfo *charset_table[256];
101 unsigned char hpf_code_table[256];
103 static int warning_mask = DEFAULT_WARNING_MASK;
104 static int inhibit_errors = 0;
105 static int ignoring = 0;
107 static void enable_warning(const char *);
108 static void disable_warning(const char *);
110 static int escape_char = '\\';
111 static symbol end_macro_name;
112 static symbol blank_line_macro_name;
113 static int compatible_flag = 0;
114 int ascii_output_flag = 0;
115 int suppress_output_flag = 0;
116 int is_html = 0;
117 int begin_level = 0; // number of nested .begin requests
119 int have_input = 0; // whether \f, \H, \R, \s, or \S has
120 // been processed in token::next()
121 int tcommand_flag = 0;
122 int safer_flag = 1; // safer by default
124 double spread_limit = -3.0 - 1.0; // negative means deactivated
126 double warn_scale;
127 char warn_scaling_indicator;
129 search_path *mac_path = &safer_macro_path;
131 static int get_copy(node**, int = 0);
132 static void copy_mode_error(const char *,
133 const errarg & = empty_errarg,
134 const errarg & = empty_errarg,
135 const errarg & = empty_errarg);
137 static symbol read_escape_name(int no_empty = 1);
138 static symbol read_long_escape_name(int no_empty = 1);
139 static void interpolate_string(symbol);
140 static void interpolate_macro(symbol);
141 static void interpolate_number_format(symbol);
142 static void interpolate_environment_variable(symbol);
144 static void interpolate_arg(symbol);
145 static request_or_macro *lookup_request(symbol);
146 static int get_delim_number(units *, int);
147 static int get_delim_number(units *, int, units);
148 static int get_line_arg(units *res, int si, charinfo **cp);
149 static int read_size(int *);
150 static symbol get_delim_name();
151 static void init_registers();
152 static void trapping_blank_line();
154 struct input_iterator;
155 input_iterator *make_temp_iterator(const char *);
156 const char *input_char_description(int);
159 void set_escape_char()
161 if (has_arg()) {
162 if (tok.ch() == 0) {
163 error("bad escape character");
164 escape_char = '\\';
166 else
167 escape_char = tok.ch();
169 else
170 escape_char = '\\';
171 skip_line();
174 void escape_off()
176 escape_char = 0;
177 skip_line();
180 static int saved_escape_char = '\\';
182 void save_escape_char()
184 saved_escape_char = escape_char;
185 skip_line();
188 void restore_escape_char()
190 escape_char = saved_escape_char;
191 skip_line();
194 class input_iterator {
195 public:
196 input_iterator();
197 virtual ~input_iterator() {}
198 int get(node **);
199 friend class input_stack;
200 protected:
201 const unsigned char *ptr;
202 const unsigned char *eptr;
203 input_iterator *next;
204 private:
205 virtual int fill(node **);
206 virtual int peek();
207 virtual int has_args() { return 0; }
208 virtual int nargs() { return 0; }
209 virtual input_iterator *get_arg(int) { return 0; }
210 virtual int get_location(int, const char **, int *) { return 0; }
211 virtual void backtrace() {}
212 virtual int set_location(const char *, int) { return 0; }
213 virtual int next_file(FILE *, const char *) { return 0; }
214 virtual void shift(int) {}
215 virtual int is_boundary() {return 0; }
216 virtual int internal_level() { return 0; }
217 virtual int is_file() { return 0; }
218 virtual int is_macro() { return 0; }
219 virtual void save_compatible_flag(int) {}
220 virtual int get_compatible_flag() { return 0; }
223 input_iterator::input_iterator()
224 : ptr(0), eptr(0)
228 int input_iterator::fill(node **)
230 return EOF;
233 int input_iterator::peek()
235 return EOF;
238 inline int input_iterator::get(node **p)
240 return ptr < eptr ? *ptr++ : fill(p);
243 class input_boundary : public input_iterator {
244 public:
245 int is_boundary() { return 1; }
248 class input_return_boundary : public input_iterator {
249 public:
250 int is_boundary() { return 2; }
253 class file_iterator : public input_iterator {
254 FILE *fp;
255 int lineno;
256 const char *filename;
257 int popened;
258 int newline_flag;
259 int suppress_newline_flag; // used by html
260 int seen_escape;
261 enum { BUF_SIZE = 512 };
262 unsigned char buf[BUF_SIZE];
263 void close();
264 public:
265 file_iterator(FILE *, const char *, int = 0);
266 ~file_iterator();
267 int fill(node **);
268 int peek();
269 int get_location(int, const char **, int *);
270 void backtrace();
271 int set_location(const char *, int);
272 int next_file(FILE *, const char *);
273 int is_file();
276 file_iterator::file_iterator(FILE *f, const char *fn, int po)
277 : fp(f), lineno(1), filename(fn), popened(po),
278 newline_flag(0), suppress_newline_flag(0), seen_escape(0)
280 if ((font::use_charnames_in_special) && (fn != 0)) {
281 if (!the_output)
282 init_output();
283 the_output->put_filename(fn);
287 file_iterator::~file_iterator()
289 close();
292 void file_iterator::close()
294 if (fp == stdin)
295 clearerr(stdin);
296 #ifndef POPEN_MISSING
297 else if (popened)
298 pclose(fp);
299 #endif /* not POPEN_MISSING */
300 else
301 fclose(fp);
304 int file_iterator::is_file()
306 return 1;
309 int file_iterator::next_file(FILE *f, const char *s)
311 close();
312 filename = s;
313 fp = f;
314 lineno = 1;
315 newline_flag = 0;
316 suppress_newline_flag = 0;
317 seen_escape = 0;
318 popened = 0;
319 ptr = 0;
320 eptr = 0;
321 return 1;
324 int file_iterator::fill(node **)
326 if (newline_flag && !suppress_newline_flag) {
327 curenv->add_html_tag_eol();
328 lineno++;
330 newline_flag = 0;
331 suppress_newline_flag = 0;
332 unsigned char *p = buf;
333 ptr = p;
334 unsigned char *e = p + BUF_SIZE;
335 while (p < e) {
336 int c = getc(fp);
337 if (c == EOF)
338 break;
339 if (invalid_input_char(c))
340 warning(WARN_INPUT, "invalid input character code %1", int(c));
341 else {
342 *p++ = c;
343 if (c == '\n') {
344 if (seen_escape && is_html)
345 suppress_newline_flag = 1;
346 seen_escape = 0;
347 newline_flag = 1;
348 break;
350 seen_escape = (c == '\\');
353 if (p > buf) {
354 eptr = p;
355 return *ptr++;
357 else {
358 eptr = p;
359 return EOF;
363 int file_iterator::peek()
365 int c = getc(fp);
366 while (invalid_input_char(c)) {
367 warning(WARN_INPUT, "invalid input character code %1", int(c));
368 c = getc(fp);
370 if (c != EOF)
371 ungetc(c, fp);
372 return c;
375 int file_iterator::get_location(int /*allow_macro*/,
376 const char **filenamep, int *linenop)
378 *linenop = lineno;
379 if (filename != 0 && strcmp(filename, "-") == 0)
380 *filenamep = "<standard input>";
381 else
382 *filenamep = filename;
383 return 1;
386 void file_iterator::backtrace()
388 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
389 popened ? "process" : "file");
392 int file_iterator::set_location(const char *f, int ln)
394 if (f) {
395 filename = f;
396 if (!the_output)
397 init_output();
398 the_output->put_filename(f);
400 lineno = ln;
401 return 1;
404 input_iterator nil_iterator;
406 class input_stack {
407 public:
408 static int get(node **);
409 static int peek();
410 static void push(input_iterator *);
411 static input_iterator *get_arg(int);
412 static int nargs();
413 static int get_location(int, const char **, int *);
414 static int set_location(const char *, int);
415 static void backtrace();
416 static void backtrace_all();
417 static void next_file(FILE *, const char *);
418 static void end_file();
419 static void shift(int n);
420 static void add_boundary();
421 static void add_return_boundary();
422 static int is_return_boundary();
423 static void remove_boundary();
424 static int get_level();
425 static void clear();
426 static void pop_macro();
427 static void save_compatible_flag(int);
428 static int get_compatible_flag();
430 static int limit;
431 private:
432 static input_iterator *top;
433 static int level;
435 static int finish_get(node **);
436 static int finish_peek();
439 input_iterator *input_stack::top = &nil_iterator;
440 int input_stack::level = 0;
441 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
443 inline int input_stack::get_level()
445 return level + top->internal_level();
448 inline int input_stack::get(node **np)
450 return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
453 int input_stack::finish_get(node **np)
455 for (;;) {
456 int c = top->fill(np);
457 if (c != EOF || top->is_boundary())
458 return c;
459 if (top == &nil_iterator)
460 break;
461 input_iterator *tem = top;
462 top = top->next;
463 level--;
464 delete tem;
465 if (top->ptr < top->eptr)
466 return *top->ptr++;
468 assert(level == 0);
469 return EOF;
472 inline int input_stack::peek()
474 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
477 int input_stack::finish_peek()
479 for (;;) {
480 int c = top->peek();
481 if (c != EOF || top->is_boundary())
482 return c;
483 if (top == &nil_iterator)
484 break;
485 input_iterator *tem = top;
486 top = top->next;
487 level--;
488 delete tem;
489 if (top->ptr < top->eptr)
490 return *top->ptr;
492 assert(level == 0);
493 return EOF;
496 void input_stack::add_boundary()
498 push(new input_boundary);
501 void input_stack::add_return_boundary()
503 push(new input_return_boundary);
506 int input_stack::is_return_boundary()
508 return top->is_boundary() == 2;
511 void input_stack::remove_boundary()
513 assert(top->is_boundary());
514 input_iterator *temp = top->next;
515 delete top;
516 top = temp;
517 level--;
520 void input_stack::push(input_iterator *in)
522 if (in == 0)
523 return;
524 if (++level > limit && limit > 0)
525 fatal("input stack limit exceeded (probable infinite loop)");
526 in->next = top;
527 top = in;
530 input_iterator *input_stack::get_arg(int i)
532 input_iterator *p;
533 for (p = top; p != 0; p = p->next)
534 if (p->has_args())
535 return p->get_arg(i);
536 return 0;
539 void input_stack::shift(int n)
541 for (input_iterator *p = top; p; p = p->next)
542 if (p->has_args()) {
543 p->shift(n);
544 return;
548 int input_stack::nargs()
550 for (input_iterator *p =top; p != 0; p = p->next)
551 if (p->has_args())
552 return p->nargs();
553 return 0;
556 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
558 for (input_iterator *p = top; p; p = p->next)
559 if (p->get_location(allow_macro, filenamep, linenop))
560 return 1;
561 return 0;
564 void input_stack::backtrace()
566 const char *f;
567 int n;
568 // only backtrace down to (not including) the topmost file
569 for (input_iterator *p = top;
570 p && !p->get_location(0, &f, &n);
571 p = p->next)
572 p->backtrace();
575 void input_stack::backtrace_all()
577 for (input_iterator *p = top; p; p = p->next)
578 p->backtrace();
581 int input_stack::set_location(const char *filename, int lineno)
583 for (input_iterator *p = top; p; p = p->next)
584 if (p->set_location(filename, lineno))
585 return 1;
586 return 0;
589 void input_stack::next_file(FILE *fp, const char *s)
591 input_iterator **pp;
592 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
593 if ((*pp)->next_file(fp, s))
594 return;
595 if (++level > limit && limit > 0)
596 fatal("input stack limit exceeded");
597 *pp = new file_iterator(fp, s);
598 (*pp)->next = &nil_iterator;
601 void input_stack::end_file()
603 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
604 if ((*pp)->is_file()) {
605 input_iterator *tem = *pp;
606 *pp = (*pp)->next;
607 delete tem;
608 level--;
609 return;
613 void input_stack::clear()
615 int nboundaries = 0;
616 while (top != &nil_iterator) {
617 if (top->is_boundary())
618 nboundaries++;
619 input_iterator *tem = top;
620 top = top->next;
621 level--;
622 delete tem;
624 // Keep while_request happy.
625 for (; nboundaries > 0; --nboundaries)
626 add_return_boundary();
629 void input_stack::pop_macro()
631 int nboundaries = 0;
632 int is_macro = 0;
633 do {
634 if (top->next == &nil_iterator)
635 break;
636 if (top->is_boundary())
637 nboundaries++;
638 is_macro = top->is_macro();
639 input_iterator *tem = top;
640 top = top->next;
641 level--;
642 delete tem;
643 } while (!is_macro);
644 // Keep while_request happy.
645 for (; nboundaries > 0; --nboundaries)
646 add_return_boundary();
649 inline void input_stack::save_compatible_flag(int f)
651 top->save_compatible_flag(f);
654 inline int input_stack::get_compatible_flag()
656 return top->get_compatible_flag();
659 void backtrace_request()
661 input_stack::backtrace_all();
662 fflush(stderr);
663 skip_line();
666 void next_file()
668 symbol nm = get_long_name(0);
669 while (!tok.newline() && !tok.eof())
670 tok.next();
671 if (nm.is_null())
672 input_stack::end_file();
673 else {
674 errno = 0;
675 FILE *fp = fopen(nm.contents(), "r");
676 if (!fp)
677 error("can't open `%1': %2", nm.contents(), strerror(errno));
678 else
679 input_stack::next_file(fp, nm.contents());
681 tok.next();
684 void shift()
686 int n;
687 if (!has_arg() || !get_integer(&n))
688 n = 1;
689 input_stack::shift(n);
690 skip_line();
693 static int get_char_for_escape_name()
695 int c = get_copy(0);
696 switch (c) {
697 case EOF:
698 copy_mode_error("end of input in escape name");
699 return '\0';
700 default:
701 if (!invalid_input_char(c))
702 break;
703 // fall through
704 case '\n':
705 if (c == '\n')
706 input_stack::push(make_temp_iterator("\n"));
707 case ' ':
708 case '\t':
709 case '\001':
710 case '\b':
711 copy_mode_error("%1 is not allowed in an escape name",
712 input_char_description(c));
713 return '\0';
715 return c;
718 static symbol read_two_char_escape_name()
720 char buf[3];
721 buf[0] = get_char_for_escape_name();
722 if (buf[0] != '\0') {
723 buf[1] = get_char_for_escape_name();
724 if (buf[1] == '\0')
725 buf[0] = 0;
726 else
727 buf[2] = 0;
729 return symbol(buf);
732 static symbol read_long_escape_name(int no_empty)
734 int start_level = input_stack::get_level();
735 char abuf[ABUF_SIZE];
736 char *buf = abuf;
737 int buf_size = ABUF_SIZE;
738 int i = 0;
739 for (;;) {
740 int c = get_char_for_escape_name();
741 if (c == 0) {
742 if (buf != abuf)
743 a_delete buf;
744 return NULL_SYMBOL;
746 if (i + 2 > buf_size) {
747 if (buf == abuf) {
748 buf = new char[ABUF_SIZE*2];
749 memcpy(buf, abuf, buf_size);
750 buf_size = ABUF_SIZE*2;
752 else {
753 char *old_buf = buf;
754 buf = new char[buf_size*2];
755 memcpy(buf, old_buf, buf_size);
756 buf_size *= 2;
757 a_delete old_buf;
760 if (c == ']' && input_stack::get_level() == start_level)
761 break;
762 buf[i++] = c;
764 buf[i] = 0;
765 if (buf == abuf) {
766 if (i == 0) {
767 if (no_empty)
768 copy_mode_error("empty escape name");
769 return EMPTY_SYMBOL;
771 return symbol(abuf);
773 else {
774 symbol s(buf);
775 a_delete buf;
776 return s;
780 static symbol read_escape_name(int no_empty)
782 int c = get_char_for_escape_name();
783 if (c == 0)
784 return NULL_SYMBOL;
785 if (c == '(')
786 return read_two_char_escape_name();
787 if (c == '[' && !compatible_flag)
788 return read_long_escape_name(no_empty);
789 char buf[2];
790 buf[0] = c;
791 buf[1] = '\0';
792 return symbol(buf);
795 static symbol read_increment_and_escape_name(int *incp)
797 int c = get_char_for_escape_name();
798 switch (c) {
799 case 0:
800 *incp = 0;
801 return NULL_SYMBOL;
802 case '(':
803 *incp = 0;
804 return read_two_char_escape_name();
805 case '+':
806 *incp = 1;
807 return read_escape_name();
808 case '-':
809 *incp = -1;
810 return read_escape_name();
811 case '[':
812 if (!compatible_flag) {
813 *incp = 0;
814 return read_long_escape_name();
816 break;
818 *incp = 0;
819 char buf[2];
820 buf[0] = c;
821 buf[1] = '\0';
822 return symbol(buf);
825 static int get_copy(node **nd, int defining)
827 for (;;) {
828 int c = input_stack::get(nd);
829 if (c == ESCAPE_NEWLINE) {
830 if (defining)
831 return c;
832 do {
833 c = input_stack::get(nd);
834 } while (c == ESCAPE_NEWLINE);
836 if (c != escape_char || escape_char <= 0)
837 return c;
838 c = input_stack::peek();
839 switch(c) {
840 case 0:
841 return escape_char;
842 case '"':
843 (void)input_stack::get(0);
844 while ((c = input_stack::get(0)) != '\n' && c != EOF)
846 return c;
847 case '#': // Like \" but newline is ignored.
848 (void)input_stack::get(0);
849 while ((c = input_stack::get(0)) != '\n')
850 if (c == EOF)
851 return EOF;
852 break;
853 case '$':
855 (void)input_stack::get(0);
856 symbol s = read_escape_name();
857 if (!(s.is_null() || s.is_empty()))
858 interpolate_arg(s);
859 break;
861 case '*':
863 (void)input_stack::get(0);
864 symbol s = read_escape_name();
865 if (!(s.is_null() || s.is_empty()))
866 interpolate_string(s);
867 break;
869 case 'a':
870 (void)input_stack::get(0);
871 return '\001';
872 case 'e':
873 (void)input_stack::get(0);
874 return ESCAPE_e;
875 case 'E':
876 (void)input_stack::get(0);
877 return ESCAPE_E;
878 case 'n':
880 (void)input_stack::get(0);
881 int inc;
882 symbol s = read_increment_and_escape_name(&inc);
883 if (!(s.is_null() || s.is_empty()))
884 interpolate_number_reg(s, inc);
885 break;
887 case 'g':
889 (void)input_stack::get(0);
890 symbol s = read_escape_name();
891 if (!(s.is_null() || s.is_empty()))
892 interpolate_number_format(s);
893 break;
895 case 't':
896 (void)input_stack::get(0);
897 return '\t';
898 case 'V':
900 (void)input_stack::get(0);
901 symbol s = read_escape_name();
902 if (!(s.is_null() || s.is_empty()))
903 interpolate_environment_variable(s);
904 break;
906 case '\n':
907 (void)input_stack::get(0);
908 if (defining)
909 return ESCAPE_NEWLINE;
910 break;
911 case ' ':
912 (void)input_stack::get(0);
913 return ESCAPE_SPACE;
914 case '~':
915 (void)input_stack::get(0);
916 return ESCAPE_TILDE;
917 case ':':
918 (void)input_stack::get(0);
919 return ESCAPE_COLON;
920 case '|':
921 (void)input_stack::get(0);
922 return ESCAPE_BAR;
923 case '^':
924 (void)input_stack::get(0);
925 return ESCAPE_CIRCUMFLEX;
926 case '{':
927 (void)input_stack::get(0);
928 return ESCAPE_LEFT_BRACE;
929 case '}':
930 (void)input_stack::get(0);
931 return ESCAPE_RIGHT_BRACE;
932 case '`':
933 (void)input_stack::get(0);
934 return ESCAPE_LEFT_QUOTE;
935 case '\'':
936 (void)input_stack::get(0);
937 return ESCAPE_RIGHT_QUOTE;
938 case '-':
939 (void)input_stack::get(0);
940 return ESCAPE_HYPHEN;
941 case '_':
942 (void)input_stack::get(0);
943 return ESCAPE_UNDERSCORE;
944 case 'c':
945 (void)input_stack::get(0);
946 return ESCAPE_c;
947 case '!':
948 (void)input_stack::get(0);
949 return ESCAPE_BANG;
950 case '?':
951 (void)input_stack::get(0);
952 return ESCAPE_QUESTION;
953 case '&':
954 (void)input_stack::get(0);
955 return ESCAPE_AMPERSAND;
956 case ')':
957 (void)input_stack::get(0);
958 return ESCAPE_RIGHT_PARENTHESIS;
959 case '.':
960 (void)input_stack::get(0);
961 return c;
962 case '%':
963 (void)input_stack::get(0);
964 return ESCAPE_PERCENT;
965 default:
966 if (c == escape_char) {
967 (void)input_stack::get(0);
968 return c;
970 else
971 return escape_char;
976 class non_interpreted_char_node : public node {
977 unsigned char c;
978 public:
979 non_interpreted_char_node(unsigned char);
980 node *copy();
981 int interpret(macro *);
982 int same(node *);
983 const char *type();
984 int force_tprint();
987 int non_interpreted_char_node::same(node *nd)
989 return c == ((non_interpreted_char_node *)nd)->c;
992 const char *non_interpreted_char_node::type()
994 return "non_interpreted_char_node";
997 int non_interpreted_char_node::force_tprint()
999 return 0;
1002 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1004 assert(n != 0);
1007 node *non_interpreted_char_node::copy()
1009 return new non_interpreted_char_node(c);
1012 int non_interpreted_char_node::interpret(macro *mac)
1014 mac->append(c);
1015 return 1;
1018 static void do_width();
1019 static node *do_non_interpreted();
1020 static node *do_special();
1021 static node *do_suppress(symbol nm);
1022 static void do_register();
1024 dictionary color_dictionary(501);
1025 static symbol default_symbol("default");
1027 static color *lookup_color(symbol nm)
1029 assert(!nm.is_null());
1030 if (nm == default_symbol)
1031 return &default_color;
1032 color *c = (color *)color_dictionary.lookup(nm);
1033 if (c == 0)
1034 warning(WARN_COLOR, "`%1' not defined", nm.contents());
1035 return c;
1038 static node *do_glyph_color(symbol nm)
1040 if (nm.is_null())
1041 return 0;
1042 if (nm.is_empty())
1043 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1044 else {
1045 color *tem = lookup_color(nm);
1046 if (tem)
1047 curenv->set_glyph_color(tem);
1048 else
1049 (void)color_dictionary.lookup(nm, new color);
1051 return new glyph_color_node(curenv->get_glyph_color());
1054 static node *do_fill_color(symbol nm)
1056 if (nm.is_null())
1057 return 0;
1058 if (nm.is_empty())
1059 curenv->set_fill_color(curenv->get_prev_fill_color());
1060 else {
1061 color *tem = lookup_color(nm);
1062 if (tem)
1063 curenv->set_fill_color(tem);
1064 else
1065 (void)color_dictionary.lookup(nm, new color);
1067 return new fill_color_node(curenv->get_fill_color());
1070 static unsigned int get_color_element(const char *scheme, const char *col)
1072 units val;
1073 if (!get_number(&val, 'f')) {
1074 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1075 tok.next();
1076 return 0;
1078 if (val < 0) {
1079 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1080 return 0;
1082 if (val > color::MAX_COLOR_VAL+1) {
1083 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1084 // we change 0x10000 to 0xffff
1085 return color::MAX_COLOR_VAL;
1087 return (unsigned int)val;
1090 static color *read_rgb()
1092 symbol component = get_long_name(0);
1093 if (component.is_null()) {
1094 warning(WARN_COLOR, "missing rgb color values");
1095 return 0;
1097 const char *s = component.contents();
1098 color *col = new color;
1099 if (*s == '#') {
1100 if (!col->read_rgb(s)) {
1101 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1102 delete col;
1103 return 0;
1106 else {
1107 input_stack::push(make_temp_iterator(" "));
1108 input_stack::push(make_temp_iterator(s));
1109 unsigned int r = get_color_element("rgb color", "red component");
1110 unsigned int g = get_color_element("rgb color", "green component");
1111 unsigned int b = get_color_element("rgb color", "blue component");
1112 col->set_rgb(r, g, b);
1114 return col;
1117 static color *read_cmy()
1119 symbol component = get_long_name(0);
1120 if (component.is_null()) {
1121 warning(WARN_COLOR, "missing cmy color values");
1122 return 0;
1124 const char *s = component.contents();
1125 color *col = new color;
1126 if (*s == '#') {
1127 if (!col->read_cmy(s)) {
1128 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1129 delete col;
1130 return 0;
1133 else {
1134 input_stack::push(make_temp_iterator(" "));
1135 input_stack::push(make_temp_iterator(s));
1136 unsigned int c = get_color_element("cmy color", "cyan component");
1137 unsigned int m = get_color_element("cmy color", "magenta component");
1138 unsigned int y = get_color_element("cmy color", "yellow component");
1139 col->set_cmy(c, m, y);
1141 return col;
1144 static color *read_cmyk()
1146 symbol component = get_long_name(0);
1147 if (component.is_null()) {
1148 warning(WARN_COLOR, "missing cmyk color values");
1149 return 0;
1151 const char *s = component.contents();
1152 color *col = new color;
1153 if (*s == '#') {
1154 if (!col->read_cmyk(s)) {
1155 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1156 delete col;
1157 return 0;
1160 else {
1161 input_stack::push(make_temp_iterator(" "));
1162 input_stack::push(make_temp_iterator(s));
1163 unsigned int c = get_color_element("cmyk color", "cyan component");
1164 unsigned int m = get_color_element("cmyk color", "magenta component");
1165 unsigned int y = get_color_element("cmyk color", "yellow component");
1166 unsigned int k = get_color_element("cmyk color", "black component");
1167 col->set_cmyk(c, m, y, k);
1169 return col;
1172 static color *read_gray()
1174 symbol component = get_long_name(0);
1175 if (component.is_null()) {
1176 warning(WARN_COLOR, "missing gray values");
1177 return 0;
1179 const char *s = component.contents();
1180 color *col = new color;
1181 if (*s == '#') {
1182 if (!col->read_gray(s)) {
1183 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1184 delete col;
1185 return 0;
1188 else {
1189 input_stack::push(make_temp_iterator(" "));
1190 input_stack::push(make_temp_iterator(s));
1191 unsigned int g = get_color_element("gray", "gray value");
1192 col->set_gray(g);
1194 return col;
1197 static void define_color()
1199 symbol color_name = get_long_name(1);
1200 if (color_name.is_null()) {
1201 skip_line();
1202 return;
1204 if (color_name == default_symbol) {
1205 warning(WARN_COLOR, "default color can't be redefined");
1206 skip_line();
1207 return;
1209 symbol style = get_long_name(1);
1210 if (style.is_null()) {
1211 skip_line();
1212 return;
1214 color *col;
1215 if (strcmp(style.contents(), "rgb") == 0)
1216 col = read_rgb();
1217 else if (strcmp(style.contents(), "cmyk") == 0)
1218 col = read_cmyk();
1219 else if (strcmp(style.contents(), "gray") == 0)
1220 col = read_gray();
1221 else if (strcmp(style.contents(), "grey") == 0)
1222 col = read_gray();
1223 else if (strcmp(style.contents(), "cmy") == 0)
1224 col = read_cmy();
1225 else {
1226 warning(WARN_COLOR,
1227 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1228 style.contents());
1229 skip_line();
1230 return;
1232 if (col)
1233 (void)color_dictionary.lookup(color_name, col);
1234 skip_line();
1237 static node *do_overstrike()
1239 token start;
1240 overstrike_node *on = new overstrike_node;
1241 int start_level = input_stack::get_level();
1242 start.next();
1243 for (;;) {
1244 tok.next();
1245 if (tok.newline() || tok.eof()) {
1246 warning(WARN_DELIM, "missing closing delimiter");
1247 break;
1249 if (tok == start
1250 && (compatible_flag || input_stack::get_level() == start_level))
1251 break;
1252 charinfo *ci = tok.get_char(1);
1253 if (ci) {
1254 node *n = curenv->make_char_node(ci);
1255 if (n)
1256 on->overstrike(n);
1259 return on;
1262 static node *do_bracket()
1264 token start;
1265 bracket_node *bn = new bracket_node;
1266 start.next();
1267 int start_level = input_stack::get_level();
1268 for (;;) {
1269 tok.next();
1270 if (tok.eof()) {
1271 warning(WARN_DELIM, "missing closing delimiter");
1272 break;
1274 if (tok.newline()) {
1275 warning(WARN_DELIM, "missing closing delimiter");
1276 input_stack::push(make_temp_iterator("\n"));
1277 break;
1279 if (tok == start
1280 && (compatible_flag || input_stack::get_level() == start_level))
1281 break;
1282 charinfo *ci = tok.get_char(1);
1283 if (ci) {
1284 node *n = curenv->make_char_node(ci);
1285 if (n)
1286 bn->bracket(n);
1289 return bn;
1292 static int do_name_test()
1294 token start;
1295 start.next();
1296 int start_level = input_stack::get_level();
1297 int bad_char = 0;
1298 int some_char = 0;
1299 for (;;) {
1300 tok.next();
1301 if (tok.newline() || tok.eof()) {
1302 warning(WARN_DELIM, "missing closing delimiter");
1303 break;
1305 if (tok == start
1306 && (compatible_flag || input_stack::get_level() == start_level))
1307 break;
1308 if (!tok.ch())
1309 bad_char = 1;
1310 some_char = 1;
1312 return some_char && !bad_char;
1315 static int do_expr_test()
1317 token start;
1318 start.next();
1319 int start_level = input_stack::get_level();
1320 if (!start.delimiter(1))
1321 return 0;
1322 tok.next();
1323 // disable all warning and error messages temporarily
1324 int saved_warning_mask = warning_mask;
1325 int saved_inhibit_errors = inhibit_errors;
1326 warning_mask = 0;
1327 inhibit_errors = 1;
1328 int dummy;
1329 int result = get_number_rigidly(&dummy, 'u');
1330 warning_mask = saved_warning_mask;
1331 inhibit_errors = saved_inhibit_errors;
1332 if (tok == start && input_stack::get_level() == start_level)
1333 return result;
1334 // ignore everything up to the delimiter in case we aren't right there
1335 for (;;) {
1336 tok.next();
1337 if (tok.newline() || tok.eof()) {
1338 warning(WARN_DELIM, "missing closing delimiter");
1339 break;
1341 if (tok == start && input_stack::get_level() == start_level)
1342 break;
1344 return 0;
1347 #if 0
1348 static node *do_zero_width()
1350 token start;
1351 start.next();
1352 int start_level = input_stack::get_level();
1353 environment env(curenv);
1354 environment *oldenv = curenv;
1355 curenv = &env;
1356 for (;;) {
1357 tok.next();
1358 if (tok.newline() || tok.eof()) {
1359 error("missing closing delimiter");
1360 break;
1362 if (tok == start
1363 && (compatible_flag || input_stack::get_level() == start_level))
1364 break;
1365 tok.process();
1367 curenv = oldenv;
1368 node *rev = env.extract_output_line();
1369 node *n = 0;
1370 while (rev) {
1371 node *tem = rev;
1372 rev = rev->next;
1373 tem->next = n;
1374 n = tem;
1376 return new zero_width_node(n);
1379 #else
1381 // It's undesirable for \Z to change environments, because then
1382 // \n(.w won't work as expected.
1384 static node *do_zero_width()
1386 node *rev = new dummy_node;
1387 token start;
1388 start.next();
1389 int start_level = input_stack::get_level();
1390 for (;;) {
1391 tok.next();
1392 if (tok.newline() || tok.eof()) {
1393 warning(WARN_DELIM, "missing closing delimiter");
1394 break;
1396 if (tok == start
1397 && (compatible_flag || input_stack::get_level() == start_level))
1398 break;
1399 if (!tok.add_to_node_list(&rev))
1400 error("invalid token in argument to \\Z");
1402 node *n = 0;
1403 while (rev) {
1404 node *tem = rev;
1405 rev = rev->next;
1406 tem->next = n;
1407 n = tem;
1409 return new zero_width_node(n);
1412 #endif
1414 token_node *node::get_token_node()
1416 return 0;
1419 class token_node : public node {
1420 public:
1421 token tk;
1422 token_node(const token &t);
1423 node *copy();
1424 token_node *get_token_node();
1425 int same(node *);
1426 const char *type();
1427 int force_tprint();
1430 token_node::token_node(const token &t) : tk(t)
1434 node *token_node::copy()
1436 return new token_node(tk);
1439 token_node *token_node::get_token_node()
1441 return this;
1444 int token_node::same(node *nd)
1446 return tk == ((token_node *)nd)->tk;
1449 const char *token_node::type()
1451 return "token_node";
1454 int token_node::force_tprint()
1456 return 0;
1459 token::token() : nd(0), type(TOKEN_EMPTY)
1463 token::~token()
1465 delete nd;
1468 token::token(const token &t)
1469 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1471 // Use two statements to work around bug in SGI C++.
1472 node *tem = t.nd;
1473 nd = tem ? tem->copy() : 0;
1476 void token::operator=(const token &t)
1478 delete nd;
1479 nm = t.nm;
1480 // Use two statements to work around bug in SGI C++.
1481 node *tem = t.nd;
1482 nd = tem ? tem->copy() : 0;
1483 c = t.c;
1484 val = t.val;
1485 dim = t.dim;
1486 type = t.type;
1489 void token::skip()
1491 while (space())
1492 next();
1495 int has_arg()
1497 while (tok.space())
1498 tok.next();
1499 return !tok.newline();
1502 void token::make_space()
1504 type = TOKEN_SPACE;
1507 void token::make_newline()
1509 type = TOKEN_NEWLINE;
1512 void token::next()
1514 if (nd) {
1515 delete nd;
1516 nd = 0;
1518 units x;
1519 for (;;) {
1520 node *n;
1521 int cc = input_stack::get(&n);
1522 if (cc != escape_char || escape_char == 0) {
1523 handle_normal_char:
1524 switch(cc) {
1525 case COMPATIBLE_SAVE:
1526 input_stack::save_compatible_flag(compatible_flag);
1527 compatible_flag = 0;
1528 continue;
1529 case COMPATIBLE_RESTORE:
1530 compatible_flag = input_stack::get_compatible_flag();
1531 continue;
1532 case EOF:
1533 type = TOKEN_EOF;
1534 return;
1535 case TRANSPARENT_FILE_REQUEST:
1536 case TITLE_REQUEST:
1537 case COPY_FILE_REQUEST:
1538 #ifdef COLUMN
1539 case VJUSTIFY_REQUEST:
1540 #endif /* COLUMN */
1541 type = TOKEN_REQUEST;
1542 c = cc;
1543 return;
1544 case BEGIN_TRAP:
1545 type = TOKEN_BEGIN_TRAP;
1546 return;
1547 case END_TRAP:
1548 type = TOKEN_END_TRAP;
1549 return;
1550 case LAST_PAGE_EJECTOR:
1551 seen_last_page_ejector = 1;
1552 // fall through
1553 case PAGE_EJECTOR:
1554 type = TOKEN_PAGE_EJECTOR;
1555 return;
1556 case ESCAPE_PERCENT:
1557 ESCAPE_PERCENT:
1558 type = TOKEN_HYPHEN_INDICATOR;
1559 return;
1560 case ESCAPE_SPACE:
1561 ESCAPE_SPACE:
1562 type = TOKEN_NODE;
1563 nd = new space_char_hmotion_node(curenv->get_space_width());
1564 return;
1565 case ESCAPE_TILDE:
1566 ESCAPE_TILDE:
1567 type = TOKEN_STRETCHABLE_SPACE;
1568 return;
1569 case ESCAPE_COLON:
1570 ESCAPE_COLON:
1571 type = TOKEN_ZERO_WIDTH_BREAK;
1572 return;
1573 case ESCAPE_e:
1574 ESCAPE_e:
1575 type = TOKEN_ESCAPE;
1576 return;
1577 case ESCAPE_E:
1578 goto handle_escape_char;
1579 case ESCAPE_BAR:
1580 ESCAPE_BAR:
1581 type = TOKEN_NODE;
1582 nd = new hmotion_node(curenv->get_narrow_space_width());
1583 return;
1584 case ESCAPE_CIRCUMFLEX:
1585 ESCAPE_CIRCUMFLEX:
1586 type = TOKEN_NODE;
1587 nd = new hmotion_node(curenv->get_half_narrow_space_width());
1588 return;
1589 case ESCAPE_NEWLINE:
1590 break;
1591 case ESCAPE_LEFT_BRACE:
1592 ESCAPE_LEFT_BRACE:
1593 type = TOKEN_LEFT_BRACE;
1594 return;
1595 case ESCAPE_RIGHT_BRACE:
1596 ESCAPE_RIGHT_BRACE:
1597 type = TOKEN_RIGHT_BRACE;
1598 return;
1599 case ESCAPE_LEFT_QUOTE:
1600 ESCAPE_LEFT_QUOTE:
1601 type = TOKEN_SPECIAL;
1602 nm = symbol("ga");
1603 return;
1604 case ESCAPE_RIGHT_QUOTE:
1605 ESCAPE_RIGHT_QUOTE:
1606 type = TOKEN_SPECIAL;
1607 nm = symbol("aa");
1608 return;
1609 case ESCAPE_HYPHEN:
1610 ESCAPE_HYPHEN:
1611 type = TOKEN_SPECIAL;
1612 nm = symbol("-");
1613 return;
1614 case ESCAPE_UNDERSCORE:
1615 ESCAPE_UNDERSCORE:
1616 type = TOKEN_SPECIAL;
1617 nm = symbol("ul");
1618 return;
1619 case ESCAPE_c:
1620 ESCAPE_c:
1621 type = TOKEN_INTERRUPT;
1622 return;
1623 case ESCAPE_BANG:
1624 ESCAPE_BANG:
1625 type = TOKEN_TRANSPARENT;
1626 return;
1627 case ESCAPE_QUESTION:
1628 ESCAPE_QUESTION:
1629 nd = do_non_interpreted();
1630 if (nd) {
1631 type = TOKEN_NODE;
1632 return;
1634 break;
1635 case ESCAPE_AMPERSAND:
1636 ESCAPE_AMPERSAND:
1637 type = TOKEN_DUMMY;
1638 return;
1639 case ESCAPE_RIGHT_PARENTHESIS:
1640 ESCAPE_RIGHT_PARENTHESIS:
1641 type = TOKEN_TRANSPARENT_DUMMY;
1642 return;
1643 case '\b':
1644 type = TOKEN_BACKSPACE;
1645 return;
1646 case ' ':
1647 type = TOKEN_SPACE;
1648 return;
1649 case '\t':
1650 type = TOKEN_TAB;
1651 return;
1652 case '\n':
1653 type = TOKEN_NEWLINE;
1654 return;
1655 case '\001':
1656 type = TOKEN_LEADER;
1657 return;
1658 case 0:
1660 assert(n != 0);
1661 token_node *tn = n->get_token_node();
1662 if (tn) {
1663 *this = tn->tk;
1664 delete tn;
1666 else {
1667 nd = n;
1668 type = TOKEN_NODE;
1671 return;
1672 default:
1673 type = TOKEN_CHAR;
1674 c = cc;
1675 return;
1678 else {
1679 handle_escape_char:
1680 cc = input_stack::get(0);
1681 switch(cc) {
1682 case '(':
1683 nm = read_two_char_escape_name();
1684 type = TOKEN_SPECIAL;
1685 return;
1686 case EOF:
1687 type = TOKEN_EOF;
1688 error("end of input after escape character");
1689 return;
1690 case '`':
1691 goto ESCAPE_LEFT_QUOTE;
1692 case '\'':
1693 goto ESCAPE_RIGHT_QUOTE;
1694 case '-':
1695 goto ESCAPE_HYPHEN;
1696 case '_':
1697 goto ESCAPE_UNDERSCORE;
1698 case '%':
1699 goto ESCAPE_PERCENT;
1700 case ' ':
1701 goto ESCAPE_SPACE;
1702 case '0':
1703 nd = new hmotion_node(curenv->get_digit_width());
1704 type = TOKEN_NODE;
1705 return;
1706 case '|':
1707 goto ESCAPE_BAR;
1708 case '^':
1709 goto ESCAPE_CIRCUMFLEX;
1710 case '/':
1711 type = TOKEN_ITALIC_CORRECTION;
1712 return;
1713 case ',':
1714 type = TOKEN_NODE;
1715 nd = new left_italic_corrected_node;
1716 return;
1717 case '&':
1718 goto ESCAPE_AMPERSAND;
1719 case ')':
1720 goto ESCAPE_RIGHT_PARENTHESIS;
1721 case '!':
1722 goto ESCAPE_BANG;
1723 case '?':
1724 goto ESCAPE_QUESTION;
1725 case '~':
1726 goto ESCAPE_TILDE;
1727 case ':':
1728 goto ESCAPE_COLON;
1729 case '"':
1730 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1732 if (cc == '\n')
1733 type = TOKEN_NEWLINE;
1734 else
1735 type = TOKEN_EOF;
1736 return;
1737 case '#': // Like \" but newline is ignored.
1738 while ((cc = input_stack::get(0)) != '\n')
1739 if (cc == EOF) {
1740 type = TOKEN_EOF;
1741 return;
1743 break;
1744 case '$':
1746 symbol nm = read_escape_name();
1747 if (!(nm.is_null() || nm.is_empty()))
1748 interpolate_arg(nm);
1749 break;
1751 case '*':
1753 symbol nm = read_escape_name();
1754 if (!(nm.is_null() || nm.is_empty()))
1755 interpolate_string(nm);
1756 break;
1758 case 'a':
1759 nd = new non_interpreted_char_node('\001');
1760 type = TOKEN_NODE;
1761 return;
1762 case 'A':
1763 c = '0' + do_name_test();
1764 type = TOKEN_CHAR;
1765 return;
1766 case 'b':
1767 nd = do_bracket();
1768 type = TOKEN_NODE;
1769 return;
1770 case 'B':
1771 c = '0' + do_expr_test();
1772 type = TOKEN_CHAR;
1773 return;
1774 case 'c':
1775 goto ESCAPE_c;
1776 case 'C':
1777 nm = get_delim_name();
1778 if (nm.is_null())
1779 break;
1780 type = TOKEN_SPECIAL;
1781 return;
1782 case 'd':
1783 type = TOKEN_NODE;
1784 nd = new vmotion_node(curenv->get_size()/2);
1785 return;
1786 case 'D':
1787 nd = read_draw_node();
1788 if (!nd)
1789 break;
1790 type = TOKEN_NODE;
1791 return;
1792 case 'e':
1793 goto ESCAPE_e;
1794 case 'E':
1795 goto handle_escape_char;
1796 case 'f':
1798 symbol s = read_escape_name(0);
1799 if (s.is_null())
1800 break;
1801 const char *p;
1802 for (p = s.contents(); *p != '\0'; p++)
1803 if (!csdigit(*p))
1804 break;
1805 if (*p || s.is_empty())
1806 curenv->set_font(s);
1807 else
1808 curenv->set_font(atoi(s.contents()));
1809 if (!compatible_flag)
1810 have_input = 1;
1811 break;
1813 case 'F':
1815 symbol s = read_escape_name(0);
1816 if (s.is_null())
1817 break;
1818 curenv->set_family(s);
1819 break;
1821 case 'g':
1823 symbol s = read_escape_name();
1824 if (!(s.is_null() || s.is_empty()))
1825 interpolate_number_format(s);
1826 break;
1828 case 'h':
1829 if (!get_delim_number(&x, 'm'))
1830 break;
1831 type = TOKEN_NODE;
1832 nd = new hmotion_node(x);
1833 return;
1834 case 'H':
1835 // don't take height increments relative to previous height if
1836 // in compatibility mode
1837 if (!compatible_flag && curenv->get_char_height())
1839 if (get_delim_number(&x, 'z', curenv->get_char_height()))
1840 curenv->set_char_height(x);
1842 else
1844 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1845 curenv->set_char_height(x);
1847 if (!compatible_flag)
1848 have_input = 1;
1849 break;
1850 case 'k':
1851 nm = read_escape_name();
1852 if (nm.is_null() || nm.is_empty())
1853 break;
1854 type = TOKEN_MARK_INPUT;
1855 return;
1856 case 'l':
1857 case 'L':
1859 charinfo *s = 0;
1860 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1861 break;
1862 if (s == 0)
1863 s = get_charinfo(cc == 'l' ? "ru" : "br");
1864 type = TOKEN_NODE;
1865 node *n = curenv->make_char_node(s);
1866 if (cc == 'l')
1867 nd = new hline_node(x, n);
1868 else
1869 nd = new vline_node(x, n);
1870 return;
1872 case 'm':
1873 nd = do_glyph_color(read_escape_name(0));
1874 if (!nd)
1875 break;
1876 type = TOKEN_NODE;
1877 return;
1878 case 'M':
1879 nd = do_fill_color(read_escape_name(0));
1880 if (!nd)
1881 break;
1882 type = TOKEN_NODE;
1883 return;
1884 case 'n':
1886 int inc;
1887 symbol nm = read_increment_and_escape_name(&inc);
1888 if (!(nm.is_null() || nm.is_empty()))
1889 interpolate_number_reg(nm, inc);
1890 break;
1892 case 'N':
1893 if (!get_delim_number(&val, 0))
1894 break;
1895 type = TOKEN_NUMBERED_CHAR;
1896 return;
1897 case 'o':
1898 nd = do_overstrike();
1899 type = TOKEN_NODE;
1900 return;
1901 case 'O':
1902 nd = do_suppress(read_escape_name());
1903 if (!nd)
1904 break;
1905 type = TOKEN_NODE;
1906 return;
1907 case 'p':
1908 type = TOKEN_SPREAD;
1909 return;
1910 case 'r':
1911 type = TOKEN_NODE;
1912 nd = new vmotion_node(-curenv->get_size());
1913 return;
1914 case 'R':
1915 do_register();
1916 if (!compatible_flag)
1917 have_input = 1;
1918 break;
1919 case 's':
1920 if (read_size(&x))
1921 curenv->set_size(x);
1922 if (!compatible_flag)
1923 have_input = 1;
1924 break;
1925 case 'S':
1926 if (get_delim_number(&x, 0))
1927 curenv->set_char_slant(x);
1928 if (!compatible_flag)
1929 have_input = 1;
1930 break;
1931 case 't':
1932 type = TOKEN_NODE;
1933 nd = new non_interpreted_char_node('\t');
1934 return;
1935 case 'u':
1936 type = TOKEN_NODE;
1937 nd = new vmotion_node(-curenv->get_size()/2);
1938 return;
1939 case 'v':
1940 if (!get_delim_number(&x, 'v'))
1941 break;
1942 type = TOKEN_NODE;
1943 nd = new vmotion_node(x);
1944 return;
1945 case 'V':
1947 symbol nm = read_escape_name();
1948 if (!(nm.is_null() || nm.is_empty()))
1949 interpolate_environment_variable(nm);
1950 break;
1952 case 'w':
1953 do_width();
1954 break;
1955 case 'x':
1956 if (!get_delim_number(&x, 'v'))
1957 break;
1958 type = TOKEN_NODE;
1959 nd = new extra_size_node(x);
1960 return;
1961 case 'X':
1962 nd = do_special();
1963 if (!nd)
1964 break;
1965 type = TOKEN_NODE;
1966 return;
1967 case 'Y':
1969 symbol s = read_escape_name();
1970 if (s.is_null() || s.is_empty())
1971 break;
1972 request_or_macro *p = lookup_request(s);
1973 macro *m = p->to_macro();
1974 if (!m) {
1975 error("can't transparently throughput a request");
1976 break;
1978 nd = new special_node(*m);
1979 type = TOKEN_NODE;
1980 return;
1982 case 'z':
1984 next();
1985 if (type == TOKEN_NODE)
1986 nd = new zero_width_node(nd);
1987 else {
1988 charinfo *ci = get_char(1);
1989 if (ci == 0)
1990 break;
1991 node *gn = curenv->make_char_node(ci);
1992 if (gn == 0)
1993 break;
1994 nd = new zero_width_node(gn);
1995 type = TOKEN_NODE;
1997 return;
1999 case 'Z':
2000 nd = do_zero_width();
2001 if (nd == 0)
2002 break;
2003 type = TOKEN_NODE;
2004 return;
2005 case '{':
2006 goto ESCAPE_LEFT_BRACE;
2007 case '}':
2008 goto ESCAPE_RIGHT_BRACE;
2009 case '\n':
2010 break;
2011 case '[':
2012 if (!compatible_flag) {
2013 nm = read_long_escape_name();
2014 if (nm.is_null() || nm.is_empty())
2015 break;
2016 type = TOKEN_SPECIAL;
2017 return;
2019 goto handle_normal_char;
2020 default:
2021 if (cc != escape_char && cc != '.')
2022 warning(WARN_ESCAPE, "escape character ignored before %1",
2023 input_char_description(cc));
2024 goto handle_normal_char;
2030 int token::operator==(const token &t)
2032 if (type != t.type)
2033 return 0;
2034 switch(type) {
2035 case TOKEN_CHAR:
2036 return c == t.c;
2037 case TOKEN_SPECIAL:
2038 return nm == t.nm;
2039 case TOKEN_NUMBERED_CHAR:
2040 return val == t.val;
2041 default:
2042 return 1;
2046 int token::operator!=(const token &t)
2048 return !(*this == t);
2051 // is token a suitable delimiter (like ')?
2053 int token::delimiter(int err)
2055 switch(type) {
2056 case TOKEN_CHAR:
2057 switch(c) {
2058 case '0':
2059 case '1':
2060 case '2':
2061 case '3':
2062 case '4':
2063 case '5':
2064 case '6':
2065 case '7':
2066 case '8':
2067 case '9':
2068 case '+':
2069 case '-':
2070 case '/':
2071 case '*':
2072 case '%':
2073 case '<':
2074 case '>':
2075 case '=':
2076 case '&':
2077 case ':':
2078 case '(':
2079 case ')':
2080 case '.':
2081 if (err)
2082 error("cannot use character `%1' as a starting delimiter", char(c));
2083 return 0;
2084 default:
2085 return 1;
2087 case TOKEN_NODE:
2088 case TOKEN_SPACE:
2089 case TOKEN_STRETCHABLE_SPACE:
2090 case TOKEN_TAB:
2091 case TOKEN_NEWLINE:
2092 if (err)
2093 error("cannot use %1 as a starting delimiter", description());
2094 return 0;
2095 default:
2096 return 1;
2100 const char *token::description()
2102 static char buf[4];
2103 switch (type) {
2104 case TOKEN_BACKSPACE:
2105 return "a backspace character";
2106 case TOKEN_CHAR:
2107 buf[0] = '`';
2108 buf[1] = c;
2109 buf[2] = '\'';
2110 buf[3] = '\0';
2111 return buf;
2112 case TOKEN_DUMMY:
2113 return "`\\&'";
2114 case TOKEN_ESCAPE:
2115 return "`\\e'";
2116 case TOKEN_HYPHEN_INDICATOR:
2117 return "`\\%'";
2118 case TOKEN_INTERRUPT:
2119 return "`\\c'";
2120 case TOKEN_ITALIC_CORRECTION:
2121 return "`\\/'";
2122 case TOKEN_LEADER:
2123 return "a leader character";
2124 case TOKEN_LEFT_BRACE:
2125 return "`\\{'";
2126 case TOKEN_MARK_INPUT:
2127 return "`\\k'";
2128 case TOKEN_NEWLINE:
2129 return "newline";
2130 case TOKEN_NODE:
2131 return "a node";
2132 case TOKEN_NUMBERED_CHAR:
2133 return "`\\N'";
2134 case TOKEN_RIGHT_BRACE:
2135 return "`\\}'";
2136 case TOKEN_SPACE:
2137 return "a space";
2138 case TOKEN_SPECIAL:
2139 return "a special character";
2140 case TOKEN_SPREAD:
2141 return "`\\p'";
2142 case TOKEN_STRETCHABLE_SPACE:
2143 return "`\\~'";
2144 case TOKEN_TAB:
2145 return "a tab character";
2146 case TOKEN_TRANSPARENT:
2147 return "`\\!'";
2148 case TOKEN_TRANSPARENT_DUMMY:
2149 return "`\\)'";
2150 case TOKEN_ZERO_WIDTH_BREAK:
2151 return "`\\:'";
2152 case TOKEN_EOF:
2153 return "end of input";
2154 default:
2155 break;
2157 return "a magic token";
2160 void skip_line()
2162 while (!tok.newline())
2163 if (tok.eof())
2164 return;
2165 else
2166 tok.next();
2167 tok.next();
2170 void compatible()
2172 int n;
2173 if (has_arg() && get_integer(&n))
2174 compatible_flag = n != 0;
2175 else
2176 compatible_flag = 1;
2177 skip_line();
2180 static void empty_name_warning(int required)
2182 if (tok.newline() || tok.eof()) {
2183 if (required)
2184 warning(WARN_MISSING, "missing name");
2186 else if (tok.right_brace() || tok.tab()) {
2187 const char *start = tok.description();
2188 do {
2189 tok.next();
2190 } while (tok.space() || tok.right_brace() || tok.tab());
2191 if (!tok.newline() && !tok.eof())
2192 error("%1 is not allowed before an argument", start);
2193 else if (required)
2194 warning(WARN_MISSING, "missing name");
2196 else if (required)
2197 error("name expected (got %1)", tok.description());
2198 else
2199 error("name expected (got %1): treated as missing", tok.description());
2202 static void non_empty_name_warning()
2204 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2205 && !tok.right_brace()
2206 // We don't want to give a warning for .el\{
2207 && !tok.left_brace())
2208 error("%1 is not allowed in a name", tok.description());
2211 symbol get_name(int required)
2213 if (compatible_flag) {
2214 char buf[3];
2215 tok.skip();
2216 if ((buf[0] = tok.ch()) != 0) {
2217 tok.next();
2218 if ((buf[1] = tok.ch()) != 0) {
2219 buf[2] = 0;
2220 tok.make_space();
2222 else
2223 non_empty_name_warning();
2224 return symbol(buf);
2226 else {
2227 empty_name_warning(required);
2228 return NULL_SYMBOL;
2231 else
2232 return get_long_name(required);
2235 symbol get_long_name(int required)
2237 while (tok.space())
2238 tok.next();
2239 char abuf[ABUF_SIZE];
2240 char *buf = abuf;
2241 int buf_size = ABUF_SIZE;
2242 int i = 0;
2243 for (;;) {
2244 if (i + 1 > buf_size) {
2245 if (buf == abuf) {
2246 buf = new char[ABUF_SIZE*2];
2247 memcpy(buf, abuf, buf_size);
2248 buf_size = ABUF_SIZE*2;
2250 else {
2251 char *old_buf = buf;
2252 buf = new char[buf_size*2];
2253 memcpy(buf, old_buf, buf_size);
2254 buf_size *= 2;
2255 a_delete old_buf;
2258 if ((buf[i] = tok.ch()) == 0)
2259 break;
2260 i++;
2261 tok.next();
2263 if (i == 0) {
2264 empty_name_warning(required);
2265 return NULL_SYMBOL;
2267 non_empty_name_warning();
2268 if (buf == abuf)
2269 return symbol(buf);
2270 else {
2271 symbol s(buf);
2272 a_delete buf;
2273 return s;
2277 void exit_troff()
2279 exit_started = 1;
2280 topdiv->set_last_page();
2281 if (!end_macro_name.is_null()) {
2282 spring_trap(end_macro_name);
2283 tok.next();
2284 process_input_stack();
2286 curenv->final_break();
2287 tok.next();
2288 process_input_stack();
2289 end_diversions();
2290 if (topdiv->get_page_length() > 0) {
2291 done_end_macro = 1;
2292 topdiv->set_ejecting();
2293 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2294 input_stack::push(make_temp_iterator((char *)buf));
2295 topdiv->space(topdiv->get_page_length(), 1);
2296 tok.next();
2297 process_input_stack();
2298 seen_last_page_ejector = 1; // should be set already
2299 topdiv->set_ejecting();
2300 push_page_ejector();
2301 topdiv->space(topdiv->get_page_length(), 1);
2302 tok.next();
2303 process_input_stack();
2305 // This will only happen if a trap-invoked macro starts a diversion,
2306 // or if vertical position traps have been disabled.
2307 cleanup_and_exit(0);
2310 // This implements .ex. The input stack must be cleared before calling
2311 // exit_troff().
2313 void exit_request()
2315 input_stack::clear();
2316 if (exit_started)
2317 tok.next();
2318 else
2319 exit_troff();
2322 void return_macro_request()
2324 input_stack::pop_macro();
2325 tok.next();
2328 void end_macro()
2330 end_macro_name = get_name();
2331 skip_line();
2334 void blank_line_macro()
2336 blank_line_macro_name = get_name();
2337 skip_line();
2340 static void trapping_blank_line()
2342 if (!blank_line_macro_name.is_null())
2343 spring_trap(blank_line_macro_name);
2344 else
2345 blank_line();
2348 void do_request()
2350 int old_compatible_flag = compatible_flag;
2351 compatible_flag = 0;
2352 symbol nm = get_name();
2353 if (nm.is_null())
2354 skip_line();
2355 else
2356 interpolate_macro(nm);
2357 compatible_flag = old_compatible_flag;
2360 inline int possibly_handle_first_page_transition()
2362 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2363 handle_first_page_transition();
2364 return 1;
2366 else
2367 return 0;
2370 static int transparent_translate(int cc)
2372 if (!invalid_input_char(cc)) {
2373 charinfo *ci = charset_table[cc];
2374 switch (ci->get_special_translation(1)) {
2375 case charinfo::TRANSLATE_SPACE:
2376 return ' ';
2377 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2378 return ESCAPE_TILDE;
2379 case charinfo::TRANSLATE_DUMMY:
2380 return ESCAPE_AMPERSAND;
2381 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2382 return ESCAPE_PERCENT;
2384 // This is really ugly.
2385 ci = ci->get_translation(1);
2386 if (ci) {
2387 int c = ci->get_ascii_code();
2388 if (c != '\0')
2389 return c;
2390 error("can't translate %1 to special character `%2'"
2391 " in transparent throughput",
2392 input_char_description(cc),
2393 ci->nm.contents());
2396 return cc;
2399 class int_stack {
2400 struct int_stack_element {
2401 int n;
2402 int_stack_element *next;
2403 } *top;
2404 public:
2405 int_stack();
2406 ~int_stack();
2407 void push(int);
2408 int is_empty();
2409 int pop();
2412 int_stack::int_stack()
2414 top = 0;
2417 int_stack::~int_stack()
2419 while (top != 0) {
2420 int_stack_element *temp = top;
2421 top = top->next;
2422 delete temp;
2426 int int_stack::is_empty()
2428 return top == 0;
2431 void int_stack::push(int n)
2433 int_stack_element *p = new int_stack_element;
2434 p->next = top;
2435 p->n = n;
2436 top = p;
2439 int int_stack::pop()
2441 assert(top != 0);
2442 int_stack_element *p = top;
2443 top = top->next;
2444 int n = p->n;
2445 delete p;
2446 return n;
2449 int node::reread(int *)
2451 return 0;
2454 int diverted_space_node::reread(int *bolp)
2456 if (curenv->get_fill())
2457 trapping_blank_line();
2458 else
2459 curdiv->space(n);
2460 *bolp = 1;
2461 return 1;
2464 int diverted_copy_file_node::reread(int *bolp)
2466 curdiv->copy_file(filename.contents());
2467 *bolp = 1;
2468 return 1;
2471 int word_space_node::reread(int *bolp)
2473 if (unformat) {
2474 for (width_list *w = orig_width; w; w = w->next)
2475 curenv->space(w->width, w->sentence_width);
2476 unformat = 0;
2477 return 1;
2479 return 0;
2482 int unbreakable_space_node::reread(int *)
2484 return 0;
2487 int hmotion_node::reread(int *bolp)
2489 if (unformat && was_tab) {
2490 curenv->handle_tab(0);
2491 unformat = 0;
2492 return 1;
2494 return 0;
2497 void process_input_stack()
2499 int_stack trap_bol_stack;
2500 int bol = 1;
2501 for (;;) {
2502 int suppress_next = 0;
2503 switch (tok.type) {
2504 case token::TOKEN_CHAR:
2506 unsigned char ch = tok.c;
2507 if (bol && !have_input
2508 && (ch == curenv->control_char
2509 || ch == curenv->no_break_control_char)) {
2510 break_flag = ch == curenv->control_char;
2511 // skip tabs as well as spaces here
2512 do {
2513 tok.next();
2514 } while (tok.white_space());
2515 symbol nm = get_name();
2516 if (nm.is_null())
2517 skip_line();
2518 else
2519 interpolate_macro(nm);
2520 suppress_next = 1;
2521 have_input = 0;
2523 else {
2524 if (possibly_handle_first_page_transition())
2526 else {
2527 for (;;) {
2528 curenv->add_char(charset_table[ch]);
2529 tok.next();
2530 if (tok.type != token::TOKEN_CHAR)
2531 break;
2532 ch = tok.c;
2534 suppress_next = 1;
2535 bol = 0;
2538 break;
2540 case token::TOKEN_TRANSPARENT:
2542 if (bol) {
2543 if (possibly_handle_first_page_transition())
2545 else {
2546 int cc;
2547 do {
2548 node *n;
2549 cc = get_copy(&n);
2550 if (cc != EOF)
2551 if (cc != '\0')
2552 curdiv->transparent_output(transparent_translate(cc));
2553 else
2554 curdiv->transparent_output(n);
2555 } while (cc != '\n' && cc != EOF);
2556 if (cc == EOF)
2557 curdiv->transparent_output('\n');
2560 break;
2562 case token::TOKEN_NEWLINE:
2564 if (bol && !have_input
2565 && !curenv->get_prev_line_interrupted())
2566 trapping_blank_line();
2567 else {
2568 curenv->newline();
2569 bol = 1;
2570 have_input = 0;
2572 break;
2574 case token::TOKEN_REQUEST:
2576 int request_code = tok.c;
2577 tok.next();
2578 switch (request_code) {
2579 case TITLE_REQUEST:
2580 title();
2581 break;
2582 case COPY_FILE_REQUEST:
2583 copy_file();
2584 break;
2585 case TRANSPARENT_FILE_REQUEST:
2586 transparent_file();
2587 break;
2588 #ifdef COLUMN
2589 case VJUSTIFY_REQUEST:
2590 vjustify();
2591 break;
2592 #endif /* COLUMN */
2593 default:
2594 assert(0);
2595 break;
2597 suppress_next = 1;
2598 have_input = 0;
2599 break;
2601 case token::TOKEN_SPACE:
2603 if (possibly_handle_first_page_transition())
2605 else if (bol && !curenv->get_prev_line_interrupted()) {
2606 int nspaces = 0;
2607 // save space_width now so that it isn't changed by \f or \s
2608 // which we wouldn't notice here
2609 hunits space_width = curenv->get_space_width();
2610 do {
2611 nspaces += tok.nspaces();
2612 tok.next();
2613 } while (tok.space());
2614 if (tok.newline())
2615 trapping_blank_line();
2616 else {
2617 push_token(tok);
2618 curenv->do_break();
2619 curenv->add_node(new hmotion_node(space_width * nspaces));
2620 bol = 0;
2623 else {
2624 curenv->space();
2625 bol = 0;
2627 break;
2629 case token::TOKEN_EOF:
2630 return;
2631 case token::TOKEN_NODE:
2633 if (possibly_handle_first_page_transition())
2635 else if (tok.nd->reread(&bol)) {
2636 delete tok.nd;
2637 tok.nd = 0;
2639 else {
2640 curenv->add_node(tok.nd);
2641 tok.nd = 0;
2642 bol = 0;
2643 curenv->possibly_break_line(1);
2645 break;
2647 case token::TOKEN_PAGE_EJECTOR:
2649 continue_page_eject();
2650 // I think we just want to preserve bol.
2651 // bol = 1;
2652 break;
2654 case token::TOKEN_BEGIN_TRAP:
2656 trap_bol_stack.push(bol);
2657 bol = 1;
2658 have_input = 0;
2659 break;
2661 case token::TOKEN_END_TRAP:
2663 if (trap_bol_stack.is_empty())
2664 error("spurious end trap token detected!");
2665 else
2666 bol = trap_bol_stack.pop();
2668 /* I'm not totally happy about this. But I can't think of any other
2669 way to do it. Doing an output_pending_lines() whenever a
2670 TOKEN_END_TRAP is detected doesn't work: for example,
2672 .wh -1i x
2673 .de x
2676 .wh -.5i y
2677 .de y
2678 .tl ''-%-''
2681 .ll .5i
2682 .sp |\n(.pu-1i-.5v
2683 a\%very\%very\%long\%word
2685 will print all but the first lines from the word immediately
2686 after the footer, rather than on the next page. */
2688 if (trap_bol_stack.is_empty())
2689 curenv->output_pending_lines();
2690 break;
2692 default:
2694 bol = 0;
2695 tok.process();
2696 break;
2699 if (!suppress_next)
2700 tok.next();
2701 trap_sprung_flag = 0;
2705 #ifdef WIDOW_CONTROL
2707 void flush_pending_lines()
2709 while (!tok.newline() && !tok.eof())
2710 tok.next();
2711 curenv->output_pending_lines();
2712 tok.next();
2715 #endif /* WIDOW_CONTROL */
2717 request_or_macro::request_or_macro()
2721 macro *request_or_macro::to_macro()
2723 return 0;
2726 request::request(REQUEST_FUNCP pp) : p(pp)
2730 void request::invoke(symbol)
2732 (*p)();
2735 struct char_block {
2736 enum { SIZE = 128 };
2737 unsigned char s[SIZE];
2738 char_block *next;
2739 char_block();
2742 char_block::char_block()
2743 : next(0)
2747 class char_list {
2748 public:
2749 char_list();
2750 ~char_list();
2751 void append(unsigned char);
2752 int length();
2753 private:
2754 unsigned char *ptr;
2755 int len;
2756 char_block *head;
2757 char_block *tail;
2758 friend class macro_header;
2759 friend class string_iterator;
2762 char_list::char_list()
2763 : ptr(0), len(0), head(0), tail(0)
2767 char_list::~char_list()
2769 while (head != 0) {
2770 char_block *tem = head;
2771 head = head->next;
2772 delete tem;
2776 int char_list::length()
2778 return len;
2781 void char_list::append(unsigned char c)
2783 if (tail == 0) {
2784 head = tail = new char_block;
2785 ptr = tail->s;
2787 else {
2788 if (ptr >= tail->s + char_block::SIZE) {
2789 tail->next = new char_block;
2790 tail = tail->next;
2791 ptr = tail->s;
2794 *ptr++ = c;
2795 len++;
2798 class node_list {
2799 node *head;
2800 node *tail;
2801 public:
2802 node_list();
2803 ~node_list();
2804 void append(node *);
2805 int length();
2806 node *extract();
2808 friend class macro_header;
2809 friend class string_iterator;
2812 void node_list::append(node *n)
2814 if (head == 0) {
2815 n->next = 0;
2816 head = tail = n;
2818 else {
2819 n->next = 0;
2820 tail = tail->next = n;
2824 int node_list::length()
2826 int total = 0;
2827 for (node *n = head; n != 0; n = n->next)
2828 ++total;
2829 return total;
2832 node_list::node_list()
2834 head = tail = 0;
2837 node *node_list::extract()
2839 node *temp = head;
2840 head = tail = 0;
2841 return temp;
2844 node_list::~node_list()
2846 delete_node_list(head);
2849 struct macro_header {
2850 public:
2851 int count;
2852 char_list cl;
2853 node_list nl;
2854 macro_header() { count = 1; }
2855 macro_header *copy(int);
2858 macro::~macro()
2860 if (p != 0 && --(p->count) <= 0)
2861 delete p;
2864 macro::macro()
2866 if (!input_stack::get_location(1, &filename, &lineno)) {
2867 filename = 0;
2868 lineno = 0;
2870 length = 0;
2871 p = 0;
2874 macro::macro(const macro &m)
2875 : p(m.p), filename(m.filename), lineno(m.lineno), length(m.length)
2877 if (p != 0)
2878 p->count++;
2881 macro &macro::operator=(const macro &m)
2883 // don't assign object
2884 if (m.p != 0)
2885 m.p->count++;
2886 if (p != 0 && --(p->count) <= 0)
2887 delete p;
2888 p = m.p;
2889 filename = m.filename;
2890 lineno = m.lineno;
2891 length = m.length;
2892 return *this;
2895 void macro::append(unsigned char c)
2897 assert(c != 0);
2898 if (p == 0)
2899 p = new macro_header;
2900 if (p->cl.length() != length) {
2901 macro_header *tem = p->copy(length);
2902 if (--(p->count) <= 0)
2903 delete p;
2904 p = tem;
2906 p->cl.append(c);
2907 ++length;
2910 void macro::append_str(const char *s)
2912 int i = 0;
2914 if (s) {
2915 while (s[i] != (char)0) {
2916 append(s[i]);
2917 i++;
2922 void macro::append(node *n)
2924 assert(n != 0);
2925 if (p == 0)
2926 p = new macro_header;
2927 if (p->cl.length() != length) {
2928 macro_header *tem = p->copy(length);
2929 if (--(p->count) <= 0)
2930 delete p;
2931 p = tem;
2933 p->cl.append(0);
2934 p->nl.append(n);
2935 ++length;
2938 void macro::append_unsigned(unsigned int i)
2940 unsigned int j = i / 10;
2941 if (j != 0)
2942 append_unsigned(j);
2943 append(((unsigned char)(((int)'0') + i % 10)));
2946 void macro::append_int(int i)
2948 if (i < 0) {
2949 append('-');
2950 i = -i;
2952 append_unsigned((unsigned int)i);
2955 void macro::print_size()
2957 errprint("%1", length);
2960 // make a copy of the first n bytes
2962 macro_header *macro_header::copy(int n)
2964 macro_header *p = new macro_header;
2965 char_block *bp = cl.head;
2966 unsigned char *ptr = bp->s;
2967 node *nd = nl.head;
2968 while (--n >= 0) {
2969 if (ptr >= bp->s + char_block::SIZE) {
2970 bp = bp->next;
2971 ptr = bp->s;
2973 int c = *ptr++;
2974 p->cl.append(c);
2975 if (c == 0) {
2976 p->nl.append(nd->copy());
2977 nd = nd->next;
2980 return p;
2983 void print_macros()
2985 object_dictionary_iterator iter(request_dictionary);
2986 request_or_macro *rm;
2987 symbol s;
2988 while (iter.get(&s, (object **)&rm)) {
2989 assert(!s.is_null());
2990 macro *m = rm->to_macro();
2991 if (m) {
2992 errprint("%1\t", s.contents());
2993 m->print_size();
2994 errprint("\n");
2997 fflush(stderr);
2998 skip_line();
3001 class string_iterator : public input_iterator {
3002 macro mac;
3003 const char *how_invoked;
3004 int newline_flag;
3005 int suppress_newline_flag; // used by html
3006 int lineno;
3007 char_block *bp;
3008 int count; // of characters remaining
3009 node *nd;
3010 int saved_compatible_flag;
3011 protected:
3012 symbol nm;
3013 string_iterator();
3014 public:
3015 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
3016 int fill(node **);
3017 int peek();
3018 int get_location(int, const char **, int *);
3019 void backtrace();
3020 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3021 int get_compatible_flag() { return saved_compatible_flag; }
3024 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3025 : mac(m), how_invoked(p),
3026 newline_flag(0), suppress_newline_flag(0), lineno(1), nm(s)
3028 count = mac.length;
3029 if (count != 0) {
3030 bp = mac.p->cl.head;
3031 nd = mac.p->nl.head;
3032 ptr = eptr = bp->s;
3034 else {
3035 bp = 0;
3036 nd = 0;
3037 ptr = eptr = 0;
3041 string_iterator::string_iterator()
3043 bp = 0;
3044 nd = 0;
3045 ptr = eptr = 0;
3046 newline_flag = 0;
3047 suppress_newline_flag = 0;
3048 how_invoked = 0;
3049 lineno = 1;
3050 count = 0;
3053 int string_iterator::fill(node **np)
3055 if (newline_flag)
3056 lineno++;
3057 newline_flag = 0;
3058 if (count <= 0)
3059 return EOF;
3060 const unsigned char *p = eptr;
3061 if (p >= bp->s + char_block::SIZE) {
3062 bp = bp->next;
3063 p = bp->s;
3065 if (*p == '\0') {
3066 if (np)
3067 *np = nd->copy();
3068 nd = nd->next;
3069 eptr = ptr = p + 1;
3070 count--;
3071 return 0;
3073 const unsigned char *e = bp->s + char_block::SIZE;
3074 if (e - p > count)
3075 e = p + count;
3076 ptr = p;
3077 while (p < e) {
3078 unsigned char c = *p;
3079 if (c == '\n' || c == ESCAPE_NEWLINE) {
3080 newline_flag = 1;
3081 if (is_html && c == ESCAPE_NEWLINE)
3082 suppress_newline_flag = 1;
3083 p++;
3084 break;
3086 if (c == '\0')
3087 break;
3088 p++;
3090 eptr = p;
3091 count -= p - ptr;
3092 return *ptr++;
3095 int string_iterator::peek()
3097 if (count <= 0)
3098 return EOF;
3099 const unsigned char *p = eptr;
3100 if (count <= 0)
3101 return EOF;
3102 if (p >= bp->s + char_block::SIZE) {
3103 p = bp->next->s;
3105 return *p;
3108 int string_iterator::get_location(int allow_macro,
3109 const char **filep, int *linep)
3111 if (!allow_macro)
3112 return 0;
3113 if (mac.filename == 0)
3114 return 0;
3115 *filep = mac.filename;
3116 *linep = mac.lineno + lineno - 1;
3117 return 1;
3120 void string_iterator::backtrace()
3122 if (mac.filename) {
3123 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3124 if (how_invoked) {
3125 if (!nm.is_null())
3126 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3127 else
3128 errprint(": %1\n", how_invoked);
3130 else
3131 errprint("\n");
3135 class temp_iterator : public input_iterator {
3136 unsigned char *base;
3137 temp_iterator(const char *, int len);
3138 public:
3139 ~temp_iterator();
3140 friend input_iterator *make_temp_iterator(const char *);
3143 #ifdef __GNUG__
3144 inline
3145 #endif
3146 temp_iterator::temp_iterator(const char *s, int len)
3148 base = new unsigned char[len];
3149 memcpy(base, s, len);
3150 ptr = base;
3151 eptr = base + len;
3154 temp_iterator::~temp_iterator()
3156 a_delete base;
3159 class small_temp_iterator : public input_iterator {
3160 private:
3161 small_temp_iterator(const char *, int);
3162 ~small_temp_iterator();
3163 enum { BLOCK = 16 };
3164 static small_temp_iterator *free_list;
3165 void *operator new(size_t);
3166 void operator delete(void *);
3167 enum { SIZE = 12 };
3168 unsigned char buf[SIZE];
3169 friend input_iterator *make_temp_iterator(const char *);
3172 small_temp_iterator *small_temp_iterator::free_list = 0;
3174 void *small_temp_iterator::operator new(size_t n)
3176 assert(n == sizeof(small_temp_iterator));
3177 if (!free_list) {
3178 free_list =
3179 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3180 for (int i = 0; i < BLOCK - 1; i++)
3181 free_list[i].next = free_list + i + 1;
3182 free_list[BLOCK-1].next = 0;
3184 small_temp_iterator *p = free_list;
3185 free_list = (small_temp_iterator *)(free_list->next);
3186 p->next = 0;
3187 return p;
3190 #ifdef __GNUG__
3191 inline
3192 #endif
3193 void small_temp_iterator::operator delete(void *p)
3195 if (p) {
3196 ((small_temp_iterator *)p)->next = free_list;
3197 free_list = (small_temp_iterator *)p;
3201 small_temp_iterator::~small_temp_iterator()
3205 #ifdef __GNUG__
3206 inline
3207 #endif
3208 small_temp_iterator::small_temp_iterator(const char *s, int len)
3210 for (int i = 0; i < len; i++)
3211 buf[i] = s[i];
3212 ptr = buf;
3213 eptr = buf + len;
3216 input_iterator *make_temp_iterator(const char *s)
3218 if (s == 0)
3219 return new small_temp_iterator(s, 0);
3220 else {
3221 int n = strlen(s);
3222 if (n <= small_temp_iterator::SIZE)
3223 return new small_temp_iterator(s, n);
3224 else
3225 return new temp_iterator(s, n);
3229 // this is used when macros are interpolated using the .macro_name notation
3231 struct arg_list {
3232 macro mac;
3233 arg_list *next;
3234 arg_list(const macro &);
3235 ~arg_list();
3238 arg_list::arg_list(const macro &m) : mac(m), next(0)
3242 arg_list::~arg_list()
3246 class macro_iterator : public string_iterator {
3247 arg_list *args;
3248 int argc;
3249 public:
3250 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
3251 macro_iterator();
3252 ~macro_iterator();
3253 int has_args() { return 1; }
3254 input_iterator *get_arg(int i);
3255 int nargs() { return argc; }
3256 void add_arg(const macro &m);
3257 void shift(int n);
3258 int is_macro() { return 1; }
3261 input_iterator *macro_iterator::get_arg(int i)
3263 if (i == 0)
3264 return make_temp_iterator(nm.contents());
3265 if (i > 0 && i <= argc) {
3266 arg_list *p = args;
3267 for (int j = 1; j < i; j++) {
3268 assert(p != 0);
3269 p = p->next;
3271 return new string_iterator(p->mac);
3273 else
3274 return 0;
3277 void macro_iterator::add_arg(const macro &m)
3279 arg_list **p;
3280 for (p = &args; *p; p = &((*p)->next))
3282 *p = new arg_list(m);
3283 ++argc;
3286 void macro_iterator::shift(int n)
3288 while (n > 0 && argc > 0) {
3289 arg_list *tem = args;
3290 args = args->next;
3291 delete tem;
3292 --argc;
3293 --n;
3297 // This gets used by eg .if '\?xxx\?''.
3299 int operator==(const macro &m1, const macro &m2)
3301 if (m1.length != m2.length)
3302 return 0;
3303 string_iterator iter1(m1);
3304 string_iterator iter2(m2);
3305 int n = m1.length;
3306 while (--n >= 0) {
3307 node *nd1 = 0;
3308 int c1 = iter1.get(&nd1);
3309 assert(c1 != EOF);
3310 node *nd2 = 0;
3311 int c2 = iter2.get(&nd2);
3312 assert(c2 != EOF);
3313 if (c1 != c2) {
3314 if (c1 == 0)
3315 delete nd1;
3316 else if (c2 == 0)
3317 delete nd2;
3318 return 0;
3320 if (c1 == 0) {
3321 assert(nd1 != 0);
3322 assert(nd2 != 0);
3323 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3324 delete nd1;
3325 delete nd2;
3326 if (!are_same)
3327 return 0;
3330 return 1;
3333 static void interpolate_macro(symbol nm)
3335 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3336 if (p == 0) {
3337 int warned = 0;
3338 const char *s = nm.contents();
3339 if (strlen(s) > 2) {
3340 request_or_macro *r;
3341 char buf[3];
3342 buf[0] = s[0];
3343 buf[1] = s[1];
3344 buf[2] = '\0';
3345 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3346 if (r) {
3347 macro *m = r->to_macro();
3348 if (!m || !m->empty())
3349 warned = warning(WARN_SPACE,
3350 "`%1' not defined (probable missing space after `%2')",
3351 nm.contents(), buf);
3354 if (!warned) {
3355 warning(WARN_MAC, "`%1' not defined", nm.contents());
3356 p = new macro;
3357 request_dictionary.define(nm, p);
3360 if (p)
3361 p->invoke(nm);
3362 else {
3363 skip_line();
3364 return;
3368 static void decode_args(macro_iterator *mi)
3370 if (!tok.newline() && !tok.eof()) {
3371 node *n;
3372 int c = get_copy(&n);
3373 for (;;) {
3374 while (c == ' ')
3375 c = get_copy(&n);
3376 if (c == '\n' || c == EOF)
3377 break;
3378 macro arg;
3379 int quote_input_level = 0;
3380 int done_tab_warning = 0;
3381 if (c == '\"') {
3382 quote_input_level = input_stack::get_level();
3383 c = get_copy(&n);
3385 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3386 if (quote_input_level > 0 && c == '\"'
3387 && (compatible_flag
3388 || input_stack::get_level() == quote_input_level)) {
3389 c = get_copy(&n);
3390 if (c == '"') {
3391 arg.append(c);
3392 c = get_copy(&n);
3394 else
3395 break;
3397 else {
3398 if (c == 0)
3399 arg.append(n);
3400 else {
3401 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3402 warning(WARN_TAB, "tab character in unquoted macro argument");
3403 done_tab_warning = 1;
3405 arg.append(c);
3407 c = get_copy(&n);
3410 mi->add_arg(arg);
3415 void macro::invoke(symbol nm)
3417 macro_iterator *mi = new macro_iterator(nm, *this);
3418 decode_args(mi);
3419 input_stack::push(mi);
3420 tok.next();
3423 macro *macro::to_macro()
3425 return this;
3428 int macro::empty()
3430 return length == 0;
3433 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
3434 : string_iterator(m, how_invoked, s), args(0), argc(0)
3438 macro_iterator::macro_iterator() : args(0), argc(0)
3442 macro_iterator::~macro_iterator()
3444 while (args != 0) {
3445 arg_list *tem = args;
3446 args = args->next;
3447 delete tem;
3451 int trap_sprung_flag = 0;
3452 int postpone_traps_flag = 0;
3453 symbol postponed_trap;
3455 void spring_trap(symbol nm)
3457 assert(!nm.is_null());
3458 trap_sprung_flag = 1;
3459 if (postpone_traps_flag) {
3460 postponed_trap = nm;
3461 return;
3463 static char buf[2] = { BEGIN_TRAP, 0 };
3464 static char buf2[2] = { END_TRAP, '\0' };
3465 input_stack::push(make_temp_iterator(buf2));
3466 request_or_macro *p = lookup_request(nm);
3467 macro *m = p->to_macro();
3468 if (m)
3469 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
3470 else
3471 error("you can't invoke a request with a trap");
3472 input_stack::push(make_temp_iterator(buf));
3475 void postpone_traps()
3477 postpone_traps_flag = 1;
3480 int unpostpone_traps()
3482 postpone_traps_flag = 0;
3483 if (!postponed_trap.is_null()) {
3484 spring_trap(postponed_trap);
3485 postponed_trap = NULL_SYMBOL;
3486 return 1;
3488 else
3489 return 0;
3492 void read_request()
3494 macro_iterator *mi = new macro_iterator;
3495 int reading_from_terminal = isatty(fileno(stdin));
3496 int had_prompt = 0;
3497 if (!tok.newline() && !tok.eof()) {
3498 int c = get_copy(0);
3499 while (c == ' ')
3500 c = get_copy(0);
3501 while (c != EOF && c != '\n' && c != ' ') {
3502 if (!invalid_input_char(c)) {
3503 if (reading_from_terminal)
3504 fputc(c, stderr);
3505 had_prompt = 1;
3507 c = get_copy(0);
3509 if (c == ' ') {
3510 tok.make_space();
3511 decode_args(mi);
3514 if (reading_from_terminal) {
3515 fputc(had_prompt ? ':' : '\a', stderr);
3516 fflush(stderr);
3518 input_stack::push(mi);
3519 macro mac;
3520 int nl = 0;
3521 int c;
3522 while ((c = getchar()) != EOF) {
3523 if (invalid_input_char(c))
3524 warning(WARN_INPUT, "invalid input character code %1", int(c));
3525 else {
3526 if (c == '\n') {
3527 if (nl)
3528 break;
3529 else
3530 nl = 1;
3532 else
3533 nl = 0;
3534 mac.append(c);
3537 if (reading_from_terminal)
3538 clearerr(stdin);
3539 input_stack::push(new string_iterator(mac));
3540 tok.next();
3543 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3544 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT, CALLING_DISABLE_COMP };
3546 void do_define_string(define_mode mode, calling_mode calling)
3548 symbol nm;
3549 node *n;
3550 int c;
3551 nm = get_name(1);
3552 if (nm.is_null()) {
3553 skip_line();
3554 return;
3556 if (tok.newline())
3557 c = '\n';
3558 else if (tok.tab())
3559 c = '\t';
3560 else if (!tok.space()) {
3561 error("bad string definition");
3562 skip_line();
3563 return;
3565 else
3566 c = get_copy(&n);
3567 while (c == ' ')
3568 c = get_copy(&n);
3569 if (c == '"')
3570 c = get_copy(&n);
3571 macro mac;
3572 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3573 macro *mm = rm ? rm->to_macro() : 0;
3574 if (mode == DEFINE_APPEND && mm)
3575 mac = *mm;
3576 if (calling == CALLING_DISABLE_COMP)
3577 mac.append(COMPATIBLE_SAVE);
3578 while (c != '\n' && c != EOF) {
3579 if (c == 0)
3580 mac.append(n);
3581 else
3582 mac.append((unsigned char)c);
3583 c = get_copy(&n);
3585 if (!mm) {
3586 mm = new macro;
3587 request_dictionary.define(nm, mm);
3589 if (calling == CALLING_DISABLE_COMP)
3590 mac.append(COMPATIBLE_RESTORE);
3591 *mm = mac;
3592 tok.next();
3595 void define_string()
3597 do_define_string(DEFINE_NORMAL, CALLING_NORMAL);
3600 void define_nocomp_string()
3602 do_define_string(DEFINE_NORMAL, CALLING_DISABLE_COMP);
3605 void append_string()
3607 do_define_string(DEFINE_APPEND, CALLING_NORMAL);
3610 void append_nocomp_string()
3612 do_define_string(DEFINE_APPEND, CALLING_DISABLE_COMP);
3615 void do_define_character(int fallback)
3617 node *n;
3618 int c;
3619 tok.skip();
3620 charinfo *ci = tok.get_char(1);
3621 if (ci == 0) {
3622 skip_line();
3623 return;
3625 tok.next();
3626 if (tok.newline())
3627 c = '\n';
3628 else if (tok.tab())
3629 c = '\t';
3630 else if (!tok.space()) {
3631 error("bad character definition");
3632 skip_line();
3633 return;
3635 else
3636 c = get_copy(&n);
3637 while (c == ' ' || c == '\t')
3638 c = get_copy(&n);
3639 if (c == '"')
3640 c = get_copy(&n);
3641 macro *m = new macro;
3642 while (c != '\n' && c != EOF) {
3643 if (c == 0)
3644 m->append(n);
3645 else
3646 m->append((unsigned char)c);
3647 c = get_copy(&n);
3649 m = ci->set_macro(m, fallback);
3650 if (m)
3651 delete m;
3652 tok.next();
3655 void define_character()
3657 do_define_character(0);
3660 void define_fallback_character()
3662 do_define_character(1);
3665 static void remove_character()
3667 tok.skip();
3668 while (!tok.newline() && !tok.eof()) {
3669 if (!tok.space() && !tok.tab()) {
3670 charinfo *ci = tok.get_char(1);
3671 if (!ci)
3672 break;
3673 macro *m = ci->set_macro(0);
3674 if (m)
3675 delete m;
3677 tok.next();
3679 skip_line();
3682 static void interpolate_string(symbol nm)
3684 request_or_macro *p = lookup_request(nm);
3685 macro *m = p->to_macro();
3686 if (!m)
3687 error("you can only invoke a string using \\*");
3688 else {
3689 string_iterator *si = new string_iterator(*m, "string", nm);
3690 input_stack::push(si);
3694 /* This class is used for the implementation of \$@. It is used for
3695 each of the closing double quotes. It artificially increases the
3696 input level by 2, so that the closing double quote will appear to have
3697 the same input level as the opening quote. */
3699 class end_quote_iterator : public input_iterator {
3700 unsigned char buf[1];
3701 public:
3702 end_quote_iterator();
3703 ~end_quote_iterator() { }
3704 int internal_level() { return 2; }
3707 end_quote_iterator::end_quote_iterator()
3709 buf[0] = '"';
3710 ptr = buf;
3711 eptr = buf + 1;
3714 static void interpolate_arg(symbol nm)
3716 const char *s = nm.contents();
3717 if (!s || *s == '\0')
3718 copy_mode_error("missing argument name");
3719 else if (s[1] == 0 && csdigit(s[0]))
3720 input_stack::push(input_stack::get_arg(s[0] - '0'));
3721 else if (s[0] == '*' && s[1] == '\0') {
3722 for (int i = input_stack::nargs(); i > 0; i--) {
3723 input_stack::push(input_stack::get_arg(i));
3724 if (i != 1)
3725 input_stack::push(make_temp_iterator(" "));
3728 else if (s[0] == '@' && s[1] == '\0') {
3729 for (int i = input_stack::nargs(); i > 0; i--) {
3730 input_stack::push(new end_quote_iterator);
3731 input_stack::push(input_stack::get_arg(i));
3732 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
3735 else {
3736 const char *p;
3737 for (p = s; *p && csdigit(*p); p++)
3739 if (*p)
3740 copy_mode_error("bad argument name `%1'", s);
3741 else
3742 input_stack::push(input_stack::get_arg(atoi(s)));
3746 void handle_first_page_transition()
3748 push_token(tok);
3749 topdiv->begin_page();
3752 // We push back a token by wrapping it up in a token_node, and
3753 // wrapping that up in a string_iterator.
3755 static void push_token(const token &t)
3757 macro m;
3758 m.append(new token_node(t));
3759 input_stack::push(new string_iterator(m));
3762 void push_page_ejector()
3764 static char buf[2] = { PAGE_EJECTOR, '\0' };
3765 input_stack::push(make_temp_iterator(buf));
3768 void handle_initial_request(unsigned char code)
3770 char buf[2];
3771 buf[0] = code;
3772 buf[1] = '\0';
3773 macro mac;
3774 mac.append(new token_node(tok));
3775 input_stack::push(new string_iterator(mac));
3776 input_stack::push(make_temp_iterator(buf));
3777 topdiv->begin_page();
3778 tok.next();
3781 void handle_initial_title()
3783 handle_initial_request(TITLE_REQUEST);
3786 // this should be local to define_macro, but cfront 1.2 doesn't support that
3787 static symbol dot_symbol(".");
3789 void do_define_macro(define_mode mode, calling_mode calling)
3791 symbol nm, term;
3792 if (calling == CALLING_INDIRECT) {
3793 symbol temp1 = get_name(1);
3794 if (temp1.is_null()) {
3795 skip_line();
3796 return;
3798 symbol temp2 = get_name();
3799 input_stack::push(make_temp_iterator("\n"));
3800 if (!temp2.is_null()) {
3801 interpolate_string(temp2);
3802 input_stack::push(make_temp_iterator(" "));
3804 interpolate_string(temp1);
3805 input_stack::push(make_temp_iterator(" "));
3806 tok.next();
3808 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3809 nm = get_name(1);
3810 if (nm.is_null()) {
3811 skip_line();
3812 return;
3815 term = get_name(); // the request that terminates the definition
3816 if (term.is_null())
3817 term = dot_symbol;
3818 while (!tok.newline() && !tok.eof())
3819 tok.next();
3820 const char *start_filename;
3821 int start_lineno;
3822 int have_start_location = input_stack::get_location(0, &start_filename,
3823 &start_lineno);
3824 node *n;
3825 // doing this here makes the line numbers come out right
3826 int c = get_copy(&n, 1);
3827 macro mac;
3828 macro *mm = 0;
3829 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3830 request_or_macro *rm =
3831 (request_or_macro *)request_dictionary.lookup(nm);
3832 if (rm)
3833 mm = rm->to_macro();
3834 if (mm && mode == DEFINE_APPEND)
3835 mac = *mm;
3837 int bol = 1;
3838 if (calling == CALLING_DISABLE_COMP)
3839 mac.append(COMPATIBLE_SAVE);
3840 for (;;) {
3841 while (c == ESCAPE_NEWLINE) {
3842 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
3843 mac.append(c);
3844 c = get_copy(&n, 1);
3846 if (bol && c == '.') {
3847 const char *s = term.contents();
3848 int d = 0;
3849 // see if it matches term
3850 int i = 0;
3851 if (s[0] != 0) {
3852 while ((d = get_copy(&n)) == ' ' || d == '\t')
3854 if ((unsigned char)s[0] == d) {
3855 for (i = 1; s[i] != 0; i++) {
3856 d = get_copy(&n);
3857 if ((unsigned char)s[i] != d)
3858 break;
3862 if (s[i] == 0
3863 && ((i == 2 && compatible_flag)
3864 || (d = get_copy(&n)) == ' '
3865 || d == '\n')) { // we found it
3866 if (d == '\n')
3867 tok.make_newline();
3868 else
3869 tok.make_space();
3870 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3871 if (!mm) {
3872 mm = new macro;
3873 request_dictionary.define(nm, mm);
3875 if (calling == CALLING_DISABLE_COMP)
3876 mac.append(COMPATIBLE_RESTORE);
3877 *mm = mac;
3879 if (term != dot_symbol) {
3880 ignoring = 0;
3881 interpolate_macro(term);
3883 else
3884 skip_line();
3885 return;
3887 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3888 mac.append(c);
3889 for (int j = 0; j < i; j++)
3890 mac.append(s[j]);
3892 c = d;
3894 if (c == EOF) {
3895 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3896 if (have_start_location)
3897 error_with_file_and_line(start_filename, start_lineno,
3898 "end of file while defining macro `%1'",
3899 nm.contents());
3900 else
3901 error("end of file while defining macro `%1'", nm.contents());
3903 else {
3904 if (have_start_location)
3905 error_with_file_and_line(start_filename, start_lineno,
3906 "end of file while ignoring input lines");
3907 else
3908 error("end of file while ignoring input lines");
3910 tok.next();
3911 return;
3913 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3914 if (c == 0)
3915 mac.append(n);
3916 else
3917 mac.append(c);
3919 bol = (c == '\n');
3920 c = get_copy(&n, 1);
3924 void define_macro()
3926 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL);
3929 void define_nocomp_macro()
3931 do_define_macro(DEFINE_NORMAL, CALLING_DISABLE_COMP);
3934 void define_indirect_macro()
3936 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT);
3939 void append_macro()
3941 do_define_macro(DEFINE_APPEND, CALLING_NORMAL);
3944 void append_indirect_macro()
3946 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT);
3949 void append_nocomp_macro()
3951 do_define_macro(DEFINE_APPEND, CALLING_DISABLE_COMP);
3954 void ignore()
3956 ignoring = 1;
3957 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL);
3958 ignoring = 0;
3961 void remove_macro()
3963 for (;;) {
3964 symbol s = get_name();
3965 if (s.is_null())
3966 break;
3967 request_dictionary.remove(s);
3969 skip_line();
3972 void rename_macro()
3974 symbol s1 = get_name(1);
3975 if (!s1.is_null()) {
3976 symbol s2 = get_name(1);
3977 if (!s2.is_null())
3978 request_dictionary.rename(s1, s2);
3980 skip_line();
3983 void alias_macro()
3985 symbol s1 = get_name(1);
3986 if (!s1.is_null()) {
3987 symbol s2 = get_name(1);
3988 if (!s2.is_null()) {
3989 if (!request_dictionary.alias(s1, s2))
3990 warning(WARN_MAC, "`%1' not defined", s2.contents());
3993 skip_line();
3996 void chop_macro()
3998 symbol s = get_name(1);
3999 if (!s.is_null()) {
4000 request_or_macro *p = lookup_request(s);
4001 macro *m = p->to_macro();
4002 if (!m)
4003 error("cannot chop request");
4004 else if (m->length == 0)
4005 error("cannot chop empty macro");
4006 else
4007 m->length -= 1;
4009 skip_line();
4012 void substring_macro()
4014 int start;
4015 symbol s = get_name(1);
4016 if (!s.is_null() && get_integer(&start)) {
4017 request_or_macro *p = lookup_request(s);
4018 macro *m = p->to_macro();
4019 if (!m)
4020 error("cannot substring request");
4021 else {
4022 if (start <= 0)
4023 start += m->length - 1;
4024 else
4025 start--;
4026 int end = 0;
4027 if (!has_arg() || get_integer(&end)) {
4028 if (end <= 0)
4029 end += m->length - 1;
4030 else
4031 end--;
4032 if (start > end) {
4033 int tem = start;
4034 start = end;
4035 end = tem;
4037 if (start >= m->length || end < 0) {
4038 m->length = 0;
4039 if (m->p) {
4040 if (--(m->p->count) <= 0)
4041 delete m->p;
4042 m->p = 0;
4044 skip_line();
4045 return;
4047 if (start < 0)
4048 start = 0;
4049 if (end >= m->length)
4050 end = m->length - 1;
4051 if (start == 0)
4052 m->length = end + 1;
4053 else {
4054 string_iterator iter(*m);
4055 int i;
4056 for (i = 0; i < start; i++)
4057 if (iter.get(0) == EOF)
4058 break;
4059 macro mac;
4060 for (; i <= end; i++) {
4061 node *nd;
4062 int c = iter.get(&nd);
4063 if (c == EOF)
4064 break;
4065 if (c == 0)
4066 mac.append(nd);
4067 else
4068 mac.append((unsigned char)c);
4070 *m = mac;
4075 skip_line();
4078 void length_macro()
4080 symbol ret;
4081 ret = get_name(1);
4082 if (ret.is_null()) {
4083 skip_line();
4084 return;
4086 int c;
4087 node *n;
4088 if (tok.newline())
4089 c = '\n';
4090 else if (tok.tab())
4091 c = '\t';
4092 else if (!tok.space()) {
4093 error("bad string definition");
4094 skip_line();
4095 return;
4097 else
4098 c = get_copy(&n);
4099 while (c == ' ')
4100 c = get_copy(&n);
4101 if (c == '"')
4102 c = get_copy(&n);
4103 int len = 0;
4104 while (c != '\n' && c != EOF) {
4105 ++len;
4106 c = get_copy(&n);
4108 tok.next();
4109 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4110 if (r)
4111 r->set_value(len);
4112 else
4113 set_number_reg(ret, len);
4116 void asciify_macro()
4118 symbol s = get_name(1);
4119 if (!s.is_null()) {
4120 request_or_macro *p = lookup_request(s);
4121 macro *m = p->to_macro();
4122 if (!m)
4123 error("cannot asciify request");
4124 else {
4125 macro am;
4126 string_iterator iter(*m);
4127 for (;;) {
4128 node *nd;
4129 int c = iter.get(&nd);
4130 if (c == EOF)
4131 break;
4132 if (c != 0)
4133 am.append(c);
4134 else
4135 nd->asciify(&am);
4137 *m = am;
4140 skip_line();
4143 void unformat_macro()
4145 symbol s = get_name(1);
4146 if (!s.is_null()) {
4147 request_or_macro *p = lookup_request(s);
4148 macro *m = p->to_macro();
4149 if (!m)
4150 error("cannot unformat request");
4151 else {
4152 macro am;
4153 string_iterator iter(*m);
4154 for (;;) {
4155 node *nd;
4156 int c = iter.get(&nd);
4157 if (c == EOF)
4158 break;
4159 if (c != 0)
4160 am.append(c);
4161 else {
4162 if (nd->set_unformat_flag())
4163 am.append(nd);
4166 *m = am;
4169 skip_line();
4172 static void interpolate_environment_variable(symbol nm)
4174 const char *s = getenv(nm.contents());
4175 if (s && *s)
4176 input_stack::push(make_temp_iterator(s));
4179 void interpolate_number_reg(symbol nm, int inc)
4181 reg *r = lookup_number_reg(nm);
4182 if (inc < 0)
4183 r->decrement();
4184 else if (inc > 0)
4185 r->increment();
4186 input_stack::push(make_temp_iterator(r->get_string()));
4189 static void interpolate_number_format(symbol nm)
4191 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4192 if (r)
4193 input_stack::push(make_temp_iterator(r->get_format()));
4196 static int get_delim_number(units *n, int si, int prev_value)
4198 token start;
4199 start.next();
4200 if (start.delimiter(1)) {
4201 tok.next();
4202 if (get_number(n, si, prev_value)) {
4203 if (start != tok)
4204 warning(WARN_DELIM, "closing delimiter does not match");
4205 return 1;
4208 return 0;
4211 static int get_delim_number(units *n, int si)
4213 token start;
4214 start.next();
4215 if (start.delimiter(1)) {
4216 tok.next();
4217 if (get_number(n, si)) {
4218 if (start != tok)
4219 warning(WARN_DELIM, "closing delimiter does not match");
4220 return 1;
4223 return 0;
4226 static int get_line_arg(units *n, int si, charinfo **cp)
4228 token start;
4229 start.next();
4230 int start_level = input_stack::get_level();
4231 if (!start.delimiter(1))
4232 return 0;
4233 tok.next();
4234 if (get_number(n, si)) {
4235 if (tok.dummy() || tok.transparent_dummy())
4236 tok.next();
4237 if (!(start == tok && input_stack::get_level() == start_level)) {
4238 *cp = tok.get_char(1);
4239 tok.next();
4241 if (!(start == tok && input_stack::get_level() == start_level))
4242 warning(WARN_DELIM, "closing delimiter does not match");
4243 return 1;
4245 return 0;
4248 static int read_size(int *x)
4250 tok.next();
4251 int c = tok.ch();
4252 int inc = 0;
4253 if (c == '-') {
4254 inc = -1;
4255 tok.next();
4256 c = tok.ch();
4258 else if (c == '+') {
4259 inc = 1;
4260 tok.next();
4261 c = tok.ch();
4263 int val;
4264 int bad = 0;
4265 if (c == '(') {
4266 tok.next();
4267 c = tok.ch();
4268 if (!inc) {
4269 // allow an increment either before or after the left parenthesis
4270 if (c == '-') {
4271 inc = -1;
4272 tok.next();
4273 c = tok.ch();
4275 else if (c == '+') {
4276 inc = 1;
4277 tok.next();
4278 c = tok.ch();
4281 if (!csdigit(c))
4282 bad = 1;
4283 else {
4284 val = c - '0';
4285 tok.next();
4286 c = tok.ch();
4287 if (!csdigit(c))
4288 bad = 1;
4289 else {
4290 val = val*10 + (c - '0');
4291 val *= sizescale;
4295 else if (csdigit(c)) {
4296 val = c - '0';
4297 if (!inc && c != '0' && c < '4') {
4298 tok.next();
4299 c = tok.ch();
4300 if (!csdigit(c))
4301 bad = 1;
4302 else
4303 val = val*10 + (c - '0');
4305 val *= sizescale;
4307 else if (!tok.delimiter(1))
4308 return 0;
4309 else {
4310 token start(tok);
4311 tok.next();
4312 if (!(inc
4313 ? get_number(&val, 'z')
4314 : get_number(&val, 'z', curenv->get_requested_point_size())))
4315 return 0;
4316 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
4317 if (start.ch() == '[')
4318 error("missing `]'");
4319 else
4320 error("missing closing delimiter");
4321 return 0;
4324 if (!bad) {
4325 switch (inc) {
4326 case 0:
4327 if (val == 0) {
4328 // special case -- \s[0] and \s0 means to revert to previous size
4329 *x = 0;
4330 return 1;
4332 *x = val;
4333 break;
4334 case 1:
4335 *x = curenv->get_requested_point_size() + val;
4336 break;
4337 case -1:
4338 *x = curenv->get_requested_point_size() - val;
4339 break;
4340 default:
4341 assert(0);
4343 if (*x <= 0) {
4344 warning(WARN_RANGE,
4345 "\\s request results in non-positive point size; set to 1");
4346 *x = 1;
4348 return 1;
4350 else {
4351 error("bad digit in point size");
4352 return 0;
4356 static symbol get_delim_name()
4358 token start;
4359 start.next();
4360 if (start.eof()) {
4361 error("end of input at start of delimited name");
4362 return NULL_SYMBOL;
4364 if (start.newline()) {
4365 error("can't delimit name with a newline");
4366 return NULL_SYMBOL;
4368 int start_level = input_stack::get_level();
4369 char abuf[ABUF_SIZE];
4370 char *buf = abuf;
4371 int buf_size = ABUF_SIZE;
4372 int i = 0;
4373 for (;;) {
4374 if (i + 1 > buf_size) {
4375 if (buf == abuf) {
4376 buf = new char[ABUF_SIZE*2];
4377 memcpy(buf, abuf, buf_size);
4378 buf_size = ABUF_SIZE*2;
4380 else {
4381 char *old_buf = buf;
4382 buf = new char[buf_size*2];
4383 memcpy(buf, old_buf, buf_size);
4384 buf_size *= 2;
4385 a_delete old_buf;
4388 tok.next();
4389 if (tok == start
4390 && (compatible_flag || input_stack::get_level() == start_level))
4391 break;
4392 if ((buf[i] = tok.ch()) == 0) {
4393 error("missing delimiter (got %1)", tok.description());
4394 if (buf != abuf)
4395 a_delete buf;
4396 return NULL_SYMBOL;
4398 i++;
4400 buf[i] = '\0';
4401 if (buf == abuf) {
4402 if (i == 0) {
4403 error("empty delimited name");
4404 return NULL_SYMBOL;
4406 else
4407 return symbol(buf);
4409 else {
4410 symbol s(buf);
4411 a_delete buf;
4412 return s;
4416 // Implement \R
4418 static void do_register()
4420 token start;
4421 start.next();
4422 if (!start.delimiter(1))
4423 return;
4424 tok.next();
4425 symbol nm = get_long_name(1);
4426 if (nm.is_null())
4427 return;
4428 while (tok.space())
4429 tok.next();
4430 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4431 int prev_value;
4432 if (!r || !r->get_value(&prev_value))
4433 prev_value = 0;
4434 int val;
4435 if (!get_number(&val, 'u', prev_value))
4436 return;
4437 if (start != tok)
4438 warning(WARN_DELIM, "closing delimiter does not match");
4439 if (r)
4440 r->set_value(val);
4441 else
4442 set_number_reg(nm, val);
4445 // this implements the \w escape sequence
4447 static void do_width()
4449 token start;
4450 start.next();
4451 int start_level = input_stack::get_level();
4452 environment env(curenv);
4453 environment *oldenv = curenv;
4454 curenv = &env;
4455 for (;;) {
4456 tok.next();
4457 if (tok.eof()) {
4458 warning(WARN_DELIM, "missing closing delimiter");
4459 break;
4461 if (tok.newline()) {
4462 warning(WARN_DELIM, "missing closing delimiter");
4463 input_stack::push(make_temp_iterator("\n"));
4464 break;
4466 if (tok == start
4467 && (compatible_flag || input_stack::get_level() == start_level))
4468 break;
4469 tok.process();
4471 env.wrap_up_tab();
4472 units x = env.get_input_line_position().to_units();
4473 input_stack::push(make_temp_iterator(i_to_a(x)));
4474 env.width_registers();
4475 curenv = oldenv;
4478 charinfo *page_character;
4480 void set_page_character()
4482 page_character = get_optional_char();
4483 skip_line();
4486 static const symbol percent_symbol("%");
4488 void read_title_parts(node **part, hunits *part_width)
4490 tok.skip();
4491 if (tok.newline() || tok.eof())
4492 return;
4493 token start(tok);
4494 int start_level = input_stack::get_level();
4495 tok.next();
4496 for (int i = 0; i < 3; i++) {
4497 while (!tok.newline() && !tok.eof()) {
4498 if (tok == start
4499 && (compatible_flag || input_stack::get_level() == start_level)) {
4500 tok.next();
4501 break;
4503 if (page_character != 0 && tok.get_char() == page_character)
4504 interpolate_number_reg(percent_symbol, 0);
4505 else
4506 tok.process();
4507 tok.next();
4509 curenv->wrap_up_tab();
4510 part_width[i] = curenv->get_input_line_position();
4511 part[i] = curenv->extract_output_line();
4513 while (!tok.newline() && !tok.eof())
4514 tok.next();
4517 class non_interpreted_node : public node {
4518 macro mac;
4519 public:
4520 non_interpreted_node(const macro &);
4521 int interpret(macro *);
4522 node *copy();
4523 int same(node *);
4524 const char *type();
4525 int force_tprint();
4528 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
4532 int non_interpreted_node::same(node *nd)
4534 return mac == ((non_interpreted_node *)nd)->mac;
4537 const char *non_interpreted_node::type()
4539 return "non_interpreted_node";
4542 int non_interpreted_node::force_tprint()
4544 return 0;
4547 node *non_interpreted_node::copy()
4549 return new non_interpreted_node(mac);
4552 int non_interpreted_node::interpret(macro *m)
4554 string_iterator si(mac);
4555 node *n;
4556 for (;;) {
4557 int c = si.get(&n);
4558 if (c == EOF)
4559 break;
4560 if (c == 0)
4561 m->append(n);
4562 else
4563 m->append(c);
4565 return 1;
4568 static node *do_non_interpreted()
4570 node *n;
4571 int c;
4572 macro mac;
4573 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
4574 if (c == 0)
4575 mac.append(n);
4576 else
4577 mac.append(c);
4578 if (c == EOF || c == '\n') {
4579 error("missing \\?");
4580 return 0;
4582 return new non_interpreted_node(mac);
4585 static void encode_char(macro *mac, char c)
4587 if (c == '\0') {
4588 if ((font::use_charnames_in_special) && tok.special()) {
4589 charinfo *ci = tok.get_char(1);
4590 const char *s = ci->get_symbol()->contents();
4591 if (s[0] != (char)0) {
4592 mac->append('\\');
4593 mac->append('(');
4594 int i = 0;
4595 while (s[i] != (char)0) {
4596 mac->append(s[i]);
4597 i++;
4599 mac->append('\\');
4600 mac->append(')');
4603 else if (!(tok.hyphen_indicator()
4604 || tok.dummy()
4605 || tok.transparent_dummy()
4606 || tok.zero_width_break()))
4607 error("%1 is invalid within \\X", tok.description());
4609 else {
4610 if ((font::use_charnames_in_special) && (c == '\\')) {
4612 * add escape escape sequence
4614 mac->append(c);
4616 mac->append(c);
4620 node *do_special()
4622 token start;
4623 start.next();
4624 int start_level = input_stack::get_level();
4625 macro mac;
4626 for (tok.next();
4627 tok != start || input_stack::get_level() != start_level;
4628 tok.next()) {
4629 if (tok.eof()) {
4630 warning(WARN_DELIM, "missing closing delimiter");
4631 return 0;
4633 if (tok.newline()) {
4634 input_stack::push(make_temp_iterator("\n"));
4635 warning(WARN_DELIM, "missing closing delimiter");
4636 break;
4638 unsigned char c;
4639 if (tok.space())
4640 c = ' ';
4641 else if (tok.tab())
4642 c = '\t';
4643 else if (tok.leader())
4644 c = '\001';
4645 else if (tok.backspace())
4646 c = '\b';
4647 else
4648 c = tok.ch();
4649 encode_char(&mac, c);
4651 return new special_node(mac);
4654 void output_request()
4656 if (!tok.newline() && !tok.eof()) {
4657 int c;
4658 for (;;) {
4659 c = get_copy(0);
4660 if (c == '"') {
4661 c = get_copy(0);
4662 break;
4664 if (c != ' ' && c != '\t')
4665 break;
4667 for (; c != '\n' && c != EOF; c = get_copy(0))
4668 topdiv->transparent_output(c);
4669 topdiv->transparent_output('\n');
4673 extern int image_no; // from node.cc
4675 static node *do_suppress(symbol nm)
4677 if (nm.is_null() || nm.is_empty()) {
4678 error("expecting an argument to escape \\O");
4679 return 0;
4681 const char *s = nm.contents();
4682 switch (*s) {
4683 case '0':
4684 if (begin_level == 0)
4685 // suppress generation of glyphs
4686 return new suppress_node(0, 0);
4687 break;
4688 case '1':
4689 if (begin_level == 0)
4690 // enable generation of glyphs
4691 return new suppress_node(1, 0);
4692 break;
4693 case '2':
4694 if (begin_level == 0)
4695 return new suppress_node(1, 1);
4696 break;
4697 case '3':
4698 begin_level++;
4699 #if 0
4700 // say goodbye to all this code ?
4701 if ((begin_level == 1) && (!is_html)) {
4702 if (curdiv == topdiv) {
4703 if (topdiv->before_first_page) {
4704 if (!break_flag) {
4705 if (!topdiv->no_space_mode)
4706 topdiv->begin_page();
4708 else if (topdiv->no_space_mode)
4709 topdiv->begin_page();
4710 else {
4711 push_page_ejector();
4712 topdiv->begin_page();
4713 topdiv->set_ejecting();
4716 else {
4717 push_page_ejector();
4718 if (break_flag)
4719 curenv->do_break();
4720 if (!topdiv->no_space_mode)
4721 topdiv->set_ejecting();
4725 // say goodbye to all this code?
4726 #endif
4727 break;
4728 case '4':
4729 begin_level--;
4730 break;
4731 case '5':
4733 s++; // move over '5'
4734 char position = *s;
4735 if (*s == (char)0) {
4736 error("missing position and filename in \\O");
4737 return 0;
4739 if (!(position == 'l'
4740 || position == 'r'
4741 || position == 'c'
4742 || position == 'i')) {
4743 error("l, r, c, or i position expected (got %1 in \\O)", position);
4744 return 0;
4746 s++; // onto image name
4747 if (s == (char *)0) {
4748 error("missing image name for \\O");
4749 return 0;
4751 image_no++;
4752 if (begin_level == 0)
4753 return new suppress_node(symbol(s), position, image_no);
4755 break;
4756 default:
4757 error("`%1' is an invalid argument to \\O", *s);
4759 return 0;
4762 void special_node::tprint(troff_output_file *out)
4764 tprint_start(out);
4765 string_iterator iter(mac);
4766 for (;;) {
4767 int c = iter.get(0);
4768 if (c == EOF)
4769 break;
4770 for (const char *s = ::asciify(c); *s; s++)
4771 tprint_char(out, *s);
4773 tprint_end(out);
4776 int get_file_line(const char **filename, int *lineno)
4778 return input_stack::get_location(0, filename, lineno);
4781 void line_file()
4783 int n;
4784 if (get_integer(&n)) {
4785 const char *filename = 0;
4786 if (has_arg()) {
4787 symbol s = get_long_name();
4788 filename = s.contents();
4790 (void)input_stack::set_location(filename, n-1);
4792 skip_line();
4795 static int nroff_mode = 0;
4797 static void nroff_request()
4799 nroff_mode = 1;
4800 skip_line();
4803 static void troff_request()
4805 nroff_mode = 0;
4806 skip_line();
4809 static void skip_alternative()
4811 int level = 0;
4812 // ensure that ``.if 0\{'' works as expected
4813 if (tok.left_brace())
4814 level++;
4815 int c;
4816 for (;;) {
4817 c = input_stack::get(0);
4818 if (c == EOF)
4819 break;
4820 if (c == ESCAPE_LEFT_BRACE)
4821 ++level;
4822 else if (c == ESCAPE_RIGHT_BRACE)
4823 --level;
4824 else if (c == escape_char && escape_char > 0)
4825 switch(input_stack::get(0)) {
4826 case '{':
4827 ++level;
4828 break;
4829 case '}':
4830 --level;
4831 break;
4832 case '"':
4833 while ((c = input_stack::get(0)) != '\n' && c != EOF)
4837 Note that the level can properly be < 0, eg
4839 .if 1 \{\
4840 .if 0 \{\
4841 .\}\}
4843 So don't give an error message in this case.
4845 if (level <= 0 && c == '\n')
4846 break;
4848 tok.next();
4851 static void begin_alternative()
4853 while (tok.space() || tok.left_brace())
4854 tok.next();
4857 void nop_request()
4859 while (tok.space())
4860 tok.next();
4863 static int_stack if_else_stack;
4865 int do_if_request()
4867 int invert = 0;
4868 while (tok.space())
4869 tok.next();
4870 while (tok.ch() == '!') {
4871 tok.next();
4872 invert = !invert;
4874 int result;
4875 unsigned char c = tok.ch();
4876 if (c == 't') {
4877 tok.next();
4878 result = !nroff_mode;
4880 else if (c == 'n') {
4881 tok.next();
4882 result = nroff_mode;
4884 else if (c == 'v') {
4885 tok.next();
4886 result = 0;
4888 else if (c == 'o') {
4889 result = (topdiv->get_page_number() & 1);
4890 tok.next();
4892 else if (c == 'e') {
4893 result = !(topdiv->get_page_number() & 1);
4894 tok.next();
4896 else if (c == 'd' || c == 'r') {
4897 tok.next();
4898 symbol nm = get_name(1);
4899 if (nm.is_null()) {
4900 skip_alternative();
4901 return 0;
4903 result = (c == 'd'
4904 ? request_dictionary.lookup(nm) != 0
4905 : number_reg_dictionary.lookup(nm) != 0);
4907 else if (c == 'm') {
4908 tok.next();
4909 symbol nm = get_long_name(1);
4910 if (nm.is_null()) {
4911 skip_alternative();
4912 return 0;
4914 result = (nm == default_symbol
4915 || color_dictionary.lookup(nm) != 0);
4917 else if (c == 'c') {
4918 tok.next();
4919 tok.skip();
4920 charinfo *ci = tok.get_char(1);
4921 if (ci == 0) {
4922 skip_alternative();
4923 return 0;
4925 result = character_exists(ci, curenv);
4926 tok.next();
4928 else if (tok.space())
4929 result = 0;
4930 else if (tok.delimiter()) {
4931 token delim = tok;
4932 int delim_level = input_stack::get_level();
4933 environment env1(curenv);
4934 environment env2(curenv);
4935 environment *oldenv = curenv;
4936 curenv = &env1;
4937 for (int i = 0; i < 2; i++) {
4938 for (;;) {
4939 tok.next();
4940 if (tok.newline() || tok.eof()) {
4941 warning(WARN_DELIM, "missing closing delimiter");
4942 tok.next();
4943 curenv = oldenv;
4944 return 0;
4946 if (tok == delim
4947 && (compatible_flag || input_stack::get_level() == delim_level))
4948 break;
4949 tok.process();
4951 curenv = &env2;
4953 node *n1 = env1.extract_output_line();
4954 node *n2 = env2.extract_output_line();
4955 result = same_node_list(n1, n2);
4956 delete_node_list(n1);
4957 delete_node_list(n2);
4958 curenv = oldenv;
4959 tok.next();
4961 else {
4962 units n;
4963 if (!get_number(&n, 'u')) {
4964 skip_alternative();
4965 return 0;
4967 else
4968 result = n > 0;
4970 if (invert)
4971 result = !result;
4972 if (result)
4973 begin_alternative();
4974 else
4975 skip_alternative();
4976 return result;
4979 void if_else_request()
4981 if_else_stack.push(do_if_request());
4984 void if_request()
4986 do_if_request();
4989 void else_request()
4991 if (if_else_stack.is_empty()) {
4992 warning(WARN_EL, "unbalanced .el request");
4993 skip_alternative();
4995 else {
4996 if (if_else_stack.pop())
4997 skip_alternative();
4998 else
4999 begin_alternative();
5003 static int while_depth = 0;
5004 static int while_break_flag = 0;
5006 void while_request()
5008 macro mac;
5009 int escaped = 0;
5010 int level = 0;
5011 mac.append(new token_node(tok));
5012 for (;;) {
5013 node *n;
5014 int c = input_stack::get(&n);
5015 if (c == EOF)
5016 break;
5017 if (c == 0) {
5018 escaped = 0;
5019 mac.append(n);
5021 else if (escaped) {
5022 if (c == '{')
5023 level += 1;
5024 else if (c == '}')
5025 level -= 1;
5026 escaped = 0;
5027 mac.append(c);
5029 else {
5030 if (c == ESCAPE_LEFT_BRACE)
5031 level += 1;
5032 else if (c == ESCAPE_RIGHT_BRACE)
5033 level -= 1;
5034 else if (c == escape_char)
5035 escaped = 1;
5036 mac.append(c);
5037 if (c == '\n' && level <= 0)
5038 break;
5041 if (level != 0)
5042 error("unbalanced \\{ \\}");
5043 else {
5044 while_depth++;
5045 input_stack::add_boundary();
5046 for (;;) {
5047 input_stack::push(new string_iterator(mac, "while loop"));
5048 tok.next();
5049 if (!do_if_request()) {
5050 while (input_stack::get(0) != EOF)
5052 break;
5054 process_input_stack();
5055 if (while_break_flag || input_stack::is_return_boundary()) {
5056 while_break_flag = 0;
5057 break;
5060 input_stack::remove_boundary();
5061 while_depth--;
5063 tok.next();
5066 void while_break_request()
5068 if (!while_depth) {
5069 error("no while loop");
5070 skip_line();
5072 else {
5073 while_break_flag = 1;
5074 while (input_stack::get(0) != EOF)
5076 tok.next();
5080 void while_continue_request()
5082 if (!while_depth) {
5083 error("no while loop");
5084 skip_line();
5086 else {
5087 while (input_stack::get(0) != EOF)
5089 tok.next();
5093 // .so
5095 void source()
5097 symbol nm = get_long_name(1);
5098 if (nm.is_null())
5099 skip_line();
5100 else {
5101 while (!tok.newline() && !tok.eof())
5102 tok.next();
5103 errno = 0;
5104 FILE *fp = fopen(nm.contents(), "r");
5105 if (fp)
5106 input_stack::push(new file_iterator(fp, nm.contents()));
5107 else
5108 error("can't open `%1': %2", nm.contents(), strerror(errno));
5109 tok.next();
5113 // like .so but use popen()
5115 void pipe_source()
5117 if (safer_flag) {
5118 error(".pso request not allowed in safer mode");
5119 skip_line();
5121 else {
5122 #ifdef POPEN_MISSING
5123 error("pipes not available on this system");
5124 skip_line();
5125 #else /* not POPEN_MISSING */
5126 if (tok.newline() || tok.eof())
5127 error("missing command");
5128 else {
5129 int c;
5130 while ((c = get_copy(0)) == ' ' || c == '\t')
5132 int buf_size = 24;
5133 char *buf = new char[buf_size];
5134 int buf_used = 0;
5135 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5136 const char *s = asciify(c);
5137 int slen = strlen(s);
5138 if (buf_used + slen + 1> buf_size) {
5139 char *old_buf = buf;
5140 int old_buf_size = buf_size;
5141 buf_size *= 2;
5142 buf = new char[buf_size];
5143 memcpy(buf, old_buf, old_buf_size);
5144 a_delete old_buf;
5146 strcpy(buf + buf_used, s);
5147 buf_used += slen;
5149 buf[buf_used] = '\0';
5150 errno = 0;
5151 FILE *fp = popen(buf, POPEN_RT);
5152 if (fp)
5153 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5154 else
5155 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5156 a_delete buf;
5158 tok.next();
5159 #endif /* not POPEN_MISSING */
5163 // .psbb
5165 static int llx_reg_contents = 0;
5166 static int lly_reg_contents = 0;
5167 static int urx_reg_contents = 0;
5168 static int ury_reg_contents = 0;
5170 struct bounding_box {
5171 int llx, lly, urx, ury;
5174 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5175 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5177 int parse_bounding_box(char *p, bounding_box *bb)
5179 if (sscanf(p, "%d %d %d %d",
5180 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5181 return 1;
5182 else {
5183 /* The Document Structuring Conventions say that the numbers
5184 should be integers. Unfortunately some broken applications
5185 get this wrong. */
5186 double x1, x2, x3, x4;
5187 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5188 bb->llx = (int)x1;
5189 bb->lly = (int)x2;
5190 bb->urx = (int)x3;
5191 bb->ury = (int)x4;
5192 return 1;
5194 else {
5195 for (; *p == ' ' || *p == '\t'; p++)
5197 if (strncmp(p, "(atend)", 7) == 0) {
5198 return 2;
5202 bb->llx = bb->lly = bb->urx = bb->ury = 0;
5203 return 0;
5206 // This version is taken from psrm.cc
5208 #define PS_LINE_MAX 255
5209 cset white_space("\n\r \t");
5211 int ps_get_line(char *buf, FILE *fp, const char* filename)
5213 int c = getc(fp);
5214 if (c == EOF) {
5215 buf[0] = '\0';
5216 return 0;
5218 int i = 0;
5219 int err = 0;
5220 while (c != '\r' && c != '\n' && c != EOF) {
5221 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
5222 error("invalid input character code %1 in `%2'", int(c), filename);
5223 else if (i < PS_LINE_MAX)
5224 buf[i++] = c;
5225 else if (!err) {
5226 err = 1;
5227 error("PostScript file `%1' is non-conforming "
5228 "because length of line exceeds 255", filename);
5230 c = getc(fp);
5232 buf[i++] = '\n';
5233 buf[i] = '\0';
5234 if (c == '\r') {
5235 c = getc(fp);
5236 if (c != EOF && c != '\n')
5237 ungetc(c, fp);
5239 return 1;
5242 inline void assign_registers(int llx, int lly, int urx, int ury)
5244 llx_reg_contents = llx;
5245 lly_reg_contents = lly;
5246 urx_reg_contents = urx;
5247 ury_reg_contents = ury;
5250 void do_ps_file(FILE *fp, const char* filename)
5252 bounding_box bb;
5253 int bb_at_end = 0;
5254 char buf[PS_LINE_MAX];
5255 llx_reg_contents = lly_reg_contents =
5256 urx_reg_contents = ury_reg_contents = 0;
5257 if (!ps_get_line(buf, fp, filename)) {
5258 error("`%1' is empty", filename);
5259 return;
5261 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
5262 error("`%1' is not conforming to the Document Structuring Conventions",
5263 filename);
5264 return;
5266 while (ps_get_line(buf, fp, filename) != 0) {
5267 if (buf[0] != '%' || buf[1] != '%'
5268 || strncmp(buf + 2, "EndComments", 11) == 0)
5269 break;
5270 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5271 int res = parse_bounding_box(buf + 14, &bb);
5272 if (res == 1) {
5273 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5274 return;
5276 else if (res == 2) {
5277 bb_at_end = 1;
5278 break;
5280 else {
5281 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5282 filename);
5283 return;
5287 if (bb_at_end) {
5288 long offset;
5289 int last_try = 0;
5290 /* in the trailer, the last BoundingBox comment is significant */
5291 for (offset = 512; !last_try; offset *= 2) {
5292 int had_trailer = 0;
5293 int got_bb = 0;
5294 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
5295 last_try = 1;
5296 if (fseek(fp, 0L, 0) == -1)
5297 break;
5299 while (ps_get_line(buf, fp, filename) != 0) {
5300 if (buf[0] == '%' && buf[1] == '%') {
5301 if (!had_trailer) {
5302 if (strncmp(buf + 2, "Trailer", 7) == 0)
5303 had_trailer = 1;
5305 else {
5306 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5307 int res = parse_bounding_box(buf + 14, &bb);
5308 if (res == 1)
5309 got_bb = 1;
5310 else if (res == 2) {
5311 error("`(atend)' not allowed in trailer of `%1'", filename);
5312 return;
5314 else {
5315 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5316 filename);
5317 return;
5323 if (got_bb) {
5324 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5325 return;
5329 error("%%%%BoundingBox comment not found in `%1'", filename);
5332 void ps_bbox_request()
5334 symbol nm = get_long_name(1);
5335 if (nm.is_null())
5336 skip_line();
5337 else {
5338 while (!tok.newline() && !tok.eof())
5339 tok.next();
5340 errno = 0;
5341 // PS files might contain non-printable characters, such as ^Z
5342 // and CRs not followed by an LF, so open them in binary mode.
5343 FILE *fp = fopen(nm.contents(), FOPEN_RB);
5344 if (fp) {
5345 do_ps_file(fp, nm.contents());
5346 fclose(fp);
5348 else
5349 error("can't open `%1': %2", nm.contents(), strerror(errno));
5350 tok.next();
5354 const char *asciify(int c)
5356 static char buf[3];
5357 buf[0] = escape_char == '\0' ? '\\' : escape_char;
5358 buf[1] = buf[2] = '\0';
5359 switch (c) {
5360 case ESCAPE_QUESTION:
5361 buf[1] = '?';
5362 break;
5363 case ESCAPE_AMPERSAND:
5364 buf[1] = '&';
5365 break;
5366 case ESCAPE_RIGHT_PARENTHESIS:
5367 buf[1] = ')';
5368 break;
5369 case ESCAPE_UNDERSCORE:
5370 buf[1] = '_';
5371 break;
5372 case ESCAPE_BAR:
5373 buf[1] = '|';
5374 break;
5375 case ESCAPE_CIRCUMFLEX:
5376 buf[1] = '^';
5377 break;
5378 case ESCAPE_LEFT_BRACE:
5379 buf[1] = '{';
5380 break;
5381 case ESCAPE_RIGHT_BRACE:
5382 buf[1] = '}';
5383 break;
5384 case ESCAPE_LEFT_QUOTE:
5385 buf[1] = '`';
5386 break;
5387 case ESCAPE_RIGHT_QUOTE:
5388 buf[1] = '\'';
5389 break;
5390 case ESCAPE_HYPHEN:
5391 buf[1] = '-';
5392 break;
5393 case ESCAPE_BANG:
5394 buf[1] = '!';
5395 break;
5396 case ESCAPE_c:
5397 buf[1] = 'c';
5398 break;
5399 case ESCAPE_e:
5400 buf[1] = 'e';
5401 break;
5402 case ESCAPE_E:
5403 buf[1] = 'E';
5404 break;
5405 case ESCAPE_PERCENT:
5406 buf[1] = '%';
5407 break;
5408 case ESCAPE_SPACE:
5409 buf[1] = ' ';
5410 break;
5411 case ESCAPE_TILDE:
5412 buf[1] = '~';
5413 break;
5414 case ESCAPE_COLON:
5415 buf[1] = ':';
5416 break;
5417 default:
5418 if (invalid_input_char(c))
5419 buf[0] = '\0';
5420 else
5421 buf[0] = c;
5422 break;
5424 return buf;
5427 const char *input_char_description(int c)
5429 switch (c) {
5430 case '\n':
5431 return "a newline character";
5432 case '\b':
5433 return "a backspace character";
5434 case '\001':
5435 return "a leader character";
5436 case '\t':
5437 return "a tab character";
5438 case ' ':
5439 return "a space character";
5440 case '\0':
5441 return "a node";
5443 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
5444 if (invalid_input_char(c)) {
5445 const char *s = asciify(c);
5446 if (*s) {
5447 buf[0] = '`';
5448 strcpy(buf + 1, s);
5449 strcat(buf, "'");
5450 return buf;
5452 sprintf(buf, "magic character code %d", c);
5453 return buf;
5455 if (csprint(c)) {
5456 buf[0] = '`';
5457 buf[1] = c;
5458 buf[2] = '\'';
5459 return buf;
5461 sprintf(buf, "character code %d", c);
5462 return buf;
5465 // .tm, .tm1, and .tmc
5467 void do_terminal(int newline, int string_like)
5469 if (!tok.newline() && !tok.eof()) {
5470 int c;
5471 for (;;) {
5472 c = get_copy(0);
5473 if (string_like && c == '"') {
5474 c = get_copy(0);
5475 break;
5477 if (c != ' ' && c != '\t')
5478 break;
5480 for (; c != '\n' && c != EOF; c = get_copy(0))
5481 fputs(asciify(c), stderr);
5483 if (newline)
5484 fputc('\n', stderr);
5485 fflush(stderr);
5486 tok.next();
5489 void terminal()
5491 do_terminal(1, 0);
5494 void terminal1()
5496 do_terminal(1, 1);
5499 void terminal_continue()
5501 do_terminal(0, 1);
5504 dictionary stream_dictionary(20);
5506 void do_open(int append)
5508 symbol stream = get_name(1);
5509 if (!stream.is_null()) {
5510 symbol filename = get_long_name(1);
5511 if (!filename.is_null()) {
5512 errno = 0;
5513 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
5514 if (!fp) {
5515 error("can't open `%1' for %2: %3",
5516 filename.contents(),
5517 append ? "appending" : "writing",
5518 strerror(errno));
5519 fp = (FILE *)stream_dictionary.remove(stream);
5521 else
5522 fp = (FILE *)stream_dictionary.lookup(stream, fp);
5523 if (fp)
5524 fclose(fp);
5527 skip_line();
5530 void open_request()
5532 if (safer_flag) {
5533 error(".open request not allowed in safer mode");
5534 skip_line();
5536 else
5537 do_open(0);
5540 void opena_request()
5542 if (safer_flag) {
5543 error(".opena request not allowed in safer mode");
5544 skip_line();
5546 else
5547 do_open(1);
5550 void close_request()
5552 symbol stream = get_name(1);
5553 if (!stream.is_null()) {
5554 FILE *fp = (FILE *)stream_dictionary.remove(stream);
5555 if (!fp)
5556 error("no stream named `%1'", stream.contents());
5557 else
5558 fclose(fp);
5560 skip_line();
5563 // .write and .writec
5565 void do_write_request(int newline)
5567 symbol stream = get_name(1);
5568 if (stream.is_null()) {
5569 skip_line();
5570 return;
5572 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5573 if (!fp) {
5574 error("no stream named `%1'", stream.contents());
5575 skip_line();
5576 return;
5578 int c;
5579 while ((c = get_copy(0)) == ' ')
5581 if (c == '"')
5582 c = get_copy(0);
5583 for (; c != '\n' && c != EOF; c = get_copy(0))
5584 fputs(asciify(c), fp);
5585 if (newline)
5586 fputc('\n', fp);
5587 fflush(fp);
5588 tok.next();
5591 void write_request()
5593 do_write_request(1);
5596 void write_request_continue()
5598 do_write_request(0);
5601 void write_macro_request()
5603 symbol stream = get_name(1);
5604 if (stream.is_null()) {
5605 skip_line();
5606 return;
5608 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5609 if (!fp) {
5610 error("no stream named `%1'", stream.contents());
5611 skip_line();
5612 return;
5614 symbol s = get_name(1);
5615 if (s.is_null()) {
5616 skip_line();
5617 return;
5619 request_or_macro *p = lookup_request(s);
5620 macro *m = p->to_macro();
5621 if (!m)
5622 error("cannot write request");
5623 else {
5624 string_iterator iter(*m);
5625 for (;;) {
5626 int c = iter.get(0);
5627 if (c == EOF)
5628 break;
5629 fputs(asciify(c), fp);
5631 fflush(fp);
5633 skip_line();
5636 void warnscale_request()
5638 if (has_arg()) {
5639 char c = tok.ch();
5640 if (c == 'u')
5641 warn_scale = 1.0;
5642 else if (c == 'i')
5643 warn_scale = (double)units_per_inch;
5644 else if (c == 'c')
5645 warn_scale = (double)units_per_inch / 2.54;
5646 else if (c == 'p')
5647 warn_scale = (double)units_per_inch / 72.0;
5648 else if (c == 'P')
5649 warn_scale = (double)units_per_inch / 6.0;
5650 else {
5651 warning(WARN_SCALE,
5652 "invalid scaling indicator `%1', using `i' instead", c);
5653 c = 'i';
5655 warn_scaling_indicator = c;
5657 skip_line();
5660 void spreadwarn_request()
5662 hunits n;
5663 if (has_arg() && get_hunits(&n, 'm')) {
5664 if (n < 0)
5665 n = 0;
5666 hunits em = curenv->get_size();
5667 spread_limit = (double)n.to_units()
5668 / (em.is_zero() ? hresolution : em.to_units());
5670 else
5671 spread_limit = -spread_limit - 1; // no arg toggles on/off without
5672 // changing value; we mirror at
5673 // -0.5 to make zero a valid value
5674 skip_line();
5677 static void init_charset_table()
5679 char buf[16];
5680 strcpy(buf, "char");
5681 for (int i = 0; i < 256; i++) {
5682 strcpy(buf + 4, i_to_a(i));
5683 charset_table[i] = get_charinfo(symbol(buf));
5684 charset_table[i]->set_ascii_code(i);
5685 if (csalpha(i))
5686 charset_table[i]->set_hyphenation_code(cmlower(i));
5688 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
5689 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
5690 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
5691 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
5692 charset_table['"']->set_flags(charinfo::TRANSPARENT);
5693 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
5694 charset_table[')']->set_flags(charinfo::TRANSPARENT);
5695 charset_table[']']->set_flags(charinfo::TRANSPARENT);
5696 charset_table['*']->set_flags(charinfo::TRANSPARENT);
5697 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
5698 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
5699 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
5700 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5701 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5702 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5703 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5704 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
5705 page_character = charset_table['%'];
5708 static void init_hpf_code_table()
5710 for (int i = 0; i < 256; i++)
5711 hpf_code_table[i] = i;
5714 static void do_translate(int translate_transparent, int translate_input)
5716 tok.skip();
5717 while (!tok.newline() && !tok.eof()) {
5718 if (tok.space()) {
5719 // This is a really bizarre troff feature.
5720 tok.next();
5721 translate_space_to_dummy = tok.dummy();
5722 if (tok.newline() || tok.eof())
5723 break;
5724 tok.next();
5725 continue;
5727 charinfo *ci1 = tok.get_char(1);
5728 if (ci1 == 0)
5729 break;
5730 tok.next();
5731 if (tok.newline() || tok.eof()) {
5732 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
5733 translate_transparent);
5734 break;
5736 if (tok.space())
5737 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
5738 translate_transparent);
5739 else if (tok.stretchable_space())
5740 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
5741 translate_transparent);
5742 else if (tok.dummy())
5743 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
5744 translate_transparent);
5745 else if (tok.hyphen_indicator())
5746 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
5747 translate_transparent);
5748 else {
5749 charinfo *ci2 = tok.get_char(1);
5750 if (ci2 == 0)
5751 break;
5752 if (ci1 == ci2)
5753 ci1->set_translation(0, translate_transparent, translate_input);
5754 else
5755 ci1->set_translation(ci2, translate_transparent, translate_input);
5757 tok.next();
5759 skip_line();
5762 void translate()
5764 do_translate(1, 0);
5767 void translate_no_transparent()
5769 do_translate(0, 0);
5772 void translate_input()
5774 do_translate(1, 1);
5777 void char_flags()
5779 int flags;
5780 if (get_integer(&flags))
5781 while (has_arg()) {
5782 charinfo *ci = tok.get_char(1);
5783 if (ci) {
5784 charinfo *tem = ci->get_translation();
5785 if (tem)
5786 ci = tem;
5787 ci->set_flags(flags);
5789 tok.next();
5791 skip_line();
5794 void hyphenation_code()
5796 tok.skip();
5797 while (!tok.newline() && !tok.eof()) {
5798 charinfo *ci = tok.get_char(1);
5799 if (ci == 0)
5800 break;
5801 tok.next();
5802 tok.skip();
5803 unsigned char c = tok.ch();
5804 if (c == 0) {
5805 error("hyphenation code must be ordinary character");
5806 break;
5808 if (csdigit(c)) {
5809 error("hyphenation code cannot be digit");
5810 break;
5812 ci->set_hyphenation_code(c);
5813 if (ci->get_translation()
5814 && ci->get_translation()->get_translation_input())
5815 ci->get_translation()->set_hyphenation_code(c);
5816 tok.next();
5817 tok.skip();
5819 skip_line();
5822 void hyphenation_patterns_file_code()
5824 tok.skip();
5825 while (!tok.newline() && !tok.eof()) {
5826 int n1, n2;
5827 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
5828 if (!has_arg()) {
5829 error("missing output hyphenation code");
5830 break;
5832 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
5833 hpf_code_table[n1] = n2;
5834 tok.skip();
5836 else {
5837 error("output hyphenation code must be integer in the range 0..255");
5838 break;
5841 else {
5842 error("input hyphenation code must be integer in the range 0..255");
5843 break;
5846 skip_line();
5849 charinfo *token::get_char(int required)
5851 if (type == TOKEN_CHAR)
5852 return charset_table[c];
5853 if (type == TOKEN_SPECIAL)
5854 return get_charinfo(nm);
5855 if (type == TOKEN_NUMBERED_CHAR)
5856 return get_charinfo_by_number(val);
5857 if (type == TOKEN_ESCAPE) {
5858 if (escape_char != 0)
5859 return charset_table[escape_char];
5860 else {
5861 error("`\\e' used while no current escape character");
5862 return 0;
5865 if (required) {
5866 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
5867 warning(WARN_MISSING, "missing normal or special character");
5868 else
5869 error("normal or special character expected (got %1)", description());
5871 return 0;
5874 charinfo *get_optional_char()
5876 while (tok.space())
5877 tok.next();
5878 charinfo *ci = tok.get_char();
5879 if (!ci)
5880 check_missing_character();
5881 else
5882 tok.next();
5883 return ci;
5886 void check_missing_character()
5888 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
5889 error("normal or special character expected (got %1): "
5890 "treated as missing",
5891 tok.description());
5894 // this is for \Z
5896 int token::add_to_node_list(node **pp)
5898 hunits w;
5899 int s;
5900 node *n = 0;
5901 switch (type) {
5902 case TOKEN_CHAR:
5903 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
5904 break;
5905 case TOKEN_DUMMY:
5906 n = new dummy_node;
5907 break;
5908 case TOKEN_ESCAPE:
5909 if (escape_char != 0)
5910 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
5911 break;
5912 case TOKEN_HYPHEN_INDICATOR:
5913 *pp = (*pp)->add_discretionary_hyphen();
5914 break;
5915 case TOKEN_ITALIC_CORRECTION:
5916 *pp = (*pp)->add_italic_correction(&w);
5917 break;
5918 case TOKEN_LEFT_BRACE:
5919 break;
5920 case TOKEN_MARK_INPUT:
5921 set_number_reg(nm, curenv->get_input_line_position().to_units());
5922 break;
5923 case TOKEN_NODE:
5924 n = nd;
5925 nd = 0;
5926 break;
5927 case TOKEN_NUMBERED_CHAR:
5928 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
5929 break;
5930 case TOKEN_RIGHT_BRACE:
5931 break;
5932 case TOKEN_SPACE:
5933 n = new hmotion_node(curenv->get_space_width());
5934 break;
5935 case TOKEN_SPECIAL:
5936 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
5937 break;
5938 case TOKEN_STRETCHABLE_SPACE:
5939 n = new unbreakable_space_node(curenv->get_space_width());
5940 break;
5941 case TOKEN_TRANSPARENT_DUMMY:
5942 n = new transparent_dummy_node;
5943 break;
5944 case TOKEN_ZERO_WIDTH_BREAK:
5945 n = new space_node(H0);
5946 n->freeze_space();
5947 n->is_escape_colon();
5948 break;
5949 default:
5950 return 0;
5952 if (n) {
5953 n->next = *pp;
5954 *pp = n;
5956 return 1;
5959 void token::process()
5961 if (possibly_handle_first_page_transition())
5962 return;
5963 switch (type) {
5964 case TOKEN_BACKSPACE:
5965 curenv->add_node(new hmotion_node(-curenv->get_space_width()));
5966 break;
5967 case TOKEN_CHAR:
5968 curenv->add_char(charset_table[c]);
5969 break;
5970 case TOKEN_DUMMY:
5971 curenv->add_node(new dummy_node);
5972 break;
5973 case TOKEN_EMPTY:
5974 assert(0);
5975 break;
5976 case TOKEN_EOF:
5977 assert(0);
5978 break;
5979 case TOKEN_ESCAPE:
5980 if (escape_char != 0)
5981 curenv->add_char(charset_table[escape_char]);
5982 break;
5983 case TOKEN_BEGIN_TRAP:
5984 case TOKEN_END_TRAP:
5985 case TOKEN_PAGE_EJECTOR:
5986 // these are all handled in process_input_stack()
5987 break;
5988 case TOKEN_HYPHEN_INDICATOR:
5989 curenv->add_hyphen_indicator();
5990 break;
5991 case TOKEN_INTERRUPT:
5992 curenv->interrupt();
5993 break;
5994 case TOKEN_ITALIC_CORRECTION:
5995 curenv->add_italic_correction();
5996 break;
5997 case TOKEN_LEADER:
5998 curenv->handle_tab(1);
5999 break;
6000 case TOKEN_LEFT_BRACE:
6001 break;
6002 case TOKEN_MARK_INPUT:
6003 set_number_reg(nm, curenv->get_input_line_position().to_units());
6004 break;
6005 case TOKEN_NEWLINE:
6006 curenv->newline();
6007 break;
6008 case TOKEN_NODE:
6009 curenv->add_node(nd);
6010 nd = 0;
6011 break;
6012 case TOKEN_NUMBERED_CHAR:
6013 curenv->add_char(get_charinfo_by_number(val));
6014 break;
6015 case TOKEN_REQUEST:
6016 // handled in process_input_stack()
6017 break;
6018 case TOKEN_RIGHT_BRACE:
6019 break;
6020 case TOKEN_SPACE:
6021 curenv->space();
6022 break;
6023 case TOKEN_SPECIAL:
6024 curenv->add_char(get_charinfo(nm));
6025 break;
6026 case TOKEN_SPREAD:
6027 curenv->spread();
6028 break;
6029 case TOKEN_STRETCHABLE_SPACE:
6030 curenv->add_node(new unbreakable_space_node(curenv->get_space_width()));
6031 break;
6032 case TOKEN_TAB:
6033 curenv->handle_tab(0);
6034 break;
6035 case TOKEN_TRANSPARENT:
6036 break;
6037 case TOKEN_TRANSPARENT_DUMMY:
6038 curenv->add_node(new transparent_dummy_node);
6039 break;
6040 case TOKEN_ZERO_WIDTH_BREAK:
6042 node *tmp = new space_node(H0);
6043 tmp->freeze_space();
6044 tmp->is_escape_colon();
6045 curenv->add_node(tmp);
6046 break;
6048 default:
6049 assert(0);
6053 class nargs_reg : public reg {
6054 public:
6055 const char *get_string();
6058 const char *nargs_reg::get_string()
6060 return i_to_a(input_stack::nargs());
6063 class lineno_reg : public reg {
6064 public:
6065 const char *get_string();
6068 const char *lineno_reg::get_string()
6070 int line;
6071 const char *file;
6072 if (!input_stack::get_location(0, &file, &line))
6073 line = 0;
6074 return i_to_a(line);
6077 class writable_lineno_reg : public general_reg {
6078 public:
6079 writable_lineno_reg();
6080 void set_value(units);
6081 int get_value(units *);
6084 writable_lineno_reg::writable_lineno_reg()
6088 int writable_lineno_reg::get_value(units *res)
6090 int line;
6091 const char *file;
6092 if (!input_stack::get_location(0, &file, &line))
6093 return 0;
6094 *res = line;
6095 return 1;
6098 void writable_lineno_reg::set_value(units n)
6100 input_stack::set_location(0, n);
6103 class filename_reg : public reg {
6104 public:
6105 const char *get_string();
6108 const char *filename_reg::get_string()
6110 int line;
6111 const char *file;
6112 if (input_stack::get_location(0, &file, &line))
6113 return file;
6114 else
6115 return 0;
6118 class constant_reg : public reg {
6119 const char *s;
6120 public:
6121 constant_reg(const char *);
6122 const char *get_string();
6125 constant_reg::constant_reg(const char *p) : s(p)
6129 const char *constant_reg::get_string()
6131 return s;
6134 constant_int_reg::constant_int_reg(int *q) : p(q)
6138 const char *constant_int_reg::get_string()
6140 return i_to_a(*p);
6143 void abort_request()
6145 int c;
6146 if (tok.eof())
6147 c = EOF;
6148 else if (tok.newline())
6149 c = '\n';
6150 else {
6151 while ((c = get_copy(0)) == ' ')
6154 if (c == EOF || c == '\n')
6155 fputs("User Abort.", stderr);
6156 else {
6157 for (; c != '\n' && c != EOF; c = get_copy(0))
6158 fputs(asciify(c), stderr);
6160 fputc('\n', stderr);
6161 cleanup_and_exit(1);
6164 char *read_string()
6166 int len = 256;
6167 char *s = new char[len];
6168 int c;
6169 while ((c = get_copy(0)) == ' ')
6171 int i = 0;
6172 while (c != '\n' && c != EOF) {
6173 if (!invalid_input_char(c)) {
6174 if (i + 2 > len) {
6175 char *tem = s;
6176 s = new char[len*2];
6177 memcpy(s, tem, len);
6178 len *= 2;
6179 a_delete tem;
6181 s[i++] = c;
6183 c = get_copy(0);
6185 s[i] = '\0';
6186 tok.next();
6187 if (i == 0) {
6188 a_delete s;
6189 return 0;
6191 return s;
6194 void pipe_output()
6196 if (safer_flag) {
6197 error(".pi request not allowed in safer mode");
6198 skip_line();
6200 else {
6201 #ifdef POPEN_MISSING
6202 error("pipes not available on this system");
6203 skip_line();
6204 #else /* not POPEN_MISSING */
6205 if (the_output) {
6206 error("can't pipe: output already started");
6207 skip_line();
6209 else {
6210 char *pc;
6211 if ((pc = read_string()) == 0)
6212 error("can't pipe to empty command");
6213 if (pipe_command) {
6214 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
6215 strcpy(s, pipe_command);
6216 strcat(s, "|");
6217 strcat(s, pc);
6218 a_delete pipe_command;
6219 a_delete pc;
6220 pipe_command = s;
6222 else
6223 pipe_command = pc;
6225 #endif /* not POPEN_MISSING */
6229 static int system_status;
6231 void system_request()
6233 if (safer_flag) {
6234 error(".sy request not allowed in safer mode");
6235 skip_line();
6237 else {
6238 char *command = read_string();
6239 if (!command)
6240 error("empty command");
6241 else {
6242 system_status = system(command);
6243 a_delete command;
6248 void copy_file()
6250 if (curdiv == topdiv && topdiv->before_first_page) {
6251 handle_initial_request(COPY_FILE_REQUEST);
6252 return;
6254 symbol filename = get_long_name(1);
6255 while (!tok.newline() && !tok.eof())
6256 tok.next();
6257 if (break_flag)
6258 curenv->do_break();
6259 if (!filename.is_null())
6260 curdiv->copy_file(filename.contents());
6261 tok.next();
6264 #ifdef COLUMN
6266 void vjustify()
6268 if (curdiv == topdiv && topdiv->before_first_page) {
6269 handle_initial_request(VJUSTIFY_REQUEST);
6270 return;
6272 symbol type = get_long_name(1);
6273 if (!type.is_null())
6274 curdiv->vjustify(type);
6275 skip_line();
6278 #endif /* COLUMN */
6280 void transparent_file()
6282 if (curdiv == topdiv && topdiv->before_first_page) {
6283 handle_initial_request(TRANSPARENT_FILE_REQUEST);
6284 return;
6286 symbol filename = get_long_name(1);
6287 while (!tok.newline() && !tok.eof())
6288 tok.next();
6289 if (break_flag)
6290 curenv->do_break();
6291 if (!filename.is_null()) {
6292 errno = 0;
6293 FILE *fp = fopen(filename.contents(), "r");
6294 if (!fp)
6295 error("can't open `%1': %2", filename.contents(), strerror(errno));
6296 else {
6297 int bol = 1;
6298 for (;;) {
6299 int c = getc(fp);
6300 if (c == EOF)
6301 break;
6302 if (invalid_input_char(c))
6303 warning(WARN_INPUT, "invalid input character code %1", int(c));
6304 else {
6305 curdiv->transparent_output(c);
6306 bol = c == '\n';
6309 if (!bol)
6310 curdiv->transparent_output('\n');
6311 fclose(fp);
6314 tok.next();
6317 class page_range {
6318 int first;
6319 int last;
6320 public:
6321 page_range *next;
6322 page_range(int, int, page_range *);
6323 int contains(int n);
6326 page_range::page_range(int i, int j, page_range *p)
6327 : first(i), last(j), next(p)
6331 int page_range::contains(int n)
6333 return n >= first && (last <= 0 || n <= last);
6336 page_range *output_page_list = 0;
6338 int in_output_page_list(int n)
6340 if (!output_page_list)
6341 return 1;
6342 for (page_range *p = output_page_list; p; p = p->next)
6343 if (p->contains(n))
6344 return 1;
6345 return 0;
6348 static void parse_output_page_list(char *p)
6350 for (;;) {
6351 int i;
6352 if (*p == '-')
6353 i = 1;
6354 else if (csdigit(*p)) {
6355 i = 0;
6357 i = i*10 + *p++ - '0';
6358 while (csdigit(*p));
6360 else
6361 break;
6362 int j;
6363 if (*p == '-') {
6364 p++;
6365 j = 0;
6366 if (csdigit(*p)) {
6368 j = j*10 + *p++ - '0';
6369 while (csdigit(*p));
6372 else
6373 j = i;
6374 if (j == 0)
6375 last_page_number = -1;
6376 else if (last_page_number >= 0 && j > last_page_number)
6377 last_page_number = j;
6378 output_page_list = new page_range(i, j, output_page_list);
6379 if (*p != ',')
6380 break;
6381 ++p;
6383 if (*p != '\0') {
6384 error("bad output page list");
6385 output_page_list = 0;
6389 static FILE *open_mac_file(const char *mac, char **path)
6391 // Try first FOOBAR.tmac, then tmac.FOOBAR
6392 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
6393 strcpy(s1, mac);
6394 strcat(s1, MACRO_POSTFIX);
6395 FILE *fp = mac_path->open_file(s1, path);
6396 a_delete s1;
6397 if (!fp) {
6398 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
6399 strcpy(s2, MACRO_PREFIX);
6400 strcat(s2, mac);
6401 fp = mac_path->open_file(s2, path);
6402 a_delete s2;
6404 return fp;
6407 static void process_macro_file(const char *mac)
6409 char *path;
6410 FILE *fp = open_mac_file(mac, &path);
6411 if (!fp)
6412 fatal("can't find macro file %1", mac);
6413 const char *s = symbol(path).contents();
6414 a_delete path;
6415 input_stack::push(new file_iterator(fp, s));
6416 tok.next();
6417 process_input_stack();
6420 static void process_startup_file(char *filename)
6422 char *path;
6423 search_path *orig_mac_path = mac_path;
6424 mac_path = &config_macro_path;
6425 FILE *fp = mac_path->open_file(filename, &path);
6426 if (fp) {
6427 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6428 a_delete path;
6429 tok.next();
6430 process_input_stack();
6432 mac_path = orig_mac_path;
6435 void macro_source()
6437 symbol nm = get_long_name(1);
6438 if (nm.is_null())
6439 skip_line();
6440 else {
6441 while (!tok.newline() && !tok.eof())
6442 tok.next();
6443 char *path;
6444 FILE *fp = mac_path->open_file(nm.contents(), &path);
6445 // .mso doesn't (and cannot) go through open_mac_file, so we
6446 // need to do it here manually: If we have tmac.FOOBAR, try
6447 // FOOBAR.tmac and vice versa
6448 if (!fp) {
6449 const char *fn = nm.contents();
6450 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
6451 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
6452 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
6453 strcat(s, MACRO_POSTFIX);
6454 fp = mac_path->open_file(s, &path);
6455 a_delete s;
6457 if (!fp) {
6458 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
6459 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
6460 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
6461 strcpy(s, MACRO_PREFIX);
6462 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
6463 fp = mac_path->open_file(s, &path);
6464 a_delete s;
6468 if (fp) {
6469 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6470 a_delete path;
6472 else
6473 error("can't find macro file `%1'", nm.contents());
6474 tok.next();
6478 static void process_input_file(const char *name)
6480 FILE *fp;
6481 if (strcmp(name, "-") == 0) {
6482 clearerr(stdin);
6483 fp = stdin;
6485 else {
6486 errno = 0;
6487 fp = fopen(name, "r");
6488 if (!fp)
6489 fatal("can't open `%1': %2", name, strerror(errno));
6491 input_stack::push(new file_iterator(fp, name));
6492 tok.next();
6493 process_input_stack();
6496 // make sure the_input is empty before calling this
6498 static int evaluate_expression(const char *expr, units *res)
6500 input_stack::push(make_temp_iterator(expr));
6501 tok.next();
6502 int success = get_number(res, 'u');
6503 while (input_stack::get(0) != EOF)
6505 return success;
6508 static void do_register_assignment(const char *s)
6510 const char *p = strchr(s, '=');
6511 if (!p) {
6512 char buf[2];
6513 buf[0] = s[0];
6514 buf[1] = 0;
6515 units n;
6516 if (evaluate_expression(s + 1, &n))
6517 set_number_reg(buf, n);
6519 else {
6520 char *buf = new char[p - s + 1];
6521 memcpy(buf, s, p - s);
6522 buf[p - s] = 0;
6523 units n;
6524 if (evaluate_expression(p + 1, &n))
6525 set_number_reg(buf, n);
6526 a_delete buf;
6530 static void set_string(const char *name, const char *value)
6532 macro *m = new macro;
6533 for (const char *p = value; *p; p++)
6534 if (!invalid_input_char((unsigned char)*p))
6535 m->append(*p);
6536 request_dictionary.define(name, m);
6539 static void do_string_assignment(const char *s)
6541 const char *p = strchr(s, '=');
6542 if (!p) {
6543 char buf[2];
6544 buf[0] = s[0];
6545 buf[1] = 0;
6546 set_string(buf, s + 1);
6548 else {
6549 char *buf = new char[p - s + 1];
6550 memcpy(buf, s, p - s);
6551 buf[p - s] = 0;
6552 set_string(buf, p + 1);
6553 a_delete buf;
6557 struct string_list {
6558 const char *s;
6559 string_list *next;
6560 string_list(const char *ss) : s(ss), next(0) {}
6563 #if 0
6564 static void prepend_string(const char *s, string_list **p)
6566 string_list *l = new string_list(s);
6567 l->next = *p;
6568 *p = l;
6570 #endif
6572 static void add_string(const char *s, string_list **p)
6574 while (*p)
6575 p = &((*p)->next);
6576 *p = new string_list(s);
6579 void usage(FILE *stream, const char *prog)
6581 fprintf(stream,
6582 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
6583 " -rcn -Tname -Fdir -Mdir [files...]\n",
6584 prog);
6587 int main(int argc, char **argv)
6589 program_name = argv[0];
6590 static char stderr_buf[BUFSIZ];
6591 setbuf(stderr, stderr_buf);
6592 int c;
6593 string_list *macros = 0;
6594 string_list *register_assignments = 0;
6595 string_list *string_assignments = 0;
6596 int iflag = 0;
6597 int tflag = 0;
6598 int fflag = 0;
6599 int nflag = 0;
6600 int no_rc = 0; // don't process troffrc and troffrc-end
6601 int next_page_number;
6602 opterr = 0;
6603 hresolution = vresolution = 1;
6604 // restore $PATH if called from groff
6605 char* groff_path = getenv("GROFF_PATH__");
6606 if (groff_path) {
6607 string e = "PATH";
6608 e += '=';
6609 if (*groff_path)
6610 e += groff_path;
6611 e += '\0';
6612 if (putenv(strsave(e.contents())))
6613 fatal("putenv failed");
6615 static const struct option long_options[] = {
6616 { "help", no_argument, 0, CHAR_MAX + 1 },
6617 { "version", no_argument, 0, 'v' },
6618 { 0, 0, 0, 0 }
6620 while ((c = getopt_long(argc, argv, "abcivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU",
6621 long_options, 0))
6622 != EOF)
6623 switch(c) {
6624 case 'v':
6626 printf("GNU troff (groff) version %s\n", Version_string);
6627 exit(0);
6628 break;
6630 case 'T':
6631 device = optarg;
6632 tflag = 1;
6633 is_html = (strcmp(device, "html") == 0);
6634 break;
6635 case 'C':
6636 compatible_flag = 1;
6637 // fall through
6638 case 'c':
6639 disable_color_flag = 1;
6640 break;
6641 case 'M':
6642 macro_path.command_line_dir(optarg);
6643 safer_macro_path.command_line_dir(optarg);
6644 config_macro_path.command_line_dir(optarg);
6645 break;
6646 case 'F':
6647 font::command_line_font_dir(optarg);
6648 break;
6649 case 'm':
6650 add_string(optarg, &macros);
6651 break;
6652 case 'E':
6653 inhibit_errors = 1;
6654 break;
6655 case 'R':
6656 no_rc = 1;
6657 break;
6658 case 'w':
6659 enable_warning(optarg);
6660 break;
6661 case 'W':
6662 disable_warning(optarg);
6663 break;
6664 case 'i':
6665 iflag = 1;
6666 break;
6667 case 'b':
6668 backtrace_flag = 1;
6669 break;
6670 case 'a':
6671 ascii_output_flag = 1;
6672 break;
6673 case 'z':
6674 suppress_output_flag = 1;
6675 break;
6676 case 'n':
6677 if (sscanf(optarg, "%d", &next_page_number) == 1)
6678 nflag++;
6679 else
6680 error("bad page number");
6681 break;
6682 case 'o':
6683 parse_output_page_list(optarg);
6684 break;
6685 case 'd':
6686 if (*optarg == '\0')
6687 error("`-d' requires non-empty argument");
6688 else
6689 add_string(optarg, &string_assignments);
6690 break;
6691 case 'r':
6692 if (*optarg == '\0')
6693 error("`-r' requires non-empty argument");
6694 else
6695 add_string(optarg, &register_assignments);
6696 break;
6697 case 'f':
6698 default_family = symbol(optarg);
6699 fflag = 1;
6700 break;
6701 case 'q':
6702 case 's':
6703 case 't':
6704 // silently ignore these
6705 break;
6706 case 'U':
6707 safer_flag = 0; // unsafe behaviour
6708 break;
6709 case CHAR_MAX + 1: // --help
6710 usage(stdout, argv[0]);
6711 exit(0);
6712 break;
6713 case '?':
6714 usage(stderr, argv[0]);
6715 exit(1);
6716 break; // never reached
6717 default:
6718 assert(0);
6720 if (!safer_flag)
6721 mac_path = &macro_path;
6722 set_string(".T", device);
6723 init_charset_table();
6724 init_hpf_code_table();
6725 if (!font::load_desc())
6726 fatal("sorry, I can't continue");
6727 units_per_inch = font::res;
6728 hresolution = font::hor;
6729 vresolution = font::vert;
6730 sizescale = font::sizescale;
6731 tcommand_flag = font::tcommand;
6732 warn_scale = (double)units_per_inch;
6733 warn_scaling_indicator = 'i';
6734 if (!fflag && font::family != 0 && *font::family != '\0')
6735 default_family = symbol(font::family);
6736 font_size::init_size_table(font::sizes);
6737 int i;
6738 int j = 1;
6739 if (font::style_table) {
6740 for (i = 0; font::style_table[i]; i++)
6741 mount_style(j++, symbol(font::style_table[i]));
6743 for (i = 0; font::font_name_table[i]; i++, j++)
6744 // In the DESC file a font name of 0 (zero) means leave this
6745 // position empty.
6746 if (strcmp(font::font_name_table[i], "0") != 0)
6747 mount_font(j, symbol(font::font_name_table[i]));
6748 curdiv = topdiv = new top_level_diversion;
6749 if (nflag)
6750 topdiv->set_next_page_number(next_page_number);
6751 init_input_requests();
6752 init_env_requests();
6753 init_div_requests();
6754 #ifdef COLUMN
6755 init_column_requests();
6756 #endif /* COLUMN */
6757 init_node_requests();
6758 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
6759 init_registers();
6760 init_reg_requests();
6761 init_hyphen_requests();
6762 init_environments();
6763 while (string_assignments) {
6764 do_string_assignment(string_assignments->s);
6765 string_list *tem = string_assignments;
6766 string_assignments = string_assignments->next;
6767 delete tem;
6769 while (register_assignments) {
6770 do_register_assignment(register_assignments->s);
6771 string_list *tem = register_assignments;
6772 register_assignments = register_assignments->next;
6773 delete tem;
6775 if (!no_rc)
6776 process_startup_file(INITIAL_STARTUP_FILE);
6777 while (macros) {
6778 process_macro_file(macros->s);
6779 string_list *tem = macros;
6780 macros = macros->next;
6781 delete tem;
6783 if (!no_rc)
6784 process_startup_file(FINAL_STARTUP_FILE);
6785 for (i = optind; i < argc; i++)
6786 process_input_file(argv[i]);
6787 if (optind >= argc || iflag)
6788 process_input_file("-");
6789 exit_troff();
6790 return 0; // not reached
6793 void warn_request()
6795 int n;
6796 if (has_arg() && get_integer(&n)) {
6797 if (n & ~WARN_TOTAL) {
6798 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
6799 n &= WARN_TOTAL;
6801 warning_mask = n;
6803 else
6804 warning_mask = WARN_TOTAL;
6805 skip_line();
6808 static void init_registers()
6810 #ifdef LONG_FOR_TIME_T
6811 long
6812 #else /* not LONG_FOR_TIME_T */
6813 time_t
6814 #endif /* not LONG_FOR_TIME_T */
6815 t = time(0);
6816 // Use struct here to work around misfeature in old versions of g++.
6817 struct tm *tt = localtime(&t);
6818 set_number_reg("dw", int(tt->tm_wday + 1));
6819 set_number_reg("dy", int(tt->tm_mday));
6820 set_number_reg("mo", int(tt->tm_mon + 1));
6821 set_number_reg("year", int(1900 + tt->tm_year));
6822 set_number_reg("yr", int(tt->tm_year));
6823 set_number_reg("$$", getpid());
6824 number_reg_dictionary.define(".A",
6825 new constant_reg(ascii_output_flag
6826 ? "1"
6827 : "0"));
6831 * registers associated with \O
6834 static int output_reg_minx_contents = -1;
6835 static int output_reg_miny_contents = -1;
6836 static int output_reg_maxx_contents = -1;
6837 static int output_reg_maxy_contents = -1;
6839 void check_output_limits(int x, int y)
6841 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
6842 output_reg_minx_contents = x;
6843 if (x > output_reg_maxx_contents)
6844 output_reg_maxx_contents = x;
6845 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
6846 output_reg_miny_contents = y;
6847 if (y > output_reg_maxy_contents)
6848 output_reg_maxy_contents = y;
6851 void reset_output_registers(int miny)
6853 // fprintf(stderr, "reset_output_registers\n");
6854 output_reg_minx_contents = -1;
6855 output_reg_miny_contents = -1;
6856 output_reg_maxx_contents = -1;
6857 output_reg_maxy_contents = -1;
6860 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
6862 *minx = output_reg_minx_contents;
6863 *miny = output_reg_miny_contents;
6864 *maxx = output_reg_maxx_contents;
6865 *maxy = output_reg_maxy_contents;
6868 void init_input_requests()
6870 init_request("ab", abort_request);
6871 init_request("als", alias_macro);
6872 init_request("am", append_macro);
6873 init_request("am1", append_nocomp_macro);
6874 init_request("ami", append_indirect_macro);
6875 init_request("as", append_string);
6876 init_request("as1", append_nocomp_string);
6877 init_request("asciify", asciify_macro);
6878 init_request("backtrace", backtrace_request);
6879 init_request("blm", blank_line_macro);
6880 init_request("break", while_break_request);
6881 init_request("cf", copy_file);
6882 init_request("cflags", char_flags);
6883 init_request("char", define_character);
6884 init_request("chop", chop_macro);
6885 init_request("close", close_request);
6886 init_request("continue", while_continue_request);
6887 init_request("cp", compatible);
6888 init_request("de", define_macro);
6889 init_request("de1", define_nocomp_macro);
6890 init_request("defcolor", define_color);
6891 init_request("dei", define_indirect_macro);
6892 init_request("do", do_request);
6893 init_request("ds", define_string);
6894 init_request("ds1", define_nocomp_string);
6895 init_request("ec", set_escape_char);
6896 init_request("ecr", restore_escape_char);
6897 init_request("ecs", save_escape_char);
6898 init_request("el", else_request);
6899 init_request("em", end_macro);
6900 init_request("eo", escape_off);
6901 init_request("ex", exit_request);
6902 init_request("fchar", define_fallback_character);
6903 #ifdef WIDOW_CONTROL
6904 init_request("fpl", flush_pending_lines);
6905 #endif /* WIDOW_CONTROL */
6906 init_request("hcode", hyphenation_code);
6907 init_request("hpfcode", hyphenation_patterns_file_code);
6908 init_request("ie", if_else_request);
6909 init_request("if", if_request);
6910 init_request("ig", ignore);
6911 init_request("length", length_macro);
6912 init_request("lf", line_file);
6913 init_request("mso", macro_source);
6914 init_request("nop", nop_request);
6915 init_request("nx", next_file);
6916 init_request("open", open_request);
6917 init_request("opena", opena_request);
6918 init_request("output", output_request);
6919 init_request("pc", set_page_character);
6920 init_request("pi", pipe_output);
6921 init_request("pm", print_macros);
6922 init_request("psbb", ps_bbox_request);
6923 #ifndef POPEN_MISSING
6924 init_request("pso", pipe_source);
6925 #endif /* not POPEN_MISSING */
6926 init_request("rchar", remove_character);
6927 init_request("rd", read_request);
6928 init_request("return", return_macro_request);
6929 init_request("rm", remove_macro);
6930 init_request("rn", rename_macro);
6931 init_request("shift", shift);
6932 init_request("so", source);
6933 init_request("spreadwarn", spreadwarn_request);
6934 init_request("substring", substring_macro);
6935 init_request("sy", system_request);
6936 init_request("tm", terminal);
6937 init_request("tm1", terminal1);
6938 init_request("tmc", terminal_continue);
6939 init_request("tr", translate);
6940 init_request("trf", transparent_file);
6941 init_request("trin", translate_input);
6942 init_request("trnt", translate_no_transparent);
6943 init_request("unformat", unformat_macro);
6944 init_request("warn", warn_request);
6945 init_request("while", while_request);
6946 init_request("write", write_request);
6947 init_request("writec", write_request_continue);
6948 init_request("writem", write_macro_request);
6949 init_request("nroff", nroff_request);
6950 init_request("troff", troff_request);
6951 #ifdef COLUMN
6952 init_request("vj", vjustify);
6953 #endif /* COLUMN */
6954 init_request("warnscale", warnscale_request);
6955 number_reg_dictionary.define(".$", new nargs_reg);
6956 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
6957 number_reg_dictionary.define(".F", new filename_reg);
6958 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
6959 number_reg_dictionary.define(".R", new constant_reg("10000"));
6960 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
6961 extern const char *revision;
6962 number_reg_dictionary.define(".Y", new constant_reg(revision));
6963 number_reg_dictionary.define(".c", new lineno_reg);
6964 number_reg_dictionary.define(".g", new constant_reg("1"));
6965 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
6966 extern const char *major_version;
6967 number_reg_dictionary.define(".x", new constant_reg(major_version));
6968 extern const char *minor_version;
6969 number_reg_dictionary.define(".y", new constant_reg(minor_version));
6970 number_reg_dictionary.define("c.", new writable_lineno_reg);
6971 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
6972 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
6973 number_reg_dictionary.define("opmaxx",
6974 new variable_reg(&output_reg_maxx_contents));
6975 number_reg_dictionary.define("opmaxy",
6976 new variable_reg(&output_reg_maxy_contents));
6977 number_reg_dictionary.define("opminx",
6978 new variable_reg(&output_reg_minx_contents));
6979 number_reg_dictionary.define("opminy",
6980 new variable_reg(&output_reg_miny_contents));
6981 number_reg_dictionary.define("slimit",
6982 new variable_reg(&input_stack::limit));
6983 number_reg_dictionary.define("systat", new variable_reg(&system_status));
6984 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
6985 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
6988 object_dictionary request_dictionary(501);
6990 void init_request(const char *s, REQUEST_FUNCP f)
6992 request_dictionary.define(s, new request(f));
6995 static request_or_macro *lookup_request(symbol nm)
6997 assert(!nm.is_null());
6998 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
6999 if (p == 0) {
7000 warning(WARN_MAC, "`%1' not defined", nm.contents());
7001 p = new macro;
7002 request_dictionary.define(nm, p);
7004 return p;
7007 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7009 // Don't interpret character definitions in compatible mode.
7010 int old_compatible_flag = compatible_flag;
7011 compatible_flag = 0;
7012 int old_escape_char = escape_char;
7013 escape_char = '\\';
7014 macro *mac = ci->set_macro(0);
7015 assert(mac != 0);
7016 environment *oldenv = curenv;
7017 environment env(envp);
7018 curenv = &env;
7019 curenv->set_composite();
7020 token old_tok = tok;
7021 input_stack::add_boundary();
7022 string_iterator *si =
7023 new string_iterator(*mac, "composite character", ci->nm);
7024 input_stack::push(si);
7025 // we don't use process_input_stack, because we don't want to recognise
7026 // requests
7027 for (;;) {
7028 tok.next();
7029 if (tok.eof())
7030 break;
7031 if (tok.newline()) {
7032 error("composite character mustn't contain newline");
7033 while (!tok.eof())
7034 tok.next();
7035 break;
7037 else
7038 tok.process();
7040 node *n = curenv->extract_output_line();
7041 input_stack::remove_boundary();
7042 ci->set_macro(mac);
7043 tok = old_tok;
7044 curenv = oldenv;
7045 compatible_flag = old_compatible_flag;
7046 escape_char = old_escape_char;
7047 return n;
7050 static node *read_draw_node()
7052 token start;
7053 start.next();
7054 if (!start.delimiter(1)){
7055 do {
7056 tok.next();
7057 } while (tok != start && !tok.newline() && !tok.eof());
7059 else {
7060 tok.next();
7061 if (tok == start)
7062 error("missing argument");
7063 else {
7064 unsigned char type = tok.ch();
7065 tok.next();
7066 int maxpoints = 10;
7067 hvpair *point = new hvpair[maxpoints];
7068 int npoints = 0;
7069 int no_last_v = 0;
7070 int err = 0;
7071 int i;
7072 for (i = 0; tok != start; i++) {
7073 if (i == maxpoints) {
7074 hvpair *oldpoint = point;
7075 point = new hvpair[maxpoints*2];
7076 for (int j = 0; j < maxpoints; j++)
7077 point[j] = oldpoint[j];
7078 maxpoints *= 2;
7079 a_delete oldpoint;
7081 if (!get_hunits(&point[i].h,
7082 type == 'f' || type == 't' ? 'u' : 'm')) {
7083 err = 1;
7084 break;
7086 ++npoints;
7087 tok.skip();
7088 point[i].v = V0;
7089 if (tok == start) {
7090 no_last_v = 1;
7091 break;
7093 if (!get_vunits(&point[i].v, 'v')) {
7094 err = 1;
7095 break;
7097 tok.skip();
7099 while (tok != start && !tok.newline() && !tok.eof())
7100 tok.next();
7101 if (!err) {
7102 switch (type) {
7103 case 'l':
7104 if (npoints != 1 || no_last_v) {
7105 error("two arguments needed for line");
7106 npoints = 1;
7108 break;
7109 case 'c':
7110 if (npoints != 1 || !no_last_v) {
7111 error("one argument needed for circle");
7112 npoints = 1;
7113 point[0].v = V0;
7115 break;
7116 case 'e':
7117 if (npoints != 1 || no_last_v) {
7118 error("two arguments needed for ellipse");
7119 npoints = 1;
7121 break;
7122 case 'a':
7123 if (npoints != 2 || no_last_v) {
7124 error("four arguments needed for arc");
7125 npoints = 2;
7127 break;
7128 case '~':
7129 if (no_last_v)
7130 error("even number of arguments needed for spline");
7131 break;
7132 case 'f':
7133 if (npoints != 1 || !no_last_v) {
7134 error("one argument needed for gray shade");
7135 npoints = 1;
7136 point[0].v = V0;
7138 default:
7139 // silently pass it through
7140 break;
7142 draw_node *dn = new draw_node(type, point, npoints,
7143 curenv->get_font_size());
7144 a_delete point;
7145 return dn;
7147 else {
7148 a_delete point;
7152 return 0;
7155 static struct {
7156 const char *name;
7157 int mask;
7158 } warning_table[] = {
7159 { "char", WARN_CHAR },
7160 { "range", WARN_RANGE },
7161 { "break", WARN_BREAK },
7162 { "delim", WARN_DELIM },
7163 { "el", WARN_EL },
7164 { "scale", WARN_SCALE },
7165 { "number", WARN_NUMBER },
7166 { "syntax", WARN_SYNTAX },
7167 { "tab", WARN_TAB },
7168 { "right-brace", WARN_RIGHT_BRACE },
7169 { "missing", WARN_MISSING },
7170 { "input", WARN_INPUT },
7171 { "escape", WARN_ESCAPE },
7172 { "space", WARN_SPACE },
7173 { "font", WARN_FONT },
7174 { "di", WARN_DI },
7175 { "mac", WARN_MAC },
7176 { "reg", WARN_REG },
7177 { "ig", WARN_IG },
7178 { "color", WARN_COLOR },
7179 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
7180 { "w", WARN_TOTAL },
7181 { "default", DEFAULT_WARNING_MASK },
7184 static int lookup_warning(const char *name)
7186 for (unsigned int i = 0;
7187 i < sizeof(warning_table)/sizeof(warning_table[0]);
7188 i++)
7189 if (strcmp(name, warning_table[i].name) == 0)
7190 return warning_table[i].mask;
7191 return 0;
7194 static void enable_warning(const char *name)
7196 int mask = lookup_warning(name);
7197 if (mask)
7198 warning_mask |= mask;
7199 else
7200 error("unknown warning `%1'", name);
7203 static void disable_warning(const char *name)
7205 int mask = lookup_warning(name);
7206 if (mask)
7207 warning_mask &= ~mask;
7208 else
7209 error("unknown warning `%1'", name);
7212 static void copy_mode_error(const char *format,
7213 const errarg &arg1,
7214 const errarg &arg2,
7215 const errarg &arg3)
7217 if (ignoring) {
7218 static const char prefix[] = "(in ignored input) ";
7219 char *s = new char[sizeof(prefix) + strlen(format)];
7220 strcpy(s, prefix);
7221 strcat(s, format);
7222 warning(WARN_IG, s, arg1, arg2, arg3);
7223 a_delete s;
7225 else
7226 error(format, arg1, arg2, arg3);
7229 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
7231 static void do_error(error_type type,
7232 const char *format,
7233 const errarg &arg1,
7234 const errarg &arg2,
7235 const errarg &arg3)
7237 const char *filename;
7238 int lineno;
7239 if (inhibit_errors && type < FATAL)
7240 return;
7241 if (backtrace_flag)
7242 input_stack::backtrace();
7243 if (!get_file_line(&filename, &lineno))
7244 filename = 0;
7245 if (filename)
7246 errprint("%1:%2: ", filename, lineno);
7247 else if (program_name)
7248 fprintf(stderr, "%s: ", program_name);
7249 switch (type) {
7250 case FATAL:
7251 fputs("fatal error: ", stderr);
7252 break;
7253 case ERROR:
7254 break;
7255 case WARNING:
7256 fputs("warning: ", stderr);
7257 break;
7258 case OUTPUT_WARNING:
7259 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
7260 fprintf(stderr, "warning [p %d, %.1f%c",
7261 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
7262 if (topdiv != curdiv) {
7263 double fromtop1 = curdiv->get_vertical_position().to_units()
7264 / warn_scale;
7265 fprintf(stderr, ", div `%s', %.1f%c",
7266 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
7268 fprintf(stderr, "]: ");
7269 break;
7271 errprint(format, arg1, arg2, arg3);
7272 fputc('\n', stderr);
7273 fflush(stderr);
7274 if (type == FATAL)
7275 cleanup_and_exit(1);
7278 int warning(warning_type t,
7279 const char *format,
7280 const errarg &arg1,
7281 const errarg &arg2,
7282 const errarg &arg3)
7284 if ((t & warning_mask) != 0) {
7285 do_error(WARNING, format, arg1, arg2, arg3);
7286 return 1;
7288 else
7289 return 0;
7292 int output_warning(warning_type t,
7293 const char *format,
7294 const errarg &arg1,
7295 const errarg &arg2,
7296 const errarg &arg3)
7298 if ((t & warning_mask) != 0) {
7299 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
7300 return 1;
7302 else
7303 return 0;
7306 void error(const char *format,
7307 const errarg &arg1,
7308 const errarg &arg2,
7309 const errarg &arg3)
7311 do_error(ERROR, format, arg1, arg2, arg3);
7314 void fatal(const char *format,
7315 const errarg &arg1,
7316 const errarg &arg2,
7317 const errarg &arg3)
7319 do_error(FATAL, format, arg1, arg2, arg3);
7322 void fatal_with_file_and_line(const char *filename, int lineno,
7323 const char *format,
7324 const errarg &arg1,
7325 const errarg &arg2,
7326 const errarg &arg3)
7328 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
7329 errprint(format, arg1, arg2, arg3);
7330 fputc('\n', stderr);
7331 fflush(stderr);
7332 cleanup_and_exit(1);
7335 void error_with_file_and_line(const char *filename, int lineno,
7336 const char *format,
7337 const errarg &arg1,
7338 const errarg &arg2,
7339 const errarg &arg3)
7341 fprintf(stderr, "%s:%d: error: ", filename, lineno);
7342 errprint(format, arg1, arg2, arg3);
7343 fputc('\n', stderr);
7344 fflush(stderr);
7347 dictionary charinfo_dictionary(501);
7349 charinfo *get_charinfo(symbol nm)
7351 void *p = charinfo_dictionary.lookup(nm);
7352 if (p != 0)
7353 return (charinfo *)p;
7354 charinfo *cp = new charinfo(nm);
7355 (void)charinfo_dictionary.lookup(nm, cp);
7356 return cp;
7359 int charinfo::next_index = 0;
7361 charinfo::charinfo(symbol s)
7362 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
7363 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
7364 not_found(0), transparent_translate(1), translate_input(0),
7365 fallback(0), nm(s)
7367 index = next_index++;
7370 void charinfo::set_hyphenation_code(unsigned char c)
7372 hyphenation_code = c;
7375 void charinfo::set_translation(charinfo *ci, int tt, int ti)
7377 translation = ci;
7378 if (ci && ti) {
7379 if (hyphenation_code != 0)
7380 ci->set_hyphenation_code(hyphenation_code);
7381 if (asciify_code != 0)
7382 ci->set_asciify_code(asciify_code);
7383 else if (ascii_code != 0)
7384 ci->set_asciify_code(ascii_code);
7385 ci->set_translation_input();
7387 special_translation = TRANSLATE_NONE;
7388 transparent_translate = tt;
7391 void charinfo::set_special_translation(int c, int tt)
7393 special_translation = c;
7394 translation = 0;
7395 transparent_translate = tt;
7398 void charinfo::set_ascii_code(unsigned char c)
7400 ascii_code = c;
7403 void charinfo::set_asciify_code(unsigned char c)
7405 asciify_code = c;
7408 macro *charinfo::set_macro(macro *m, int f)
7410 macro *tem = mac;
7411 mac = m;
7412 fallback = f;
7413 return tem;
7416 void charinfo::set_number(int n)
7418 number = n;
7419 flags |= NUMBERED;
7422 int charinfo::get_number()
7424 assert(flags & NUMBERED);
7425 return number;
7428 symbol UNNAMED_SYMBOL("---");
7430 // For numbered characters not between 0 and 255, we make a symbol out
7431 // of the number and store them in this dictionary.
7433 dictionary numbered_charinfo_dictionary(11);
7435 charinfo *get_charinfo_by_number(int n)
7437 static charinfo *number_table[256];
7439 if (n >= 0 && n < 256) {
7440 charinfo *ci = number_table[n];
7441 if (!ci) {
7442 ci = new charinfo(UNNAMED_SYMBOL);
7443 ci->set_number(n);
7444 number_table[n] = ci;
7446 return ci;
7448 else {
7449 symbol ns(i_to_a(n));
7450 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
7451 if (!ci) {
7452 ci = new charinfo(UNNAMED_SYMBOL);
7453 ci->set_number(n);
7454 numbered_charinfo_dictionary.lookup(ns, ci);
7456 return ci;
7460 int font::name_to_index(const char *nm)
7462 charinfo *ci;
7463 if (nm[1] == 0)
7464 ci = charset_table[nm[0] & 0xff];
7465 else if (nm[0] == '\\' && nm[2] == 0)
7466 ci = get_charinfo(symbol(nm + 1));
7467 else
7468 ci = get_charinfo(symbol(nm));
7469 if (ci == 0)
7470 return -1;
7471 else
7472 return ci->get_index();
7475 int font::number_to_index(int n)
7477 return get_charinfo_by_number(n)->get_index();