Adding `warnscale' and `spreadwarn' requests, based on a patch from
[s-roff.git] / src / roff / troff / input.cc
blobd3ff2ea3305ac9202de61f85b92ad1bb233415a6
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();
90 void transparent_file();
91 void process_input_stack();
93 const char *program_name = 0;
94 token tok;
95 int break_flag = 0;
96 int disable_color_flag = 0;
97 static int backtrace_flag = 0;
98 #ifndef POPEN_MISSING
99 char *pipe_command = 0;
100 #endif
101 charinfo *charset_table[256];
102 unsigned char hpf_code_table[256];
104 static int warning_mask = DEFAULT_WARNING_MASK;
105 static int inhibit_errors = 0;
106 static int ignoring = 0;
108 static void enable_warning(const char *);
109 static void disable_warning(const char *);
111 static int escape_char = '\\';
112 static symbol end_macro_name;
113 static symbol blank_line_macro_name;
114 static int compatible_flag = 0;
115 int ascii_output_flag = 0;
116 int suppress_output_flag = 0;
117 int is_html = 0;
118 int begin_level = 0; // number of nested .begin requests
120 int have_input = 0; // whether \f, \H, \R, \s, or \S has
121 // been processed in token::next()
122 int tcommand_flag = 0;
123 int safer_flag = 1; // safer by default
125 double spread_limit = -3.0 - 1.0; // negative means deactivated
127 double warn_scale;
128 char warn_scaling_indicator;
130 search_path *mac_path = &safer_macro_path;
132 static int get_copy(node**, int = 0);
133 static void copy_mode_error(const char *,
134 const errarg & = empty_errarg,
135 const errarg & = empty_errarg,
136 const errarg & = empty_errarg);
138 static symbol read_escape_name(int no_empty = 1);
139 static symbol read_long_escape_name(int no_empty = 1);
140 static void interpolate_string(symbol);
141 static void interpolate_macro(symbol);
142 static void interpolate_number_format(symbol);
143 static void interpolate_environment_variable(symbol);
145 static void interpolate_arg(symbol);
146 static request_or_macro *lookup_request(symbol);
147 static int get_delim_number(units *, int);
148 static int get_delim_number(units *, int, units);
149 static int get_line_arg(units *res, int si, charinfo **cp);
150 static int read_size(int *);
151 static symbol get_delim_name();
152 static void init_registers();
153 static void trapping_blank_line();
155 struct input_iterator;
156 input_iterator *make_temp_iterator(const char *);
157 const char *input_char_description(int);
160 void set_escape_char()
162 if (has_arg()) {
163 if (tok.ch() == 0) {
164 error("bad escape character");
165 escape_char = '\\';
167 else
168 escape_char = tok.ch();
170 else
171 escape_char = '\\';
172 skip_line();
175 void escape_off()
177 escape_char = 0;
178 skip_line();
181 static int saved_escape_char = '\\';
183 void save_escape_char()
185 saved_escape_char = escape_char;
186 skip_line();
189 void restore_escape_char()
191 escape_char = saved_escape_char;
192 skip_line();
195 class input_iterator {
196 public:
197 input_iterator();
198 virtual ~input_iterator() {}
199 int get(node **);
200 friend class input_stack;
201 protected:
202 const unsigned char *ptr;
203 const unsigned char *eptr;
204 input_iterator *next;
205 private:
206 virtual int fill(node **);
207 virtual int peek();
208 virtual int has_args() { return 0; }
209 virtual int nargs() { return 0; }
210 virtual input_iterator *get_arg(int) { return 0; }
211 virtual int get_location(int, const char **, int *) { return 0; }
212 virtual void backtrace() {}
213 virtual int set_location(const char *, int) { return 0; }
214 virtual int next_file(FILE *, const char *) { return 0; }
215 virtual void shift(int) {}
216 virtual int is_boundary() {return 0; }
217 virtual int internal_level() { return 0; }
218 virtual int is_file() { return 0; }
219 virtual int is_macro() { return 0; }
220 virtual void save_compatible_flag(int) {}
221 virtual int get_compatible_flag() { return 0; }
224 input_iterator::input_iterator()
225 : ptr(0), eptr(0)
229 int input_iterator::fill(node **)
231 return EOF;
234 int input_iterator::peek()
236 return EOF;
239 inline int input_iterator::get(node **p)
241 return ptr < eptr ? *ptr++ : fill(p);
244 class input_boundary : public input_iterator {
245 public:
246 int is_boundary() { return 1; }
249 class input_return_boundary : public input_iterator {
250 public:
251 int is_boundary() { return 2; }
254 class file_iterator : public input_iterator {
255 FILE *fp;
256 int lineno;
257 const char *filename;
258 int popened;
259 int newline_flag;
260 int suppress_newline_flag; // used by html
261 int seen_escape;
262 enum { BUF_SIZE = 512 };
263 unsigned char buf[BUF_SIZE];
264 void close();
265 public:
266 file_iterator(FILE *, const char *, int = 0);
267 ~file_iterator();
268 int fill(node **);
269 int peek();
270 int get_location(int, const char **, int *);
271 void backtrace();
272 int set_location(const char *, int);
273 int next_file(FILE *, const char *);
274 int is_file();
277 file_iterator::file_iterator(FILE *f, const char *fn, int po)
278 : fp(f), lineno(1), filename(fn), popened(po),
279 newline_flag(0), suppress_newline_flag(0), seen_escape(0)
281 if ((font::use_charnames_in_special) && (fn != 0)) {
282 if (!the_output)
283 init_output();
284 the_output->put_filename(fn);
288 file_iterator::~file_iterator()
290 close();
293 void file_iterator::close()
295 if (fp == stdin)
296 clearerr(stdin);
297 #ifndef POPEN_MISSING
298 else if (popened)
299 pclose(fp);
300 #endif /* not POPEN_MISSING */
301 else
302 fclose(fp);
305 int file_iterator::is_file()
307 return 1;
310 int file_iterator::next_file(FILE *f, const char *s)
312 close();
313 filename = s;
314 fp = f;
315 lineno = 1;
316 newline_flag = 0;
317 suppress_newline_flag = 0;
318 seen_escape = 0;
319 popened = 0;
320 ptr = 0;
321 eptr = 0;
322 return 1;
325 int file_iterator::fill(node **)
327 if (newline_flag && !suppress_newline_flag) {
328 curenv->add_html_tag_eol();
329 lineno++;
331 newline_flag = 0;
332 suppress_newline_flag = 0;
333 unsigned char *p = buf;
334 ptr = p;
335 unsigned char *e = p + BUF_SIZE;
336 while (p < e) {
337 int c = getc(fp);
338 if (c == EOF)
339 break;
340 if (invalid_input_char(c))
341 warning(WARN_INPUT, "invalid input character code %1", int(c));
342 else {
343 *p++ = c;
344 if (c == '\n') {
345 if (seen_escape && is_html)
346 suppress_newline_flag = 1;
347 seen_escape = 0;
348 newline_flag = 1;
349 break;
351 seen_escape = (c == '\\');
354 if (p > buf) {
355 eptr = p;
356 return *ptr++;
358 else {
359 eptr = p;
360 return EOF;
364 int file_iterator::peek()
366 int c = getc(fp);
367 while (invalid_input_char(c)) {
368 warning(WARN_INPUT, "invalid input character code %1", int(c));
369 c = getc(fp);
371 if (c != EOF)
372 ungetc(c, fp);
373 return c;
376 int file_iterator::get_location(int /*allow_macro*/,
377 const char **filenamep, int *linenop)
379 *linenop = lineno;
380 if (filename != 0 && strcmp(filename, "-") == 0)
381 *filenamep = "<standard input>";
382 else
383 *filenamep = filename;
384 return 1;
387 void file_iterator::backtrace()
389 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
390 popened ? "process" : "file");
393 int file_iterator::set_location(const char *f, int ln)
395 if (f) {
396 filename = f;
397 if (!the_output)
398 init_output();
399 the_output->put_filename(f);
401 lineno = ln;
402 return 1;
405 input_iterator nil_iterator;
407 class input_stack {
408 public:
409 static int get(node **);
410 static int peek();
411 static void push(input_iterator *);
412 static input_iterator *get_arg(int);
413 static int nargs();
414 static int get_location(int, const char **, int *);
415 static int set_location(const char *, int);
416 static void backtrace();
417 static void backtrace_all();
418 static void next_file(FILE *, const char *);
419 static void end_file();
420 static void shift(int n);
421 static void add_boundary();
422 static void add_return_boundary();
423 static int is_return_boundary();
424 static void remove_boundary();
425 static int get_level();
426 static void clear();
427 static void pop_macro();
428 static void save_compatible_flag(int);
429 static int get_compatible_flag();
431 static int limit;
432 private:
433 static input_iterator *top;
434 static int level;
436 static int finish_get(node **);
437 static int finish_peek();
440 input_iterator *input_stack::top = &nil_iterator;
441 int input_stack::level = 0;
442 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
444 inline int input_stack::get_level()
446 return level + top->internal_level();
449 inline int input_stack::get(node **np)
451 return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
454 int input_stack::finish_get(node **np)
456 for (;;) {
457 int c = top->fill(np);
458 if (c != EOF || top->is_boundary())
459 return c;
460 if (top == &nil_iterator)
461 break;
462 input_iterator *tem = top;
463 top = top->next;
464 level--;
465 delete tem;
466 if (top->ptr < top->eptr)
467 return *top->ptr++;
469 assert(level == 0);
470 return EOF;
473 inline int input_stack::peek()
475 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
478 int input_stack::finish_peek()
480 for (;;) {
481 int c = top->peek();
482 if (c != EOF || top->is_boundary())
483 return c;
484 if (top == &nil_iterator)
485 break;
486 input_iterator *tem = top;
487 top = top->next;
488 level--;
489 delete tem;
490 if (top->ptr < top->eptr)
491 return *top->ptr;
493 assert(level == 0);
494 return EOF;
497 void input_stack::add_boundary()
499 push(new input_boundary);
502 void input_stack::add_return_boundary()
504 push(new input_return_boundary);
507 int input_stack::is_return_boundary()
509 return top->is_boundary() == 2;
512 void input_stack::remove_boundary()
514 assert(top->is_boundary());
515 input_iterator *temp = top->next;
516 delete top;
517 top = temp;
518 level--;
521 void input_stack::push(input_iterator *in)
523 if (in == 0)
524 return;
525 if (++level > limit && limit > 0)
526 fatal("input stack limit exceeded (probable infinite loop)");
527 in->next = top;
528 top = in;
531 input_iterator *input_stack::get_arg(int i)
533 input_iterator *p;
534 for (p = top; p != 0; p = p->next)
535 if (p->has_args())
536 return p->get_arg(i);
537 return 0;
540 void input_stack::shift(int n)
542 for (input_iterator *p = top; p; p = p->next)
543 if (p->has_args()) {
544 p->shift(n);
545 return;
549 int input_stack::nargs()
551 for (input_iterator *p =top; p != 0; p = p->next)
552 if (p->has_args())
553 return p->nargs();
554 return 0;
557 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
559 for (input_iterator *p = top; p; p = p->next)
560 if (p->get_location(allow_macro, filenamep, linenop))
561 return 1;
562 return 0;
565 void input_stack::backtrace()
567 const char *f;
568 int n;
569 // only backtrace down to (not including) the topmost file
570 for (input_iterator *p = top;
571 p && !p->get_location(0, &f, &n);
572 p = p->next)
573 p->backtrace();
576 void input_stack::backtrace_all()
578 for (input_iterator *p = top; p; p = p->next)
579 p->backtrace();
582 int input_stack::set_location(const char *filename, int lineno)
584 for (input_iterator *p = top; p; p = p->next)
585 if (p->set_location(filename, lineno))
586 return 1;
587 return 0;
590 void input_stack::next_file(FILE *fp, const char *s)
592 input_iterator **pp;
593 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
594 if ((*pp)->next_file(fp, s))
595 return;
596 if (++level > limit && limit > 0)
597 fatal("input stack limit exceeded");
598 *pp = new file_iterator(fp, s);
599 (*pp)->next = &nil_iterator;
602 void input_stack::end_file()
604 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
605 if ((*pp)->is_file()) {
606 input_iterator *tem = *pp;
607 *pp = (*pp)->next;
608 delete tem;
609 level--;
610 return;
614 void input_stack::clear()
616 int nboundaries = 0;
617 while (top != &nil_iterator) {
618 if (top->is_boundary())
619 nboundaries++;
620 input_iterator *tem = top;
621 top = top->next;
622 level--;
623 delete tem;
625 // Keep while_request happy.
626 for (; nboundaries > 0; --nboundaries)
627 add_return_boundary();
630 void input_stack::pop_macro()
632 int nboundaries = 0;
633 int is_macro = 0;
634 do {
635 if (top->next == &nil_iterator)
636 break;
637 if (top->is_boundary())
638 nboundaries++;
639 is_macro = top->is_macro();
640 input_iterator *tem = top;
641 top = top->next;
642 level--;
643 delete tem;
644 } while (!is_macro);
645 // Keep while_request happy.
646 for (; nboundaries > 0; --nboundaries)
647 add_return_boundary();
650 inline void input_stack::save_compatible_flag(int f)
652 top->save_compatible_flag(f);
655 inline int input_stack::get_compatible_flag()
657 return top->get_compatible_flag();
660 void backtrace_request()
662 input_stack::backtrace_all();
663 fflush(stderr);
664 skip_line();
667 void next_file()
669 symbol nm = get_long_name(0);
670 while (!tok.newline() && !tok.eof())
671 tok.next();
672 if (nm.is_null())
673 input_stack::end_file();
674 else {
675 errno = 0;
676 FILE *fp = fopen(nm.contents(), "r");
677 if (!fp)
678 error("can't open `%1': %2", nm.contents(), strerror(errno));
679 else
680 input_stack::next_file(fp, nm.contents());
682 tok.next();
685 void shift()
687 int n;
688 if (!has_arg() || !get_integer(&n))
689 n = 1;
690 input_stack::shift(n);
691 skip_line();
694 static int get_char_for_escape_name()
696 int c = get_copy(0);
697 switch (c) {
698 case EOF:
699 copy_mode_error("end of input in escape name");
700 return '\0';
701 default:
702 if (!invalid_input_char(c))
703 break;
704 // fall through
705 case '\n':
706 if (c == '\n')
707 input_stack::push(make_temp_iterator("\n"));
708 case ' ':
709 case '\t':
710 case '\001':
711 case '\b':
712 copy_mode_error("%1 is not allowed in an escape name",
713 input_char_description(c));
714 return '\0';
716 return c;
719 static symbol read_two_char_escape_name()
721 char buf[3];
722 buf[0] = get_char_for_escape_name();
723 if (buf[0] != '\0') {
724 buf[1] = get_char_for_escape_name();
725 if (buf[1] == '\0')
726 buf[0] = 0;
727 else
728 buf[2] = 0;
730 return symbol(buf);
733 static symbol read_long_escape_name(int no_empty)
735 int start_level = input_stack::get_level();
736 char abuf[ABUF_SIZE];
737 char *buf = abuf;
738 int buf_size = ABUF_SIZE;
739 int i = 0;
740 for (;;) {
741 int c = get_char_for_escape_name();
742 if (c == 0) {
743 if (buf != abuf)
744 a_delete buf;
745 return NULL_SYMBOL;
747 if (i + 2 > buf_size) {
748 if (buf == abuf) {
749 buf = new char[ABUF_SIZE*2];
750 memcpy(buf, abuf, buf_size);
751 buf_size = ABUF_SIZE*2;
753 else {
754 char *old_buf = buf;
755 buf = new char[buf_size*2];
756 memcpy(buf, old_buf, buf_size);
757 buf_size *= 2;
758 a_delete old_buf;
761 if (c == ']' && input_stack::get_level() == start_level)
762 break;
763 buf[i++] = c;
765 buf[i] = 0;
766 if (buf == abuf) {
767 if (i == 0) {
768 if (no_empty)
769 copy_mode_error("empty escape name");
770 return EMPTY_SYMBOL;
772 return symbol(abuf);
774 else {
775 symbol s(buf);
776 a_delete buf;
777 return s;
781 static symbol read_escape_name(int no_empty)
783 int c = get_char_for_escape_name();
784 if (c == 0)
785 return NULL_SYMBOL;
786 if (c == '(')
787 return read_two_char_escape_name();
788 if (c == '[' && !compatible_flag)
789 return read_long_escape_name(no_empty);
790 char buf[2];
791 buf[0] = c;
792 buf[1] = '\0';
793 return symbol(buf);
796 static symbol read_increment_and_escape_name(int *incp)
798 int c = get_char_for_escape_name();
799 switch (c) {
800 case 0:
801 *incp = 0;
802 return NULL_SYMBOL;
803 case '(':
804 *incp = 0;
805 return read_two_char_escape_name();
806 case '+':
807 *incp = 1;
808 return read_escape_name();
809 case '-':
810 *incp = -1;
811 return read_escape_name();
812 case '[':
813 if (!compatible_flag) {
814 *incp = 0;
815 return read_long_escape_name();
817 break;
819 *incp = 0;
820 char buf[2];
821 buf[0] = c;
822 buf[1] = '\0';
823 return symbol(buf);
826 static int get_copy(node **nd, int defining)
828 for (;;) {
829 int c = input_stack::get(nd);
830 if (c == ESCAPE_NEWLINE) {
831 if (defining)
832 return c;
833 do {
834 c = input_stack::get(nd);
835 } while (c == ESCAPE_NEWLINE);
837 if (c != escape_char || escape_char <= 0)
838 return c;
839 c = input_stack::peek();
840 switch(c) {
841 case 0:
842 return escape_char;
843 case '"':
844 (void)input_stack::get(0);
845 while ((c = input_stack::get(0)) != '\n' && c != EOF)
847 return c;
848 case '#': // Like \" but newline is ignored.
849 (void)input_stack::get(0);
850 while ((c = input_stack::get(0)) != '\n')
851 if (c == EOF)
852 return EOF;
853 break;
854 case '$':
856 (void)input_stack::get(0);
857 symbol s = read_escape_name();
858 if (!(s.is_null() || s.is_empty()))
859 interpolate_arg(s);
860 break;
862 case '*':
864 (void)input_stack::get(0);
865 symbol s = read_escape_name();
866 if (!(s.is_null() || s.is_empty()))
867 interpolate_string(s);
868 break;
870 case 'a':
871 (void)input_stack::get(0);
872 return '\001';
873 case 'e':
874 (void)input_stack::get(0);
875 return ESCAPE_e;
876 case 'E':
877 (void)input_stack::get(0);
878 return ESCAPE_E;
879 case 'n':
881 (void)input_stack::get(0);
882 int inc;
883 symbol s = read_increment_and_escape_name(&inc);
884 if (!(s.is_null() || s.is_empty()))
885 interpolate_number_reg(s, inc);
886 break;
888 case 'g':
890 (void)input_stack::get(0);
891 symbol s = read_escape_name();
892 if (!(s.is_null() || s.is_empty()))
893 interpolate_number_format(s);
894 break;
896 case 't':
897 (void)input_stack::get(0);
898 return '\t';
899 case 'V':
901 (void)input_stack::get(0);
902 symbol s = read_escape_name();
903 if (!(s.is_null() || s.is_empty()))
904 interpolate_environment_variable(s);
905 break;
907 case '\n':
908 (void)input_stack::get(0);
909 if (defining)
910 return ESCAPE_NEWLINE;
911 break;
912 case ' ':
913 (void)input_stack::get(0);
914 return ESCAPE_SPACE;
915 case '~':
916 (void)input_stack::get(0);
917 return ESCAPE_TILDE;
918 case ':':
919 (void)input_stack::get(0);
920 return ESCAPE_COLON;
921 case '|':
922 (void)input_stack::get(0);
923 return ESCAPE_BAR;
924 case '^':
925 (void)input_stack::get(0);
926 return ESCAPE_CIRCUMFLEX;
927 case '{':
928 (void)input_stack::get(0);
929 return ESCAPE_LEFT_BRACE;
930 case '}':
931 (void)input_stack::get(0);
932 return ESCAPE_RIGHT_BRACE;
933 case '`':
934 (void)input_stack::get(0);
935 return ESCAPE_LEFT_QUOTE;
936 case '\'':
937 (void)input_stack::get(0);
938 return ESCAPE_RIGHT_QUOTE;
939 case '-':
940 (void)input_stack::get(0);
941 return ESCAPE_HYPHEN;
942 case '_':
943 (void)input_stack::get(0);
944 return ESCAPE_UNDERSCORE;
945 case 'c':
946 (void)input_stack::get(0);
947 return ESCAPE_c;
948 case '!':
949 (void)input_stack::get(0);
950 return ESCAPE_BANG;
951 case '?':
952 (void)input_stack::get(0);
953 return ESCAPE_QUESTION;
954 case '&':
955 (void)input_stack::get(0);
956 return ESCAPE_AMPERSAND;
957 case ')':
958 (void)input_stack::get(0);
959 return ESCAPE_RIGHT_PARENTHESIS;
960 case '.':
961 (void)input_stack::get(0);
962 return c;
963 case '%':
964 (void)input_stack::get(0);
965 return ESCAPE_PERCENT;
966 default:
967 if (c == escape_char) {
968 (void)input_stack::get(0);
969 return c;
971 else
972 return escape_char;
977 class non_interpreted_char_node : public node {
978 unsigned char c;
979 public:
980 non_interpreted_char_node(unsigned char);
981 node *copy();
982 int interpret(macro *);
983 int same(node *);
984 const char *type();
985 int force_tprint();
988 int non_interpreted_char_node::same(node *nd)
990 return c == ((non_interpreted_char_node *)nd)->c;
993 const char *non_interpreted_char_node::type()
995 return "non_interpreted_char_node";
998 int non_interpreted_char_node::force_tprint()
1000 return 0;
1003 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1005 assert(n != 0);
1008 node *non_interpreted_char_node::copy()
1010 return new non_interpreted_char_node(c);
1013 int non_interpreted_char_node::interpret(macro *mac)
1015 mac->append(c);
1016 return 1;
1019 static void do_width();
1020 static node *do_non_interpreted();
1021 static node *do_special();
1022 static node *do_suppress(symbol nm);
1023 static void do_register();
1025 dictionary color_dictionary(501);
1026 static symbol default_symbol("default");
1028 static color *lookup_color(symbol nm)
1030 assert(!nm.is_null());
1031 if (nm == default_symbol)
1032 return &default_color;
1033 color *c = (color *)color_dictionary.lookup(nm);
1034 if (c == 0)
1035 warning(WARN_COLOR, "`%1' not defined", nm.contents());
1036 return c;
1039 static node *do_glyph_color(symbol nm)
1041 if (nm.is_null())
1042 return 0;
1043 if (nm.is_empty())
1044 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1045 else {
1046 color *tem = lookup_color(nm);
1047 if (tem)
1048 curenv->set_glyph_color(tem);
1049 else
1050 (void)color_dictionary.lookup(nm, new color);
1052 return new glyph_color_node(curenv->get_glyph_color());
1055 static node *do_fill_color(symbol nm)
1057 if (nm.is_null())
1058 return 0;
1059 if (nm.is_empty())
1060 curenv->set_fill_color(curenv->get_prev_fill_color());
1061 else {
1062 color *tem = lookup_color(nm);
1063 if (tem)
1064 curenv->set_fill_color(tem);
1065 else
1066 (void)color_dictionary.lookup(nm, new color);
1068 return new fill_color_node(curenv->get_fill_color());
1071 static unsigned int get_color_element(const char *scheme, const char *col)
1073 units val;
1074 if (!get_number(&val, 'f')) {
1075 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1076 tok.next();
1077 return 0;
1079 if (val < 0) {
1080 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1081 return 0;
1083 if (val > color::MAX_COLOR_VAL+1) {
1084 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1085 // we change 0x10000 to 0xffff
1086 return color::MAX_COLOR_VAL;
1088 return (unsigned int)val;
1091 static color *read_rgb()
1093 symbol component = get_long_name(0);
1094 if (component.is_null()) {
1095 warning(WARN_COLOR, "missing rgb color values");
1096 return 0;
1098 const char *s = component.contents();
1099 color *col = new color;
1100 if (*s == '#') {
1101 if (!col->read_rgb(s)) {
1102 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1103 delete col;
1104 return 0;
1107 else {
1108 input_stack::push(make_temp_iterator(" "));
1109 input_stack::push(make_temp_iterator(s));
1110 unsigned int r = get_color_element("rgb color", "red component");
1111 unsigned int g = get_color_element("rgb color", "green component");
1112 unsigned int b = get_color_element("rgb color", "blue component");
1113 col->set_rgb(r, g, b);
1115 return col;
1118 static color *read_cmy()
1120 symbol component = get_long_name(0);
1121 if (component.is_null()) {
1122 warning(WARN_COLOR, "missing cmy color values");
1123 return 0;
1125 const char *s = component.contents();
1126 color *col = new color;
1127 if (*s == '#') {
1128 if (!col->read_cmy(s)) {
1129 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1130 delete col;
1131 return 0;
1134 else {
1135 input_stack::push(make_temp_iterator(" "));
1136 input_stack::push(make_temp_iterator(s));
1137 unsigned int c = get_color_element("cmy color", "cyan component");
1138 unsigned int m = get_color_element("cmy color", "magenta component");
1139 unsigned int y = get_color_element("cmy color", "yellow component");
1140 col->set_cmy(c, m, y);
1142 return col;
1145 static color *read_cmyk()
1147 symbol component = get_long_name(0);
1148 if (component.is_null()) {
1149 warning(WARN_COLOR, "missing cmyk color values");
1150 return 0;
1152 const char *s = component.contents();
1153 color *col = new color;
1154 if (*s == '#') {
1155 if (!col->read_cmyk(s)) {
1156 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1157 delete col;
1158 return 0;
1161 else {
1162 input_stack::push(make_temp_iterator(" "));
1163 input_stack::push(make_temp_iterator(s));
1164 unsigned int c = get_color_element("cmyk color", "cyan component");
1165 unsigned int m = get_color_element("cmyk color", "magenta component");
1166 unsigned int y = get_color_element("cmyk color", "yellow component");
1167 unsigned int k = get_color_element("cmyk color", "black component");
1168 col->set_cmyk(c, m, y, k);
1170 return col;
1173 static color *read_gray()
1175 symbol component = get_long_name(0);
1176 if (component.is_null()) {
1177 warning(WARN_COLOR, "missing gray values");
1178 return 0;
1180 const char *s = component.contents();
1181 color *col = new color;
1182 if (*s == '#') {
1183 if (!col->read_gray(s)) {
1184 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1185 delete col;
1186 return 0;
1189 else {
1190 input_stack::push(make_temp_iterator(" "));
1191 input_stack::push(make_temp_iterator(s));
1192 unsigned int g = get_color_element("gray", "gray value");
1193 col->set_gray(g);
1195 return col;
1198 static void define_color()
1200 symbol color_name = get_long_name(1);
1201 if (color_name.is_null()) {
1202 skip_line();
1203 return;
1205 if (color_name == default_symbol) {
1206 warning(WARN_COLOR, "default color can't be redefined");
1207 skip_line();
1208 return;
1210 symbol style = get_long_name(1);
1211 if (style.is_null()) {
1212 skip_line();
1213 return;
1215 color *col;
1216 if (strcmp(style.contents(), "rgb") == 0)
1217 col = read_rgb();
1218 else if (strcmp(style.contents(), "cmyk") == 0)
1219 col = read_cmyk();
1220 else if (strcmp(style.contents(), "gray") == 0)
1221 col = read_gray();
1222 else if (strcmp(style.contents(), "grey") == 0)
1223 col = read_gray();
1224 else if (strcmp(style.contents(), "cmy") == 0)
1225 col = read_cmy();
1226 else {
1227 warning(WARN_COLOR,
1228 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1229 style.contents());
1230 skip_line();
1231 return;
1233 if (col)
1234 (void)color_dictionary.lookup(color_name, col);
1235 skip_line();
1238 static node *do_overstrike()
1240 token start;
1241 overstrike_node *on = new overstrike_node;
1242 int start_level = input_stack::get_level();
1243 start.next();
1244 for (;;) {
1245 tok.next();
1246 if (tok.newline() || tok.eof()) {
1247 warning(WARN_DELIM, "missing closing delimiter");
1248 break;
1250 if (tok == start
1251 && (compatible_flag || input_stack::get_level() == start_level))
1252 break;
1253 charinfo *ci = tok.get_char(1);
1254 if (ci) {
1255 node *n = curenv->make_char_node(ci);
1256 if (n)
1257 on->overstrike(n);
1260 return on;
1263 static node *do_bracket()
1265 token start;
1266 bracket_node *bn = new bracket_node;
1267 start.next();
1268 int start_level = input_stack::get_level();
1269 for (;;) {
1270 tok.next();
1271 if (tok.eof()) {
1272 warning(WARN_DELIM, "missing closing delimiter");
1273 break;
1275 if (tok.newline()) {
1276 warning(WARN_DELIM, "missing closing delimiter");
1277 input_stack::push(make_temp_iterator("\n"));
1278 break;
1280 if (tok == start
1281 && (compatible_flag || input_stack::get_level() == start_level))
1282 break;
1283 charinfo *ci = tok.get_char(1);
1284 if (ci) {
1285 node *n = curenv->make_char_node(ci);
1286 if (n)
1287 bn->bracket(n);
1290 return bn;
1293 static int do_name_test()
1295 token start;
1296 start.next();
1297 int start_level = input_stack::get_level();
1298 int bad_char = 0;
1299 int some_char = 0;
1300 for (;;) {
1301 tok.next();
1302 if (tok.newline() || tok.eof()) {
1303 warning(WARN_DELIM, "missing closing delimiter");
1304 break;
1306 if (tok == start
1307 && (compatible_flag || input_stack::get_level() == start_level))
1308 break;
1309 if (!tok.ch())
1310 bad_char = 1;
1311 some_char = 1;
1313 return some_char && !bad_char;
1316 static int do_expr_test()
1318 token start;
1319 start.next();
1320 int start_level = input_stack::get_level();
1321 if (!start.delimiter(1))
1322 return 0;
1323 tok.next();
1324 // disable all warning and error messages temporarily
1325 int saved_warning_mask = warning_mask;
1326 int saved_inhibit_errors = inhibit_errors;
1327 warning_mask = 0;
1328 inhibit_errors = 1;
1329 int dummy;
1330 int result = get_number_rigidly(&dummy, 'u');
1331 warning_mask = saved_warning_mask;
1332 inhibit_errors = saved_inhibit_errors;
1333 if (tok == start && input_stack::get_level() == start_level)
1334 return result;
1335 // ignore everything up to the delimiter in case we aren't right there
1336 for (;;) {
1337 tok.next();
1338 if (tok.newline() || tok.eof()) {
1339 warning(WARN_DELIM, "missing closing delimiter");
1340 break;
1342 if (tok == start && input_stack::get_level() == start_level)
1343 break;
1345 return 0;
1348 #if 0
1349 static node *do_zero_width()
1351 token start;
1352 start.next();
1353 int start_level = input_stack::get_level();
1354 environment env(curenv);
1355 environment *oldenv = curenv;
1356 curenv = &env;
1357 for (;;) {
1358 tok.next();
1359 if (tok.newline() || tok.eof()) {
1360 error("missing closing delimiter");
1361 break;
1363 if (tok == start
1364 && (compatible_flag || input_stack::get_level() == start_level))
1365 break;
1366 tok.process();
1368 curenv = oldenv;
1369 node *rev = env.extract_output_line();
1370 node *n = 0;
1371 while (rev) {
1372 node *tem = rev;
1373 rev = rev->next;
1374 tem->next = n;
1375 n = tem;
1377 return new zero_width_node(n);
1380 #else
1382 // It's undesirable for \Z to change environments, because then
1383 // \n(.w won't work as expected.
1385 static node *do_zero_width()
1387 node *rev = new dummy_node;
1388 token start;
1389 start.next();
1390 int start_level = input_stack::get_level();
1391 for (;;) {
1392 tok.next();
1393 if (tok.newline() || tok.eof()) {
1394 warning(WARN_DELIM, "missing closing delimiter");
1395 break;
1397 if (tok == start
1398 && (compatible_flag || input_stack::get_level() == start_level))
1399 break;
1400 if (!tok.add_to_node_list(&rev))
1401 error("invalid token in argument to \\Z");
1403 node *n = 0;
1404 while (rev) {
1405 node *tem = rev;
1406 rev = rev->next;
1407 tem->next = n;
1408 n = tem;
1410 return new zero_width_node(n);
1413 #endif
1415 token_node *node::get_token_node()
1417 return 0;
1420 class token_node : public node {
1421 public:
1422 token tk;
1423 token_node(const token &t);
1424 node *copy();
1425 token_node *get_token_node();
1426 int same(node *);
1427 const char *type();
1428 int force_tprint();
1431 token_node::token_node(const token &t) : tk(t)
1435 node *token_node::copy()
1437 return new token_node(tk);
1440 token_node *token_node::get_token_node()
1442 return this;
1445 int token_node::same(node *nd)
1447 return tk == ((token_node *)nd)->tk;
1450 const char *token_node::type()
1452 return "token_node";
1455 int token_node::force_tprint()
1457 return 0;
1460 token::token() : nd(0), type(TOKEN_EMPTY)
1464 token::~token()
1466 delete nd;
1469 token::token(const token &t)
1470 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1472 // Use two statements to work around bug in SGI C++.
1473 node *tem = t.nd;
1474 nd = tem ? tem->copy() : 0;
1477 void token::operator=(const token &t)
1479 delete nd;
1480 nm = t.nm;
1481 // Use two statements to work around bug in SGI C++.
1482 node *tem = t.nd;
1483 nd = tem ? tem->copy() : 0;
1484 c = t.c;
1485 val = t.val;
1486 dim = t.dim;
1487 type = t.type;
1490 void token::skip()
1492 while (space())
1493 next();
1496 int has_arg()
1498 while (tok.space())
1499 tok.next();
1500 return !tok.newline();
1503 void token::make_space()
1505 type = TOKEN_SPACE;
1508 void token::make_newline()
1510 type = TOKEN_NEWLINE;
1513 void token::next()
1515 if (nd) {
1516 delete nd;
1517 nd = 0;
1519 units x;
1520 for (;;) {
1521 node *n;
1522 int cc = input_stack::get(&n);
1523 if (cc != escape_char || escape_char == 0) {
1524 handle_normal_char:
1525 switch(cc) {
1526 case COMPATIBLE_SAVE:
1527 input_stack::save_compatible_flag(compatible_flag);
1528 compatible_flag = 0;
1529 continue;
1530 case COMPATIBLE_RESTORE:
1531 compatible_flag = input_stack::get_compatible_flag();
1532 continue;
1533 case EOF:
1534 type = TOKEN_EOF;
1535 return;
1536 case TRANSPARENT_FILE_REQUEST:
1537 case TITLE_REQUEST:
1538 case COPY_FILE_REQUEST:
1539 #ifdef COLUMN
1540 case VJUSTIFY_REQUEST:
1541 #endif /* COLUMN */
1542 type = TOKEN_REQUEST;
1543 c = cc;
1544 return;
1545 case BEGIN_TRAP:
1546 type = TOKEN_BEGIN_TRAP;
1547 return;
1548 case END_TRAP:
1549 type = TOKEN_END_TRAP;
1550 return;
1551 case LAST_PAGE_EJECTOR:
1552 seen_last_page_ejector = 1;
1553 // fall through
1554 case PAGE_EJECTOR:
1555 type = TOKEN_PAGE_EJECTOR;
1556 return;
1557 case ESCAPE_PERCENT:
1558 ESCAPE_PERCENT:
1559 type = TOKEN_HYPHEN_INDICATOR;
1560 return;
1561 case ESCAPE_SPACE:
1562 ESCAPE_SPACE:
1563 type = TOKEN_NODE;
1564 nd = new space_char_hmotion_node(curenv->get_space_width());
1565 return;
1566 case ESCAPE_TILDE:
1567 ESCAPE_TILDE:
1568 type = TOKEN_STRETCHABLE_SPACE;
1569 return;
1570 case ESCAPE_COLON:
1571 ESCAPE_COLON:
1572 type = TOKEN_ZERO_WIDTH_BREAK;
1573 return;
1574 case ESCAPE_e:
1575 ESCAPE_e:
1576 type = TOKEN_ESCAPE;
1577 return;
1578 case ESCAPE_E:
1579 goto handle_escape_char;
1580 case ESCAPE_BAR:
1581 ESCAPE_BAR:
1582 type = TOKEN_NODE;
1583 nd = new hmotion_node(curenv->get_narrow_space_width());
1584 return;
1585 case ESCAPE_CIRCUMFLEX:
1586 ESCAPE_CIRCUMFLEX:
1587 type = TOKEN_NODE;
1588 nd = new hmotion_node(curenv->get_half_narrow_space_width());
1589 return;
1590 case ESCAPE_NEWLINE:
1591 break;
1592 case ESCAPE_LEFT_BRACE:
1593 ESCAPE_LEFT_BRACE:
1594 type = TOKEN_LEFT_BRACE;
1595 return;
1596 case ESCAPE_RIGHT_BRACE:
1597 ESCAPE_RIGHT_BRACE:
1598 type = TOKEN_RIGHT_BRACE;
1599 return;
1600 case ESCAPE_LEFT_QUOTE:
1601 ESCAPE_LEFT_QUOTE:
1602 type = TOKEN_SPECIAL;
1603 nm = symbol("ga");
1604 return;
1605 case ESCAPE_RIGHT_QUOTE:
1606 ESCAPE_RIGHT_QUOTE:
1607 type = TOKEN_SPECIAL;
1608 nm = symbol("aa");
1609 return;
1610 case ESCAPE_HYPHEN:
1611 ESCAPE_HYPHEN:
1612 type = TOKEN_SPECIAL;
1613 nm = symbol("-");
1614 return;
1615 case ESCAPE_UNDERSCORE:
1616 ESCAPE_UNDERSCORE:
1617 type = TOKEN_SPECIAL;
1618 nm = symbol("ul");
1619 return;
1620 case ESCAPE_c:
1621 ESCAPE_c:
1622 type = TOKEN_INTERRUPT;
1623 return;
1624 case ESCAPE_BANG:
1625 ESCAPE_BANG:
1626 type = TOKEN_TRANSPARENT;
1627 return;
1628 case ESCAPE_QUESTION:
1629 ESCAPE_QUESTION:
1630 nd = do_non_interpreted();
1631 if (nd) {
1632 type = TOKEN_NODE;
1633 return;
1635 break;
1636 case ESCAPE_AMPERSAND:
1637 ESCAPE_AMPERSAND:
1638 type = TOKEN_DUMMY;
1639 return;
1640 case ESCAPE_RIGHT_PARENTHESIS:
1641 ESCAPE_RIGHT_PARENTHESIS:
1642 type = TOKEN_TRANSPARENT_DUMMY;
1643 return;
1644 case '\b':
1645 type = TOKEN_BACKSPACE;
1646 return;
1647 case ' ':
1648 type = TOKEN_SPACE;
1649 return;
1650 case '\t':
1651 type = TOKEN_TAB;
1652 return;
1653 case '\n':
1654 type = TOKEN_NEWLINE;
1655 return;
1656 case '\001':
1657 type = TOKEN_LEADER;
1658 return;
1659 case 0:
1661 assert(n != 0);
1662 token_node *tn = n->get_token_node();
1663 if (tn) {
1664 *this = tn->tk;
1665 delete tn;
1667 else {
1668 nd = n;
1669 type = TOKEN_NODE;
1672 return;
1673 default:
1674 type = TOKEN_CHAR;
1675 c = cc;
1676 return;
1679 else {
1680 handle_escape_char:
1681 cc = input_stack::get(0);
1682 switch(cc) {
1683 case '(':
1684 nm = read_two_char_escape_name();
1685 type = TOKEN_SPECIAL;
1686 return;
1687 case EOF:
1688 type = TOKEN_EOF;
1689 error("end of input after escape character");
1690 return;
1691 case '`':
1692 goto ESCAPE_LEFT_QUOTE;
1693 case '\'':
1694 goto ESCAPE_RIGHT_QUOTE;
1695 case '-':
1696 goto ESCAPE_HYPHEN;
1697 case '_':
1698 goto ESCAPE_UNDERSCORE;
1699 case '%':
1700 goto ESCAPE_PERCENT;
1701 case ' ':
1702 goto ESCAPE_SPACE;
1703 case '0':
1704 nd = new hmotion_node(curenv->get_digit_width());
1705 type = TOKEN_NODE;
1706 return;
1707 case '|':
1708 goto ESCAPE_BAR;
1709 case '^':
1710 goto ESCAPE_CIRCUMFLEX;
1711 case '/':
1712 type = TOKEN_ITALIC_CORRECTION;
1713 return;
1714 case ',':
1715 type = TOKEN_NODE;
1716 nd = new left_italic_corrected_node;
1717 return;
1718 case '&':
1719 goto ESCAPE_AMPERSAND;
1720 case ')':
1721 goto ESCAPE_RIGHT_PARENTHESIS;
1722 case '!':
1723 goto ESCAPE_BANG;
1724 case '?':
1725 goto ESCAPE_QUESTION;
1726 case '~':
1727 goto ESCAPE_TILDE;
1728 case ':':
1729 goto ESCAPE_COLON;
1730 case '"':
1731 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1733 if (cc == '\n')
1734 type = TOKEN_NEWLINE;
1735 else
1736 type = TOKEN_EOF;
1737 return;
1738 case '#': // Like \" but newline is ignored.
1739 while ((cc = input_stack::get(0)) != '\n')
1740 if (cc == EOF) {
1741 type = TOKEN_EOF;
1742 return;
1744 break;
1745 case '$':
1747 symbol nm = read_escape_name();
1748 if (!(nm.is_null() || nm.is_empty()))
1749 interpolate_arg(nm);
1750 break;
1752 case '*':
1754 symbol nm = read_escape_name();
1755 if (!(nm.is_null() || nm.is_empty()))
1756 interpolate_string(nm);
1757 break;
1759 case 'a':
1760 nd = new non_interpreted_char_node('\001');
1761 type = TOKEN_NODE;
1762 return;
1763 case 'A':
1764 c = '0' + do_name_test();
1765 type = TOKEN_CHAR;
1766 return;
1767 case 'b':
1768 nd = do_bracket();
1769 type = TOKEN_NODE;
1770 return;
1771 case 'B':
1772 c = '0' + do_expr_test();
1773 type = TOKEN_CHAR;
1774 return;
1775 case 'c':
1776 goto ESCAPE_c;
1777 case 'C':
1778 nm = get_delim_name();
1779 if (nm.is_null())
1780 break;
1781 type = TOKEN_SPECIAL;
1782 return;
1783 case 'd':
1784 type = TOKEN_NODE;
1785 nd = new vmotion_node(curenv->get_size()/2);
1786 return;
1787 case 'D':
1788 nd = read_draw_node();
1789 if (!nd)
1790 break;
1791 type = TOKEN_NODE;
1792 return;
1793 case 'e':
1794 goto ESCAPE_e;
1795 case 'E':
1796 goto handle_escape_char;
1797 case 'f':
1799 symbol s = read_escape_name(0);
1800 if (s.is_null())
1801 break;
1802 const char *p;
1803 for (p = s.contents(); *p != '\0'; p++)
1804 if (!csdigit(*p))
1805 break;
1806 if (*p || s.is_empty())
1807 curenv->set_font(s);
1808 else
1809 curenv->set_font(atoi(s.contents()));
1810 if (!compatible_flag)
1811 have_input = 1;
1812 break;
1814 case 'F':
1816 symbol s = read_escape_name(0);
1817 if (s.is_null())
1818 break;
1819 curenv->set_family(s);
1820 break;
1822 case 'g':
1824 symbol s = read_escape_name();
1825 if (!(s.is_null() || s.is_empty()))
1826 interpolate_number_format(s);
1827 break;
1829 case 'h':
1830 if (!get_delim_number(&x, 'm'))
1831 break;
1832 type = TOKEN_NODE;
1833 nd = new hmotion_node(x);
1834 return;
1835 case 'H':
1836 // don't take height increments relative to previous height if
1837 // in compatibility mode
1838 if (!compatible_flag && curenv->get_char_height())
1840 if (get_delim_number(&x, 'z', curenv->get_char_height()))
1841 curenv->set_char_height(x);
1843 else
1845 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1846 curenv->set_char_height(x);
1848 if (!compatible_flag)
1849 have_input = 1;
1850 break;
1851 case 'k':
1852 nm = read_escape_name();
1853 if (nm.is_null() || nm.is_empty())
1854 break;
1855 type = TOKEN_MARK_INPUT;
1856 return;
1857 case 'l':
1858 case 'L':
1860 charinfo *s = 0;
1861 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1862 break;
1863 if (s == 0)
1864 s = get_charinfo(cc == 'l' ? "ru" : "br");
1865 type = TOKEN_NODE;
1866 node *n = curenv->make_char_node(s);
1867 if (cc == 'l')
1868 nd = new hline_node(x, n);
1869 else
1870 nd = new vline_node(x, n);
1871 return;
1873 case 'm':
1874 nd = do_glyph_color(read_escape_name(0));
1875 if (!nd)
1876 break;
1877 type = TOKEN_NODE;
1878 return;
1879 case 'M':
1880 nd = do_fill_color(read_escape_name(0));
1881 if (!nd)
1882 break;
1883 type = TOKEN_NODE;
1884 return;
1885 case 'n':
1887 int inc;
1888 symbol nm = read_increment_and_escape_name(&inc);
1889 if (!(nm.is_null() || nm.is_empty()))
1890 interpolate_number_reg(nm, inc);
1891 break;
1893 case 'N':
1894 if (!get_delim_number(&val, 0))
1895 break;
1896 type = TOKEN_NUMBERED_CHAR;
1897 return;
1898 case 'o':
1899 nd = do_overstrike();
1900 type = TOKEN_NODE;
1901 return;
1902 case 'O':
1903 nd = do_suppress(read_escape_name());
1904 if (!nd)
1905 break;
1906 type = TOKEN_NODE;
1907 return;
1908 case 'p':
1909 type = TOKEN_SPREAD;
1910 return;
1911 case 'r':
1912 type = TOKEN_NODE;
1913 nd = new vmotion_node(-curenv->get_size());
1914 return;
1915 case 'R':
1916 do_register();
1917 if (!compatible_flag)
1918 have_input = 1;
1919 break;
1920 case 's':
1921 if (read_size(&x))
1922 curenv->set_size(x);
1923 if (!compatible_flag)
1924 have_input = 1;
1925 break;
1926 case 'S':
1927 if (get_delim_number(&x, 0))
1928 curenv->set_char_slant(x);
1929 if (!compatible_flag)
1930 have_input = 1;
1931 break;
1932 case 't':
1933 type = TOKEN_NODE;
1934 nd = new non_interpreted_char_node('\t');
1935 return;
1936 case 'u':
1937 type = TOKEN_NODE;
1938 nd = new vmotion_node(-curenv->get_size()/2);
1939 return;
1940 case 'v':
1941 if (!get_delim_number(&x, 'v'))
1942 break;
1943 type = TOKEN_NODE;
1944 nd = new vmotion_node(x);
1945 return;
1946 case 'V':
1948 symbol nm = read_escape_name();
1949 if (!(nm.is_null() || nm.is_empty()))
1950 interpolate_environment_variable(nm);
1951 break;
1953 case 'w':
1954 do_width();
1955 break;
1956 case 'x':
1957 if (!get_delim_number(&x, 'v'))
1958 break;
1959 type = TOKEN_NODE;
1960 nd = new extra_size_node(x);
1961 return;
1962 case 'X':
1963 nd = do_special();
1964 if (!nd)
1965 break;
1966 type = TOKEN_NODE;
1967 return;
1968 case 'Y':
1970 symbol s = read_escape_name();
1971 if (s.is_null() || s.is_empty())
1972 break;
1973 request_or_macro *p = lookup_request(s);
1974 macro *m = p->to_macro();
1975 if (!m) {
1976 error("can't transparently throughput a request");
1977 break;
1979 nd = new special_node(*m);
1980 type = TOKEN_NODE;
1981 return;
1983 case 'z':
1985 next();
1986 if (type == TOKEN_NODE)
1987 nd = new zero_width_node(nd);
1988 else {
1989 charinfo *ci = get_char(1);
1990 if (ci == 0)
1991 break;
1992 node *gn = curenv->make_char_node(ci);
1993 if (gn == 0)
1994 break;
1995 nd = new zero_width_node(gn);
1996 type = TOKEN_NODE;
1998 return;
2000 case 'Z':
2001 nd = do_zero_width();
2002 if (nd == 0)
2003 break;
2004 type = TOKEN_NODE;
2005 return;
2006 case '{':
2007 goto ESCAPE_LEFT_BRACE;
2008 case '}':
2009 goto ESCAPE_RIGHT_BRACE;
2010 case '\n':
2011 break;
2012 case '[':
2013 if (!compatible_flag) {
2014 nm = read_long_escape_name();
2015 if (nm.is_null() || nm.is_empty())
2016 break;
2017 type = TOKEN_SPECIAL;
2018 return;
2020 goto handle_normal_char;
2021 default:
2022 if (cc != escape_char && cc != '.')
2023 warning(WARN_ESCAPE, "escape character ignored before %1",
2024 input_char_description(cc));
2025 goto handle_normal_char;
2031 int token::operator==(const token &t)
2033 if (type != t.type)
2034 return 0;
2035 switch(type) {
2036 case TOKEN_CHAR:
2037 return c == t.c;
2038 case TOKEN_SPECIAL:
2039 return nm == t.nm;
2040 case TOKEN_NUMBERED_CHAR:
2041 return val == t.val;
2042 default:
2043 return 1;
2047 int token::operator!=(const token &t)
2049 return !(*this == t);
2052 // is token a suitable delimiter (like ')?
2054 int token::delimiter(int err)
2056 switch(type) {
2057 case TOKEN_CHAR:
2058 switch(c) {
2059 case '0':
2060 case '1':
2061 case '2':
2062 case '3':
2063 case '4':
2064 case '5':
2065 case '6':
2066 case '7':
2067 case '8':
2068 case '9':
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 case '.':
2082 if (err)
2083 error("cannot use character `%1' as a starting delimiter", char(c));
2084 return 0;
2085 default:
2086 return 1;
2088 case TOKEN_NODE:
2089 case TOKEN_SPACE:
2090 case TOKEN_STRETCHABLE_SPACE:
2091 case TOKEN_TAB:
2092 case TOKEN_NEWLINE:
2093 if (err)
2094 error("cannot use %1 as a starting delimiter", description());
2095 return 0;
2096 default:
2097 return 1;
2101 const char *token::description()
2103 static char buf[4];
2104 switch (type) {
2105 case TOKEN_BACKSPACE:
2106 return "a backspace character";
2107 case TOKEN_CHAR:
2108 buf[0] = '`';
2109 buf[1] = c;
2110 buf[2] = '\'';
2111 buf[3] = '\0';
2112 return buf;
2113 case TOKEN_DUMMY:
2114 return "`\\&'";
2115 case TOKEN_ESCAPE:
2116 return "`\\e'";
2117 case TOKEN_HYPHEN_INDICATOR:
2118 return "`\\%'";
2119 case TOKEN_INTERRUPT:
2120 return "`\\c'";
2121 case TOKEN_ITALIC_CORRECTION:
2122 return "`\\/'";
2123 case TOKEN_LEADER:
2124 return "a leader character";
2125 case TOKEN_LEFT_BRACE:
2126 return "`\\{'";
2127 case TOKEN_MARK_INPUT:
2128 return "`\\k'";
2129 case TOKEN_NEWLINE:
2130 return "newline";
2131 case TOKEN_NODE:
2132 return "a node";
2133 case TOKEN_NUMBERED_CHAR:
2134 return "`\\N'";
2135 case TOKEN_RIGHT_BRACE:
2136 return "`\\}'";
2137 case TOKEN_SPACE:
2138 return "a space";
2139 case TOKEN_SPECIAL:
2140 return "a special character";
2141 case TOKEN_SPREAD:
2142 return "`\\p'";
2143 case TOKEN_STRETCHABLE_SPACE:
2144 return "`\\~'";
2145 case TOKEN_TAB:
2146 return "a tab character";
2147 case TOKEN_TRANSPARENT:
2148 return "`\\!'";
2149 case TOKEN_TRANSPARENT_DUMMY:
2150 return "`\\)'";
2151 case TOKEN_ZERO_WIDTH_BREAK:
2152 return "`\\:'";
2153 case TOKEN_EOF:
2154 return "end of input";
2155 default:
2156 break;
2158 return "a magic token";
2161 void skip_line()
2163 while (!tok.newline())
2164 if (tok.eof())
2165 return;
2166 else
2167 tok.next();
2168 tok.next();
2171 void compatible()
2173 int n;
2174 if (has_arg() && get_integer(&n))
2175 compatible_flag = n != 0;
2176 else
2177 compatible_flag = 1;
2178 skip_line();
2181 static void empty_name_warning(int required)
2183 if (tok.newline() || tok.eof()) {
2184 if (required)
2185 warning(WARN_MISSING, "missing name");
2187 else if (tok.right_brace() || tok.tab()) {
2188 const char *start = tok.description();
2189 do {
2190 tok.next();
2191 } while (tok.space() || tok.right_brace() || tok.tab());
2192 if (!tok.newline() && !tok.eof())
2193 error("%1 is not allowed before an argument", start);
2194 else if (required)
2195 warning(WARN_MISSING, "missing name");
2197 else if (required)
2198 error("name expected (got %1)", tok.description());
2199 else
2200 error("name expected (got %1): treated as missing", tok.description());
2203 static void non_empty_name_warning()
2205 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2206 && !tok.right_brace()
2207 // We don't want to give a warning for .el\{
2208 && !tok.left_brace())
2209 error("%1 is not allowed in a name", tok.description());
2212 symbol get_name(int required)
2214 if (compatible_flag) {
2215 char buf[3];
2216 tok.skip();
2217 if ((buf[0] = tok.ch()) != 0) {
2218 tok.next();
2219 if ((buf[1] = tok.ch()) != 0) {
2220 buf[2] = 0;
2221 tok.make_space();
2223 else
2224 non_empty_name_warning();
2225 return symbol(buf);
2227 else {
2228 empty_name_warning(required);
2229 return NULL_SYMBOL;
2232 else
2233 return get_long_name(required);
2236 symbol get_long_name(int required)
2238 while (tok.space())
2239 tok.next();
2240 char abuf[ABUF_SIZE];
2241 char *buf = abuf;
2242 int buf_size = ABUF_SIZE;
2243 int i = 0;
2244 for (;;) {
2245 if (i + 1 > buf_size) {
2246 if (buf == abuf) {
2247 buf = new char[ABUF_SIZE*2];
2248 memcpy(buf, abuf, buf_size);
2249 buf_size = ABUF_SIZE*2;
2251 else {
2252 char *old_buf = buf;
2253 buf = new char[buf_size*2];
2254 memcpy(buf, old_buf, buf_size);
2255 buf_size *= 2;
2256 a_delete old_buf;
2259 if ((buf[i] = tok.ch()) == 0)
2260 break;
2261 i++;
2262 tok.next();
2264 if (i == 0) {
2265 empty_name_warning(required);
2266 return NULL_SYMBOL;
2268 non_empty_name_warning();
2269 if (buf == abuf)
2270 return symbol(buf);
2271 else {
2272 symbol s(buf);
2273 a_delete buf;
2274 return s;
2278 void exit_troff()
2280 exit_started = 1;
2281 topdiv->set_last_page();
2282 if (!end_macro_name.is_null()) {
2283 spring_trap(end_macro_name);
2284 tok.next();
2285 process_input_stack();
2287 curenv->final_break();
2288 tok.next();
2289 process_input_stack();
2290 end_diversions();
2291 if (topdiv->get_page_length() > 0) {
2292 done_end_macro = 1;
2293 topdiv->set_ejecting();
2294 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2295 input_stack::push(make_temp_iterator((char *)buf));
2296 topdiv->space(topdiv->get_page_length(), 1);
2297 tok.next();
2298 process_input_stack();
2299 seen_last_page_ejector = 1; // should be set already
2300 topdiv->set_ejecting();
2301 push_page_ejector();
2302 topdiv->space(topdiv->get_page_length(), 1);
2303 tok.next();
2304 process_input_stack();
2306 // This will only happen if a trap-invoked macro starts a diversion,
2307 // or if vertical position traps have been disabled.
2308 cleanup_and_exit(0);
2311 // This implements .ex. The input stack must be cleared before calling
2312 // exit_troff().
2314 void exit_request()
2316 input_stack::clear();
2317 if (exit_started)
2318 tok.next();
2319 else
2320 exit_troff();
2323 void return_macro_request()
2325 input_stack::pop_macro();
2326 tok.next();
2329 void end_macro()
2331 end_macro_name = get_name();
2332 skip_line();
2335 void blank_line_macro()
2337 blank_line_macro_name = get_name();
2338 skip_line();
2341 static void trapping_blank_line()
2343 if (!blank_line_macro_name.is_null())
2344 spring_trap(blank_line_macro_name);
2345 else
2346 blank_line();
2349 void do_request()
2351 int old_compatible_flag = compatible_flag;
2352 compatible_flag = 0;
2353 symbol nm = get_name();
2354 if (nm.is_null())
2355 skip_line();
2356 else
2357 interpolate_macro(nm);
2358 compatible_flag = old_compatible_flag;
2361 inline int possibly_handle_first_page_transition()
2363 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2364 handle_first_page_transition();
2365 return 1;
2367 else
2368 return 0;
2371 static int transparent_translate(int cc)
2373 if (!invalid_input_char(cc)) {
2374 charinfo *ci = charset_table[cc];
2375 switch (ci->get_special_translation(1)) {
2376 case charinfo::TRANSLATE_SPACE:
2377 return ' ';
2378 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2379 return ESCAPE_TILDE;
2380 case charinfo::TRANSLATE_DUMMY:
2381 return ESCAPE_AMPERSAND;
2382 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2383 return ESCAPE_PERCENT;
2385 // This is really ugly.
2386 ci = ci->get_translation(1);
2387 if (ci) {
2388 int c = ci->get_ascii_code();
2389 if (c != '\0')
2390 return c;
2391 error("can't translate %1 to special character `%2'"
2392 " in transparent throughput",
2393 input_char_description(cc),
2394 ci->nm.contents());
2397 return cc;
2400 class int_stack {
2401 struct int_stack_element {
2402 int n;
2403 int_stack_element *next;
2404 } *top;
2405 public:
2406 int_stack();
2407 ~int_stack();
2408 void push(int);
2409 int is_empty();
2410 int pop();
2413 int_stack::int_stack()
2415 top = 0;
2418 int_stack::~int_stack()
2420 while (top != 0) {
2421 int_stack_element *temp = top;
2422 top = top->next;
2423 delete temp;
2427 int int_stack::is_empty()
2429 return top == 0;
2432 void int_stack::push(int n)
2434 int_stack_element *p = new int_stack_element;
2435 p->next = top;
2436 p->n = n;
2437 top = p;
2440 int int_stack::pop()
2442 assert(top != 0);
2443 int_stack_element *p = top;
2444 top = top->next;
2445 int n = p->n;
2446 delete p;
2447 return n;
2450 int node::reread(int *)
2452 return 0;
2455 int diverted_space_node::reread(int *bolp)
2457 if (curenv->get_fill())
2458 trapping_blank_line();
2459 else
2460 curdiv->space(n);
2461 *bolp = 1;
2462 return 1;
2465 int diverted_copy_file_node::reread(int *bolp)
2467 curdiv->copy_file(filename.contents());
2468 *bolp = 1;
2469 return 1;
2472 int word_space_node::reread(int *bolp)
2474 if (unformat) {
2475 for (width_list *w = orig_width; w; w = w->next)
2476 curenv->space(w->width, w->sentence_width);
2477 unformat = 0;
2478 return 1;
2480 return 0;
2483 int unbreakable_space_node::reread(int *)
2485 return 0;
2488 int hmotion_node::reread(int *bolp)
2490 if (unformat && was_tab) {
2491 curenv->handle_tab(0);
2492 unformat = 0;
2493 return 1;
2495 return 0;
2498 void process_input_stack()
2500 int_stack trap_bol_stack;
2501 int bol = 1;
2502 for (;;) {
2503 int suppress_next = 0;
2504 switch (tok.type) {
2505 case token::TOKEN_CHAR:
2507 unsigned char ch = tok.c;
2508 if (bol && !have_input
2509 && (ch == curenv->control_char
2510 || ch == curenv->no_break_control_char)) {
2511 break_flag = ch == curenv->control_char;
2512 // skip tabs as well as spaces here
2513 do {
2514 tok.next();
2515 } while (tok.white_space());
2516 symbol nm = get_name();
2517 if (nm.is_null())
2518 skip_line();
2519 else
2520 interpolate_macro(nm);
2521 suppress_next = 1;
2522 have_input = 0;
2524 else {
2525 if (possibly_handle_first_page_transition())
2527 else {
2528 for (;;) {
2529 curenv->add_char(charset_table[ch]);
2530 tok.next();
2531 if (tok.type != token::TOKEN_CHAR)
2532 break;
2533 ch = tok.c;
2535 suppress_next = 1;
2536 bol = 0;
2539 break;
2541 case token::TOKEN_TRANSPARENT:
2543 if (bol) {
2544 if (possibly_handle_first_page_transition())
2546 else {
2547 int cc;
2548 do {
2549 node *n;
2550 cc = get_copy(&n);
2551 if (cc != EOF)
2552 if (cc != '\0')
2553 curdiv->transparent_output(transparent_translate(cc));
2554 else
2555 curdiv->transparent_output(n);
2556 } while (cc != '\n' && cc != EOF);
2557 if (cc == EOF)
2558 curdiv->transparent_output('\n');
2561 break;
2563 case token::TOKEN_NEWLINE:
2565 if (bol && !have_input
2566 && !curenv->get_prev_line_interrupted())
2567 trapping_blank_line();
2568 else {
2569 curenv->newline();
2570 bol = 1;
2571 have_input = 0;
2573 break;
2575 case token::TOKEN_REQUEST:
2577 int request_code = tok.c;
2578 tok.next();
2579 switch (request_code) {
2580 case TITLE_REQUEST:
2581 title();
2582 break;
2583 case COPY_FILE_REQUEST:
2584 copy_file();
2585 break;
2586 case TRANSPARENT_FILE_REQUEST:
2587 transparent_file();
2588 break;
2589 #ifdef COLUMN
2590 case VJUSTIFY_REQUEST:
2591 vjustify();
2592 break;
2593 #endif /* COLUMN */
2594 default:
2595 assert(0);
2596 break;
2598 suppress_next = 1;
2599 have_input = 0;
2600 break;
2602 case token::TOKEN_SPACE:
2604 if (possibly_handle_first_page_transition())
2606 else if (bol && !curenv->get_prev_line_interrupted()) {
2607 int nspaces = 0;
2608 // save space_width now so that it isn't changed by \f or \s
2609 // which we wouldn't notice here
2610 hunits space_width = curenv->get_space_width();
2611 do {
2612 nspaces += tok.nspaces();
2613 tok.next();
2614 } while (tok.space());
2615 if (tok.newline())
2616 trapping_blank_line();
2617 else {
2618 push_token(tok);
2619 curenv->do_break();
2620 curenv->add_node(new hmotion_node(space_width * nspaces));
2621 bol = 0;
2624 else {
2625 curenv->space();
2626 bol = 0;
2628 break;
2630 case token::TOKEN_EOF:
2631 return;
2632 case token::TOKEN_NODE:
2634 if (possibly_handle_first_page_transition())
2636 else if (tok.nd->reread(&bol)) {
2637 delete tok.nd;
2638 tok.nd = 0;
2640 else {
2641 curenv->add_node(tok.nd);
2642 tok.nd = 0;
2643 bol = 0;
2644 curenv->possibly_break_line(1);
2646 break;
2648 case token::TOKEN_PAGE_EJECTOR:
2650 continue_page_eject();
2651 // I think we just want to preserve bol.
2652 // bol = 1;
2653 break;
2655 case token::TOKEN_BEGIN_TRAP:
2657 trap_bol_stack.push(bol);
2658 bol = 1;
2659 have_input = 0;
2660 break;
2662 case token::TOKEN_END_TRAP:
2664 if (trap_bol_stack.is_empty())
2665 error("spurious end trap token detected!");
2666 else
2667 bol = trap_bol_stack.pop();
2669 /* I'm not totally happy about this. But I can't think of any other
2670 way to do it. Doing an output_pending_lines() whenever a
2671 TOKEN_END_TRAP is detected doesn't work: for example,
2673 .wh -1i x
2674 .de x
2677 .wh -.5i y
2678 .de y
2679 .tl ''-%-''
2682 .ll .5i
2683 .sp |\n(.pu-1i-.5v
2684 a\%very\%very\%long\%word
2686 will print all but the first lines from the word immediately
2687 after the footer, rather than on the next page. */
2689 if (trap_bol_stack.is_empty())
2690 curenv->output_pending_lines();
2691 break;
2693 default:
2695 bol = 0;
2696 tok.process();
2697 break;
2700 if (!suppress_next)
2701 tok.next();
2702 trap_sprung_flag = 0;
2706 #ifdef WIDOW_CONTROL
2708 void flush_pending_lines()
2710 while (!tok.newline() && !tok.eof())
2711 tok.next();
2712 curenv->output_pending_lines();
2713 tok.next();
2716 #endif /* WIDOW_CONTROL */
2718 request_or_macro::request_or_macro()
2722 macro *request_or_macro::to_macro()
2724 return 0;
2727 request::request(REQUEST_FUNCP pp) : p(pp)
2731 void request::invoke(symbol)
2733 (*p)();
2736 struct char_block {
2737 enum { SIZE = 128 };
2738 unsigned char s[SIZE];
2739 char_block *next;
2740 char_block();
2743 char_block::char_block()
2744 : next(0)
2748 class char_list {
2749 public:
2750 char_list();
2751 ~char_list();
2752 void append(unsigned char);
2753 int length();
2754 private:
2755 unsigned char *ptr;
2756 int len;
2757 char_block *head;
2758 char_block *tail;
2759 friend class macro_header;
2760 friend class string_iterator;
2763 char_list::char_list()
2764 : ptr(0), len(0), head(0), tail(0)
2768 char_list::~char_list()
2770 while (head != 0) {
2771 char_block *tem = head;
2772 head = head->next;
2773 delete tem;
2777 int char_list::length()
2779 return len;
2782 void char_list::append(unsigned char c)
2784 if (tail == 0) {
2785 head = tail = new char_block;
2786 ptr = tail->s;
2788 else {
2789 if (ptr >= tail->s + char_block::SIZE) {
2790 tail->next = new char_block;
2791 tail = tail->next;
2792 ptr = tail->s;
2795 *ptr++ = c;
2796 len++;
2799 class node_list {
2800 node *head;
2801 node *tail;
2802 public:
2803 node_list();
2804 ~node_list();
2805 void append(node *);
2806 int length();
2807 node *extract();
2809 friend class macro_header;
2810 friend class string_iterator;
2813 void node_list::append(node *n)
2815 if (head == 0) {
2816 n->next = 0;
2817 head = tail = n;
2819 else {
2820 n->next = 0;
2821 tail = tail->next = n;
2825 int node_list::length()
2827 int total = 0;
2828 for (node *n = head; n != 0; n = n->next)
2829 ++total;
2830 return total;
2833 node_list::node_list()
2835 head = tail = 0;
2838 node *node_list::extract()
2840 node *temp = head;
2841 head = tail = 0;
2842 return temp;
2845 node_list::~node_list()
2847 delete_node_list(head);
2850 struct macro_header {
2851 public:
2852 int count;
2853 char_list cl;
2854 node_list nl;
2855 macro_header() { count = 1; }
2856 macro_header *copy(int);
2859 macro::~macro()
2861 if (p != 0 && --(p->count) <= 0)
2862 delete p;
2865 macro::macro()
2867 if (!input_stack::get_location(1, &filename, &lineno)) {
2868 filename = 0;
2869 lineno = 0;
2871 length = 0;
2872 p = 0;
2875 macro::macro(const macro &m)
2876 : p(m.p), filename(m.filename), lineno(m.lineno), length(m.length)
2878 if (p != 0)
2879 p->count++;
2882 macro &macro::operator=(const macro &m)
2884 // don't assign object
2885 if (m.p != 0)
2886 m.p->count++;
2887 if (p != 0 && --(p->count) <= 0)
2888 delete p;
2889 p = m.p;
2890 filename = m.filename;
2891 lineno = m.lineno;
2892 length = m.length;
2893 return *this;
2896 void macro::append(unsigned char c)
2898 assert(c != 0);
2899 if (p == 0)
2900 p = new macro_header;
2901 if (p->cl.length() != length) {
2902 macro_header *tem = p->copy(length);
2903 if (--(p->count) <= 0)
2904 delete p;
2905 p = tem;
2907 p->cl.append(c);
2908 ++length;
2911 void macro::append_str(const char *s)
2913 int i = 0;
2915 if (s) {
2916 while (s[i] != (char)0) {
2917 append(s[i]);
2918 i++;
2923 void macro::append(node *n)
2925 assert(n != 0);
2926 if (p == 0)
2927 p = new macro_header;
2928 if (p->cl.length() != length) {
2929 macro_header *tem = p->copy(length);
2930 if (--(p->count) <= 0)
2931 delete p;
2932 p = tem;
2934 p->cl.append(0);
2935 p->nl.append(n);
2936 ++length;
2939 void macro::append_unsigned(unsigned int i)
2941 unsigned int j = i / 10;
2942 if (j != 0)
2943 append_unsigned(j);
2944 append(((unsigned char)(((int)'0') + i % 10)));
2947 void macro::append_int(int i)
2949 if (i < 0) {
2950 append('-');
2951 i = -i;
2953 append_unsigned((unsigned int)i);
2956 void macro::print_size()
2958 errprint("%1", length);
2961 // make a copy of the first n bytes
2963 macro_header *macro_header::copy(int n)
2965 macro_header *p = new macro_header;
2966 char_block *bp = cl.head;
2967 unsigned char *ptr = bp->s;
2968 node *nd = nl.head;
2969 while (--n >= 0) {
2970 if (ptr >= bp->s + char_block::SIZE) {
2971 bp = bp->next;
2972 ptr = bp->s;
2974 int c = *ptr++;
2975 p->cl.append(c);
2976 if (c == 0) {
2977 p->nl.append(nd->copy());
2978 nd = nd->next;
2981 return p;
2984 void print_macros()
2986 object_dictionary_iterator iter(request_dictionary);
2987 request_or_macro *rm;
2988 symbol s;
2989 while (iter.get(&s, (object **)&rm)) {
2990 assert(!s.is_null());
2991 macro *m = rm->to_macro();
2992 if (m) {
2993 errprint("%1\t", s.contents());
2994 m->print_size();
2995 errprint("\n");
2998 fflush(stderr);
2999 skip_line();
3002 class string_iterator : public input_iterator {
3003 macro mac;
3004 const char *how_invoked;
3005 int newline_flag;
3006 int suppress_newline_flag; // used by html
3007 int lineno;
3008 char_block *bp;
3009 int count; // of characters remaining
3010 node *nd;
3011 int saved_compatible_flag;
3012 protected:
3013 symbol nm;
3014 string_iterator();
3015 public:
3016 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
3017 int fill(node **);
3018 int peek();
3019 int get_location(int, const char **, int *);
3020 void backtrace();
3021 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3022 int get_compatible_flag() { return saved_compatible_flag; }
3025 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3026 : mac(m), how_invoked(p),
3027 newline_flag(0), suppress_newline_flag(0), lineno(1), nm(s)
3029 count = mac.length;
3030 if (count != 0) {
3031 bp = mac.p->cl.head;
3032 nd = mac.p->nl.head;
3033 ptr = eptr = bp->s;
3035 else {
3036 bp = 0;
3037 nd = 0;
3038 ptr = eptr = 0;
3042 string_iterator::string_iterator()
3044 bp = 0;
3045 nd = 0;
3046 ptr = eptr = 0;
3047 newline_flag = 0;
3048 suppress_newline_flag = 0;
3049 how_invoked = 0;
3050 lineno = 1;
3051 count = 0;
3054 int string_iterator::fill(node **np)
3056 if (newline_flag)
3057 lineno++;
3058 newline_flag = 0;
3059 if (count <= 0)
3060 return EOF;
3061 const unsigned char *p = eptr;
3062 if (p >= bp->s + char_block::SIZE) {
3063 bp = bp->next;
3064 p = bp->s;
3066 if (*p == '\0') {
3067 if (np)
3068 *np = nd->copy();
3069 nd = nd->next;
3070 eptr = ptr = p + 1;
3071 count--;
3072 return 0;
3074 const unsigned char *e = bp->s + char_block::SIZE;
3075 if (e - p > count)
3076 e = p + count;
3077 ptr = p;
3078 while (p < e) {
3079 unsigned char c = *p;
3080 if (c == '\n' || c == ESCAPE_NEWLINE) {
3081 newline_flag = 1;
3082 if (is_html && c == ESCAPE_NEWLINE)
3083 suppress_newline_flag = 1;
3084 p++;
3085 break;
3087 if (c == '\0')
3088 break;
3089 p++;
3091 eptr = p;
3092 count -= p - ptr;
3093 return *ptr++;
3096 int string_iterator::peek()
3098 if (count <= 0)
3099 return EOF;
3100 const unsigned char *p = eptr;
3101 if (count <= 0)
3102 return EOF;
3103 if (p >= bp->s + char_block::SIZE) {
3104 p = bp->next->s;
3106 return *p;
3109 int string_iterator::get_location(int allow_macro,
3110 const char **filep, int *linep)
3112 if (!allow_macro)
3113 return 0;
3114 if (mac.filename == 0)
3115 return 0;
3116 *filep = mac.filename;
3117 *linep = mac.lineno + lineno - 1;
3118 return 1;
3121 void string_iterator::backtrace()
3123 if (mac.filename) {
3124 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3125 if (how_invoked) {
3126 if (!nm.is_null())
3127 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3128 else
3129 errprint(": %1\n", how_invoked);
3131 else
3132 errprint("\n");
3136 class temp_iterator : public input_iterator {
3137 unsigned char *base;
3138 temp_iterator(const char *, int len);
3139 public:
3140 ~temp_iterator();
3141 friend input_iterator *make_temp_iterator(const char *);
3144 #ifdef __GNUG__
3145 inline
3146 #endif
3147 temp_iterator::temp_iterator(const char *s, int len)
3149 base = new unsigned char[len];
3150 memcpy(base, s, len);
3151 ptr = base;
3152 eptr = base + len;
3155 temp_iterator::~temp_iterator()
3157 a_delete base;
3160 class small_temp_iterator : public input_iterator {
3161 private:
3162 small_temp_iterator(const char *, int);
3163 ~small_temp_iterator();
3164 enum { BLOCK = 16 };
3165 static small_temp_iterator *free_list;
3166 void *operator new(size_t);
3167 void operator delete(void *);
3168 enum { SIZE = 12 };
3169 unsigned char buf[SIZE];
3170 friend input_iterator *make_temp_iterator(const char *);
3173 small_temp_iterator *small_temp_iterator::free_list = 0;
3175 void *small_temp_iterator::operator new(size_t n)
3177 assert(n == sizeof(small_temp_iterator));
3178 if (!free_list) {
3179 free_list =
3180 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3181 for (int i = 0; i < BLOCK - 1; i++)
3182 free_list[i].next = free_list + i + 1;
3183 free_list[BLOCK-1].next = 0;
3185 small_temp_iterator *p = free_list;
3186 free_list = (small_temp_iterator *)(free_list->next);
3187 p->next = 0;
3188 return p;
3191 #ifdef __GNUG__
3192 inline
3193 #endif
3194 void small_temp_iterator::operator delete(void *p)
3196 if (p) {
3197 ((small_temp_iterator *)p)->next = free_list;
3198 free_list = (small_temp_iterator *)p;
3202 small_temp_iterator::~small_temp_iterator()
3206 #ifdef __GNUG__
3207 inline
3208 #endif
3209 small_temp_iterator::small_temp_iterator(const char *s, int len)
3211 for (int i = 0; i < len; i++)
3212 buf[i] = s[i];
3213 ptr = buf;
3214 eptr = buf + len;
3217 input_iterator *make_temp_iterator(const char *s)
3219 if (s == 0)
3220 return new small_temp_iterator(s, 0);
3221 else {
3222 int n = strlen(s);
3223 if (n <= small_temp_iterator::SIZE)
3224 return new small_temp_iterator(s, n);
3225 else
3226 return new temp_iterator(s, n);
3230 // this is used when macros are interpolated using the .macro_name notation
3232 struct arg_list {
3233 macro mac;
3234 arg_list *next;
3235 arg_list(const macro &);
3236 ~arg_list();
3239 arg_list::arg_list(const macro &m) : mac(m), next(0)
3243 arg_list::~arg_list()
3247 class macro_iterator : public string_iterator {
3248 arg_list *args;
3249 int argc;
3250 public:
3251 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
3252 macro_iterator();
3253 ~macro_iterator();
3254 int has_args() { return 1; }
3255 input_iterator *get_arg(int i);
3256 int nargs() { return argc; }
3257 void add_arg(const macro &m);
3258 void shift(int n);
3259 int is_macro() { return 1; }
3262 input_iterator *macro_iterator::get_arg(int i)
3264 if (i == 0)
3265 return make_temp_iterator(nm.contents());
3266 if (i > 0 && i <= argc) {
3267 arg_list *p = args;
3268 for (int j = 1; j < i; j++) {
3269 assert(p != 0);
3270 p = p->next;
3272 return new string_iterator(p->mac);
3274 else
3275 return 0;
3278 void macro_iterator::add_arg(const macro &m)
3280 arg_list **p;
3281 for (p = &args; *p; p = &((*p)->next))
3283 *p = new arg_list(m);
3284 ++argc;
3287 void macro_iterator::shift(int n)
3289 while (n > 0 && argc > 0) {
3290 arg_list *tem = args;
3291 args = args->next;
3292 delete tem;
3293 --argc;
3294 --n;
3298 // This gets used by eg .if '\?xxx\?''.
3300 int operator==(const macro &m1, const macro &m2)
3302 if (m1.length != m2.length)
3303 return 0;
3304 string_iterator iter1(m1);
3305 string_iterator iter2(m2);
3306 int n = m1.length;
3307 while (--n >= 0) {
3308 node *nd1 = 0;
3309 int c1 = iter1.get(&nd1);
3310 assert(c1 != EOF);
3311 node *nd2 = 0;
3312 int c2 = iter2.get(&nd2);
3313 assert(c2 != EOF);
3314 if (c1 != c2) {
3315 if (c1 == 0)
3316 delete nd1;
3317 else if (c2 == 0)
3318 delete nd2;
3319 return 0;
3321 if (c1 == 0) {
3322 assert(nd1 != 0);
3323 assert(nd2 != 0);
3324 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3325 delete nd1;
3326 delete nd2;
3327 if (!are_same)
3328 return 0;
3331 return 1;
3334 static void interpolate_macro(symbol nm)
3336 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3337 if (p == 0) {
3338 int warned = 0;
3339 const char *s = nm.contents();
3340 if (strlen(s) > 2) {
3341 request_or_macro *r;
3342 char buf[3];
3343 buf[0] = s[0];
3344 buf[1] = s[1];
3345 buf[2] = '\0';
3346 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3347 if (r) {
3348 macro *m = r->to_macro();
3349 if (!m || !m->empty())
3350 warned = warning(WARN_SPACE,
3351 "`%1' not defined (probable missing space after `%2')",
3352 nm.contents(), buf);
3355 if (!warned) {
3356 warning(WARN_MAC, "`%1' not defined", nm.contents());
3357 p = new macro;
3358 request_dictionary.define(nm, p);
3361 if (p)
3362 p->invoke(nm);
3363 else {
3364 skip_line();
3365 return;
3369 static void decode_args(macro_iterator *mi)
3371 if (!tok.newline() && !tok.eof()) {
3372 node *n;
3373 int c = get_copy(&n);
3374 for (;;) {
3375 while (c == ' ')
3376 c = get_copy(&n);
3377 if (c == '\n' || c == EOF)
3378 break;
3379 macro arg;
3380 int quote_input_level = 0;
3381 int done_tab_warning = 0;
3382 if (c == '\"') {
3383 quote_input_level = input_stack::get_level();
3384 c = get_copy(&n);
3386 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3387 if (quote_input_level > 0 && c == '\"'
3388 && (compatible_flag
3389 || input_stack::get_level() == quote_input_level)) {
3390 c = get_copy(&n);
3391 if (c == '"') {
3392 arg.append(c);
3393 c = get_copy(&n);
3395 else
3396 break;
3398 else {
3399 if (c == 0)
3400 arg.append(n);
3401 else {
3402 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3403 warning(WARN_TAB, "tab character in unquoted macro argument");
3404 done_tab_warning = 1;
3406 arg.append(c);
3408 c = get_copy(&n);
3411 mi->add_arg(arg);
3416 void macro::invoke(symbol nm)
3418 macro_iterator *mi = new macro_iterator(nm, *this);
3419 decode_args(mi);
3420 input_stack::push(mi);
3421 tok.next();
3424 macro *macro::to_macro()
3426 return this;
3429 int macro::empty()
3431 return length == 0;
3434 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
3435 : string_iterator(m, how_invoked, s), args(0), argc(0)
3439 macro_iterator::macro_iterator() : args(0), argc(0)
3443 macro_iterator::~macro_iterator()
3445 while (args != 0) {
3446 arg_list *tem = args;
3447 args = args->next;
3448 delete tem;
3452 int trap_sprung_flag = 0;
3453 int postpone_traps_flag = 0;
3454 symbol postponed_trap;
3456 void spring_trap(symbol nm)
3458 assert(!nm.is_null());
3459 trap_sprung_flag = 1;
3460 if (postpone_traps_flag) {
3461 postponed_trap = nm;
3462 return;
3464 static char buf[2] = { BEGIN_TRAP, 0 };
3465 static char buf2[2] = { END_TRAP, '\0' };
3466 input_stack::push(make_temp_iterator(buf2));
3467 request_or_macro *p = lookup_request(nm);
3468 macro *m = p->to_macro();
3469 if (m)
3470 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
3471 else
3472 error("you can't invoke a request with a trap");
3473 input_stack::push(make_temp_iterator(buf));
3476 void postpone_traps()
3478 postpone_traps_flag = 1;
3481 int unpostpone_traps()
3483 postpone_traps_flag = 0;
3484 if (!postponed_trap.is_null()) {
3485 spring_trap(postponed_trap);
3486 postponed_trap = NULL_SYMBOL;
3487 return 1;
3489 else
3490 return 0;
3493 void read_request()
3495 macro_iterator *mi = new macro_iterator;
3496 int reading_from_terminal = isatty(fileno(stdin));
3497 int had_prompt = 0;
3498 if (!tok.newline() && !tok.eof()) {
3499 int c = get_copy(0);
3500 while (c == ' ')
3501 c = get_copy(0);
3502 while (c != EOF && c != '\n' && c != ' ') {
3503 if (!invalid_input_char(c)) {
3504 if (reading_from_terminal)
3505 fputc(c, stderr);
3506 had_prompt = 1;
3508 c = get_copy(0);
3510 if (c == ' ') {
3511 tok.make_space();
3512 decode_args(mi);
3515 if (reading_from_terminal) {
3516 fputc(had_prompt ? ':' : '\a', stderr);
3517 fflush(stderr);
3519 input_stack::push(mi);
3520 macro mac;
3521 int nl = 0;
3522 int c;
3523 while ((c = getchar()) != EOF) {
3524 if (invalid_input_char(c))
3525 warning(WARN_INPUT, "invalid input character code %1", int(c));
3526 else {
3527 if (c == '\n') {
3528 if (nl)
3529 break;
3530 else
3531 nl = 1;
3533 else
3534 nl = 0;
3535 mac.append(c);
3538 if (reading_from_terminal)
3539 clearerr(stdin);
3540 input_stack::push(new string_iterator(mac));
3541 tok.next();
3544 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3545 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT, CALLING_DISABLE_COMP };
3547 void do_define_string(define_mode mode, calling_mode calling)
3549 symbol nm;
3550 node *n;
3551 int c;
3552 nm = get_name(1);
3553 if (nm.is_null()) {
3554 skip_line();
3555 return;
3557 if (tok.newline())
3558 c = '\n';
3559 else if (tok.tab())
3560 c = '\t';
3561 else if (!tok.space()) {
3562 error("bad string definition");
3563 skip_line();
3564 return;
3566 else
3567 c = get_copy(&n);
3568 while (c == ' ')
3569 c = get_copy(&n);
3570 if (c == '"')
3571 c = get_copy(&n);
3572 macro mac;
3573 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3574 macro *mm = rm ? rm->to_macro() : 0;
3575 if (mode == DEFINE_APPEND && mm)
3576 mac = *mm;
3577 if (calling == CALLING_DISABLE_COMP)
3578 mac.append(COMPATIBLE_SAVE);
3579 while (c != '\n' && c != EOF) {
3580 if (c == 0)
3581 mac.append(n);
3582 else
3583 mac.append((unsigned char)c);
3584 c = get_copy(&n);
3586 if (!mm) {
3587 mm = new macro;
3588 request_dictionary.define(nm, mm);
3590 if (calling == CALLING_DISABLE_COMP)
3591 mac.append(COMPATIBLE_RESTORE);
3592 *mm = mac;
3593 tok.next();
3596 void define_string()
3598 do_define_string(DEFINE_NORMAL, CALLING_NORMAL);
3601 void define_nocomp_string()
3603 do_define_string(DEFINE_NORMAL, CALLING_DISABLE_COMP);
3606 void append_string()
3608 do_define_string(DEFINE_APPEND, CALLING_NORMAL);
3611 void append_nocomp_string()
3613 do_define_string(DEFINE_APPEND, CALLING_DISABLE_COMP);
3616 void do_define_character(int fallback)
3618 node *n;
3619 int c;
3620 tok.skip();
3621 charinfo *ci = tok.get_char(1);
3622 if (ci == 0) {
3623 skip_line();
3624 return;
3626 tok.next();
3627 if (tok.newline())
3628 c = '\n';
3629 else if (tok.tab())
3630 c = '\t';
3631 else if (!tok.space()) {
3632 error("bad character definition");
3633 skip_line();
3634 return;
3636 else
3637 c = get_copy(&n);
3638 while (c == ' ' || c == '\t')
3639 c = get_copy(&n);
3640 if (c == '"')
3641 c = get_copy(&n);
3642 macro *m = new macro;
3643 while (c != '\n' && c != EOF) {
3644 if (c == 0)
3645 m->append(n);
3646 else
3647 m->append((unsigned char)c);
3648 c = get_copy(&n);
3650 m = ci->set_macro(m, fallback);
3651 if (m)
3652 delete m;
3653 tok.next();
3656 void define_character()
3658 do_define_character(0);
3661 void define_fallback_character()
3663 do_define_character(1);
3666 static void remove_character()
3668 tok.skip();
3669 while (!tok.newline() && !tok.eof()) {
3670 if (!tok.space() && !tok.tab()) {
3671 charinfo *ci = tok.get_char(1);
3672 if (!ci)
3673 break;
3674 macro *m = ci->set_macro(0);
3675 if (m)
3676 delete m;
3678 tok.next();
3680 skip_line();
3683 static void interpolate_string(symbol nm)
3685 request_or_macro *p = lookup_request(nm);
3686 macro *m = p->to_macro();
3687 if (!m)
3688 error("you can only invoke a string using \\*");
3689 else {
3690 string_iterator *si = new string_iterator(*m, "string", nm);
3691 input_stack::push(si);
3695 /* This class is used for the implementation of \$@. It is used for
3696 each of the closing double quotes. It artificially increases the
3697 input level by 2, so that the closing double quote will appear to have
3698 the same input level as the opening quote. */
3700 class end_quote_iterator : public input_iterator {
3701 unsigned char buf[1];
3702 public:
3703 end_quote_iterator();
3704 ~end_quote_iterator() { }
3705 int internal_level() { return 2; }
3708 end_quote_iterator::end_quote_iterator()
3710 buf[0] = '"';
3711 ptr = buf;
3712 eptr = buf + 1;
3715 static void interpolate_arg(symbol nm)
3717 const char *s = nm.contents();
3718 if (!s || *s == '\0')
3719 copy_mode_error("missing argument name");
3720 else if (s[1] == 0 && csdigit(s[0]))
3721 input_stack::push(input_stack::get_arg(s[0] - '0'));
3722 else if (s[0] == '*' && s[1] == '\0') {
3723 for (int i = input_stack::nargs(); i > 0; i--) {
3724 input_stack::push(input_stack::get_arg(i));
3725 if (i != 1)
3726 input_stack::push(make_temp_iterator(" "));
3729 else if (s[0] == '@' && s[1] == '\0') {
3730 for (int i = input_stack::nargs(); i > 0; i--) {
3731 input_stack::push(new end_quote_iterator);
3732 input_stack::push(input_stack::get_arg(i));
3733 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
3736 else {
3737 const char *p;
3738 for (p = s; *p && csdigit(*p); p++)
3740 if (*p)
3741 copy_mode_error("bad argument name `%1'", s);
3742 else
3743 input_stack::push(input_stack::get_arg(atoi(s)));
3747 void handle_first_page_transition()
3749 push_token(tok);
3750 topdiv->begin_page();
3753 // We push back a token by wrapping it up in a token_node, and
3754 // wrapping that up in a string_iterator.
3756 static void push_token(const token &t)
3758 macro m;
3759 m.append(new token_node(t));
3760 input_stack::push(new string_iterator(m));
3763 void push_page_ejector()
3765 static char buf[2] = { PAGE_EJECTOR, '\0' };
3766 input_stack::push(make_temp_iterator(buf));
3769 void handle_initial_request(unsigned char code)
3771 char buf[2];
3772 buf[0] = code;
3773 buf[1] = '\0';
3774 macro mac;
3775 mac.append(new token_node(tok));
3776 input_stack::push(new string_iterator(mac));
3777 input_stack::push(make_temp_iterator(buf));
3778 topdiv->begin_page();
3779 tok.next();
3782 void handle_initial_title()
3784 handle_initial_request(TITLE_REQUEST);
3787 // this should be local to define_macro, but cfront 1.2 doesn't support that
3788 static symbol dot_symbol(".");
3790 void do_define_macro(define_mode mode, calling_mode calling)
3792 symbol nm, term;
3793 if (calling == CALLING_INDIRECT) {
3794 symbol temp1 = get_name(1);
3795 if (temp1.is_null()) {
3796 skip_line();
3797 return;
3799 symbol temp2 = get_name();
3800 input_stack::push(make_temp_iterator("\n"));
3801 if (!temp2.is_null()) {
3802 interpolate_string(temp2);
3803 input_stack::push(make_temp_iterator(" "));
3805 interpolate_string(temp1);
3806 input_stack::push(make_temp_iterator(" "));
3807 tok.next();
3809 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3810 nm = get_name(1);
3811 if (nm.is_null()) {
3812 skip_line();
3813 return;
3816 term = get_name(); // the request that terminates the definition
3817 if (term.is_null())
3818 term = dot_symbol;
3819 while (!tok.newline() && !tok.eof())
3820 tok.next();
3821 const char *start_filename;
3822 int start_lineno;
3823 int have_start_location = input_stack::get_location(0, &start_filename,
3824 &start_lineno);
3825 node *n;
3826 // doing this here makes the line numbers come out right
3827 int c = get_copy(&n, 1);
3828 macro mac;
3829 macro *mm = 0;
3830 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3831 request_or_macro *rm =
3832 (request_or_macro *)request_dictionary.lookup(nm);
3833 if (rm)
3834 mm = rm->to_macro();
3835 if (mm && mode == DEFINE_APPEND)
3836 mac = *mm;
3838 int bol = 1;
3839 if (calling == CALLING_DISABLE_COMP)
3840 mac.append(COMPATIBLE_SAVE);
3841 for (;;) {
3842 while (c == ESCAPE_NEWLINE) {
3843 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
3844 mac.append(c);
3845 c = get_copy(&n, 1);
3847 if (bol && c == '.') {
3848 const char *s = term.contents();
3849 int d = 0;
3850 // see if it matches term
3851 int i = 0;
3852 if (s[0] != 0) {
3853 while ((d = get_copy(&n)) == ' ' || d == '\t')
3855 if ((unsigned char)s[0] == d) {
3856 for (i = 1; s[i] != 0; i++) {
3857 d = get_copy(&n);
3858 if ((unsigned char)s[i] != d)
3859 break;
3863 if (s[i] == 0
3864 && ((i == 2 && compatible_flag)
3865 || (d = get_copy(&n)) == ' '
3866 || d == '\n')) { // we found it
3867 if (d == '\n')
3868 tok.make_newline();
3869 else
3870 tok.make_space();
3871 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3872 if (!mm) {
3873 mm = new macro;
3874 request_dictionary.define(nm, mm);
3876 if (calling == CALLING_DISABLE_COMP)
3877 mac.append(COMPATIBLE_RESTORE);
3878 *mm = mac;
3880 if (term != dot_symbol) {
3881 ignoring = 0;
3882 interpolate_macro(term);
3884 else
3885 skip_line();
3886 return;
3888 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
3889 mac.append(c);
3890 for (int j = 0; j < i; j++)
3891 mac.append(s[j]);
3893 c = d;
3895 if (c == EOF) {
3896 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3897 if (have_start_location)
3898 error_with_file_and_line(start_filename, start_lineno,
3899 "end of file while defining macro `%1'",
3900 nm.contents());
3901 else
3902 error("end of file while defining macro `%1'", nm.contents());
3904 else {
3905 if (have_start_location)
3906 error_with_file_and_line(start_filename, start_lineno,
3907 "end of file while ignoring input lines");
3908 else
3909 error("end of file while ignoring input lines");
3911 tok.next();
3912 return;
3914 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
3915 if (c == 0)
3916 mac.append(n);
3917 else
3918 mac.append(c);
3920 bol = (c == '\n');
3921 c = get_copy(&n, 1);
3925 void define_macro()
3927 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL);
3930 void define_nocomp_macro()
3932 do_define_macro(DEFINE_NORMAL, CALLING_DISABLE_COMP);
3935 void define_indirect_macro()
3937 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT);
3940 void append_macro()
3942 do_define_macro(DEFINE_APPEND, CALLING_NORMAL);
3945 void append_indirect_macro()
3947 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT);
3950 void append_nocomp_macro()
3952 do_define_macro(DEFINE_APPEND, CALLING_DISABLE_COMP);
3955 void ignore()
3957 ignoring = 1;
3958 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL);
3959 ignoring = 0;
3962 void remove_macro()
3964 for (;;) {
3965 symbol s = get_name();
3966 if (s.is_null())
3967 break;
3968 request_dictionary.remove(s);
3970 skip_line();
3973 void rename_macro()
3975 symbol s1 = get_name(1);
3976 if (!s1.is_null()) {
3977 symbol s2 = get_name(1);
3978 if (!s2.is_null())
3979 request_dictionary.rename(s1, s2);
3981 skip_line();
3984 void alias_macro()
3986 symbol s1 = get_name(1);
3987 if (!s1.is_null()) {
3988 symbol s2 = get_name(1);
3989 if (!s2.is_null()) {
3990 if (!request_dictionary.alias(s1, s2))
3991 warning(WARN_MAC, "`%1' not defined", s2.contents());
3994 skip_line();
3997 void chop_macro()
3999 symbol s = get_name(1);
4000 if (!s.is_null()) {
4001 request_or_macro *p = lookup_request(s);
4002 macro *m = p->to_macro();
4003 if (!m)
4004 error("cannot chop request");
4005 else if (m->length == 0)
4006 error("cannot chop empty macro");
4007 else
4008 m->length -= 1;
4010 skip_line();
4013 void substring_macro()
4015 int start;
4016 symbol s = get_name(1);
4017 if (!s.is_null() && get_integer(&start)) {
4018 request_or_macro *p = lookup_request(s);
4019 macro *m = p->to_macro();
4020 if (!m)
4021 error("cannot substring request");
4022 else {
4023 if (start <= 0)
4024 start += m->length - 1;
4025 else
4026 start--;
4027 int end = 0;
4028 if (!has_arg() || get_integer(&end)) {
4029 if (end <= 0)
4030 end += m->length - 1;
4031 else
4032 end--;
4033 if (start > end) {
4034 int tem = start;
4035 start = end;
4036 end = tem;
4038 if (start >= m->length || end < 0) {
4039 m->length = 0;
4040 if (m->p) {
4041 if (--(m->p->count) <= 0)
4042 delete m->p;
4043 m->p = 0;
4045 skip_line();
4046 return;
4048 if (start < 0)
4049 start = 0;
4050 if (end >= m->length)
4051 end = m->length - 1;
4052 if (start == 0)
4053 m->length = end + 1;
4054 else {
4055 string_iterator iter(*m);
4056 int i;
4057 for (i = 0; i < start; i++)
4058 if (iter.get(0) == EOF)
4059 break;
4060 macro mac;
4061 for (; i <= end; i++) {
4062 node *nd;
4063 int c = iter.get(&nd);
4064 if (c == EOF)
4065 break;
4066 if (c == 0)
4067 mac.append(nd);
4068 else
4069 mac.append((unsigned char)c);
4071 *m = mac;
4076 skip_line();
4079 void length_macro()
4081 symbol ret;
4082 ret = get_name(1);
4083 if (ret.is_null()) {
4084 skip_line();
4085 return;
4087 int c;
4088 node *n;
4089 if (tok.newline())
4090 c = '\n';
4091 else if (tok.tab())
4092 c = '\t';
4093 else if (!tok.space()) {
4094 error("bad string definition");
4095 skip_line();
4096 return;
4098 else
4099 c = get_copy(&n);
4100 while (c == ' ')
4101 c = get_copy(&n);
4102 if (c == '"')
4103 c = get_copy(&n);
4104 int len = 0;
4105 while (c != '\n' && c != EOF) {
4106 ++len;
4107 c = get_copy(&n);
4109 tok.next();
4110 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4111 if (r)
4112 r->set_value(len);
4113 else
4114 set_number_reg(ret, len);
4117 void asciify_macro()
4119 symbol s = get_name(1);
4120 if (!s.is_null()) {
4121 request_or_macro *p = lookup_request(s);
4122 macro *m = p->to_macro();
4123 if (!m)
4124 error("cannot asciify request");
4125 else {
4126 macro am;
4127 string_iterator iter(*m);
4128 for (;;) {
4129 node *nd;
4130 int c = iter.get(&nd);
4131 if (c == EOF)
4132 break;
4133 if (c != 0)
4134 am.append(c);
4135 else
4136 nd->asciify(&am);
4138 *m = am;
4141 skip_line();
4144 void unformat_macro()
4146 symbol s = get_name(1);
4147 if (!s.is_null()) {
4148 request_or_macro *p = lookup_request(s);
4149 macro *m = p->to_macro();
4150 if (!m)
4151 error("cannot unformat request");
4152 else {
4153 macro am;
4154 string_iterator iter(*m);
4155 for (;;) {
4156 node *nd;
4157 int c = iter.get(&nd);
4158 if (c == EOF)
4159 break;
4160 if (c != 0)
4161 am.append(c);
4162 else {
4163 if (nd->set_unformat_flag())
4164 am.append(nd);
4167 *m = am;
4170 skip_line();
4173 static void interpolate_environment_variable(symbol nm)
4175 const char *s = getenv(nm.contents());
4176 if (s && *s)
4177 input_stack::push(make_temp_iterator(s));
4180 void interpolate_number_reg(symbol nm, int inc)
4182 reg *r = lookup_number_reg(nm);
4183 if (inc < 0)
4184 r->decrement();
4185 else if (inc > 0)
4186 r->increment();
4187 input_stack::push(make_temp_iterator(r->get_string()));
4190 static void interpolate_number_format(symbol nm)
4192 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4193 if (r)
4194 input_stack::push(make_temp_iterator(r->get_format()));
4197 static int get_delim_number(units *n, int si, int prev_value)
4199 token start;
4200 start.next();
4201 if (start.delimiter(1)) {
4202 tok.next();
4203 if (get_number(n, si, prev_value)) {
4204 if (start != tok)
4205 warning(WARN_DELIM, "closing delimiter does not match");
4206 return 1;
4209 return 0;
4212 static int get_delim_number(units *n, int si)
4214 token start;
4215 start.next();
4216 if (start.delimiter(1)) {
4217 tok.next();
4218 if (get_number(n, si)) {
4219 if (start != tok)
4220 warning(WARN_DELIM, "closing delimiter does not match");
4221 return 1;
4224 return 0;
4227 static int get_line_arg(units *n, int si, charinfo **cp)
4229 token start;
4230 start.next();
4231 int start_level = input_stack::get_level();
4232 if (!start.delimiter(1))
4233 return 0;
4234 tok.next();
4235 if (get_number(n, si)) {
4236 if (tok.dummy() || tok.transparent_dummy())
4237 tok.next();
4238 if (!(start == tok && input_stack::get_level() == start_level)) {
4239 *cp = tok.get_char(1);
4240 tok.next();
4242 if (!(start == tok && input_stack::get_level() == start_level))
4243 warning(WARN_DELIM, "closing delimiter does not match");
4244 return 1;
4246 return 0;
4249 static int read_size(int *x)
4251 tok.next();
4252 int c = tok.ch();
4253 int inc = 0;
4254 if (c == '-') {
4255 inc = -1;
4256 tok.next();
4257 c = tok.ch();
4259 else if (c == '+') {
4260 inc = 1;
4261 tok.next();
4262 c = tok.ch();
4264 int val;
4265 int bad = 0;
4266 if (c == '(') {
4267 tok.next();
4268 c = tok.ch();
4269 if (!inc) {
4270 // allow an increment either before or after the left parenthesis
4271 if (c == '-') {
4272 inc = -1;
4273 tok.next();
4274 c = tok.ch();
4276 else if (c == '+') {
4277 inc = 1;
4278 tok.next();
4279 c = tok.ch();
4282 if (!csdigit(c))
4283 bad = 1;
4284 else {
4285 val = c - '0';
4286 tok.next();
4287 c = tok.ch();
4288 if (!csdigit(c))
4289 bad = 1;
4290 else {
4291 val = val*10 + (c - '0');
4292 val *= sizescale;
4296 else if (csdigit(c)) {
4297 val = c - '0';
4298 if (!inc && c != '0' && c < '4') {
4299 tok.next();
4300 c = tok.ch();
4301 if (!csdigit(c))
4302 bad = 1;
4303 else
4304 val = val*10 + (c - '0');
4306 val *= sizescale;
4308 else if (!tok.delimiter(1))
4309 return 0;
4310 else {
4311 token start(tok);
4312 tok.next();
4313 if (!(inc
4314 ? get_number(&val, 'z')
4315 : get_number(&val, 'z', curenv->get_requested_point_size())))
4316 return 0;
4317 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
4318 if (start.ch() == '[')
4319 error("missing `]'");
4320 else
4321 error("missing closing delimiter");
4322 return 0;
4325 if (!bad) {
4326 switch (inc) {
4327 case 0:
4328 if (val == 0) {
4329 // special case -- \s[0] and \s0 means to revert to previous size
4330 *x = 0;
4331 return 1;
4333 *x = val;
4334 break;
4335 case 1:
4336 *x = curenv->get_requested_point_size() + val;
4337 break;
4338 case -1:
4339 *x = curenv->get_requested_point_size() - val;
4340 break;
4341 default:
4342 assert(0);
4344 if (*x <= 0) {
4345 warning(WARN_RANGE,
4346 "\\s request results in non-positive point size; set to 1");
4347 *x = 1;
4349 return 1;
4351 else {
4352 error("bad digit in point size");
4353 return 0;
4357 static symbol get_delim_name()
4359 token start;
4360 start.next();
4361 if (start.eof()) {
4362 error("end of input at start of delimited name");
4363 return NULL_SYMBOL;
4365 if (start.newline()) {
4366 error("can't delimit name with a newline");
4367 return NULL_SYMBOL;
4369 int start_level = input_stack::get_level();
4370 char abuf[ABUF_SIZE];
4371 char *buf = abuf;
4372 int buf_size = ABUF_SIZE;
4373 int i = 0;
4374 for (;;) {
4375 if (i + 1 > buf_size) {
4376 if (buf == abuf) {
4377 buf = new char[ABUF_SIZE*2];
4378 memcpy(buf, abuf, buf_size);
4379 buf_size = ABUF_SIZE*2;
4381 else {
4382 char *old_buf = buf;
4383 buf = new char[buf_size*2];
4384 memcpy(buf, old_buf, buf_size);
4385 buf_size *= 2;
4386 a_delete old_buf;
4389 tok.next();
4390 if (tok == start
4391 && (compatible_flag || input_stack::get_level() == start_level))
4392 break;
4393 if ((buf[i] = tok.ch()) == 0) {
4394 error("missing delimiter (got %1)", tok.description());
4395 if (buf != abuf)
4396 a_delete buf;
4397 return NULL_SYMBOL;
4399 i++;
4401 buf[i] = '\0';
4402 if (buf == abuf) {
4403 if (i == 0) {
4404 error("empty delimited name");
4405 return NULL_SYMBOL;
4407 else
4408 return symbol(buf);
4410 else {
4411 symbol s(buf);
4412 a_delete buf;
4413 return s;
4417 // Implement \R
4419 static void do_register()
4421 token start;
4422 start.next();
4423 if (!start.delimiter(1))
4424 return;
4425 tok.next();
4426 symbol nm = get_long_name(1);
4427 if (nm.is_null())
4428 return;
4429 while (tok.space())
4430 tok.next();
4431 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4432 int prev_value;
4433 if (!r || !r->get_value(&prev_value))
4434 prev_value = 0;
4435 int val;
4436 if (!get_number(&val, 'u', prev_value))
4437 return;
4438 if (start != tok)
4439 warning(WARN_DELIM, "closing delimiter does not match");
4440 if (r)
4441 r->set_value(val);
4442 else
4443 set_number_reg(nm, val);
4446 // this implements the \w escape sequence
4448 static void do_width()
4450 token start;
4451 start.next();
4452 int start_level = input_stack::get_level();
4453 environment env(curenv);
4454 environment *oldenv = curenv;
4455 curenv = &env;
4456 for (;;) {
4457 tok.next();
4458 if (tok.eof()) {
4459 warning(WARN_DELIM, "missing closing delimiter");
4460 break;
4462 if (tok.newline()) {
4463 warning(WARN_DELIM, "missing closing delimiter");
4464 input_stack::push(make_temp_iterator("\n"));
4465 break;
4467 if (tok == start
4468 && (compatible_flag || input_stack::get_level() == start_level))
4469 break;
4470 tok.process();
4472 env.wrap_up_tab();
4473 units x = env.get_input_line_position().to_units();
4474 input_stack::push(make_temp_iterator(i_to_a(x)));
4475 env.width_registers();
4476 curenv = oldenv;
4479 charinfo *page_character;
4481 void set_page_character()
4483 page_character = get_optional_char();
4484 skip_line();
4487 static const symbol percent_symbol("%");
4489 void read_title_parts(node **part, hunits *part_width)
4491 tok.skip();
4492 if (tok.newline() || tok.eof())
4493 return;
4494 token start(tok);
4495 int start_level = input_stack::get_level();
4496 tok.next();
4497 for (int i = 0; i < 3; i++) {
4498 while (!tok.newline() && !tok.eof()) {
4499 if (tok == start
4500 && (compatible_flag || input_stack::get_level() == start_level)) {
4501 tok.next();
4502 break;
4504 if (page_character != 0 && tok.get_char() == page_character)
4505 interpolate_number_reg(percent_symbol, 0);
4506 else
4507 tok.process();
4508 tok.next();
4510 curenv->wrap_up_tab();
4511 part_width[i] = curenv->get_input_line_position();
4512 part[i] = curenv->extract_output_line();
4514 while (!tok.newline() && !tok.eof())
4515 tok.next();
4518 class non_interpreted_node : public node {
4519 macro mac;
4520 public:
4521 non_interpreted_node(const macro &);
4522 int interpret(macro *);
4523 node *copy();
4524 int same(node *);
4525 const char *type();
4526 int force_tprint();
4529 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
4533 int non_interpreted_node::same(node *nd)
4535 return mac == ((non_interpreted_node *)nd)->mac;
4538 const char *non_interpreted_node::type()
4540 return "non_interpreted_node";
4543 int non_interpreted_node::force_tprint()
4545 return 0;
4548 node *non_interpreted_node::copy()
4550 return new non_interpreted_node(mac);
4553 int non_interpreted_node::interpret(macro *m)
4555 string_iterator si(mac);
4556 node *n;
4557 for (;;) {
4558 int c = si.get(&n);
4559 if (c == EOF)
4560 break;
4561 if (c == 0)
4562 m->append(n);
4563 else
4564 m->append(c);
4566 return 1;
4569 static node *do_non_interpreted()
4571 node *n;
4572 int c;
4573 macro mac;
4574 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
4575 if (c == 0)
4576 mac.append(n);
4577 else
4578 mac.append(c);
4579 if (c == EOF || c == '\n') {
4580 error("missing \\?");
4581 return 0;
4583 return new non_interpreted_node(mac);
4586 static void encode_char(macro *mac, char c)
4588 if (c == '\0') {
4589 if ((font::use_charnames_in_special) && tok.special()) {
4590 charinfo *ci = tok.get_char(1);
4591 const char *s = ci->get_symbol()->contents();
4592 if (s[0] != (char)0) {
4593 mac->append('\\');
4594 mac->append('(');
4595 int i = 0;
4596 while (s[i] != (char)0) {
4597 mac->append(s[i]);
4598 i++;
4600 mac->append('\\');
4601 mac->append(')');
4604 else if (!(tok.hyphen_indicator()
4605 || tok.dummy()
4606 || tok.transparent_dummy()
4607 || tok.zero_width_break()))
4608 error("%1 is invalid within \\X", tok.description());
4610 else {
4611 if ((font::use_charnames_in_special) && (c == '\\')) {
4613 * add escape escape sequence
4615 mac->append(c);
4617 mac->append(c);
4621 node *do_special()
4623 token start;
4624 start.next();
4625 int start_level = input_stack::get_level();
4626 macro mac;
4627 for (tok.next();
4628 tok != start || input_stack::get_level() != start_level;
4629 tok.next()) {
4630 if (tok.eof()) {
4631 warning(WARN_DELIM, "missing closing delimiter");
4632 return 0;
4634 if (tok.newline()) {
4635 input_stack::push(make_temp_iterator("\n"));
4636 warning(WARN_DELIM, "missing closing delimiter");
4637 break;
4639 unsigned char c;
4640 if (tok.space())
4641 c = ' ';
4642 else if (tok.tab())
4643 c = '\t';
4644 else if (tok.leader())
4645 c = '\001';
4646 else if (tok.backspace())
4647 c = '\b';
4648 else
4649 c = tok.ch();
4650 encode_char(&mac, c);
4652 return new special_node(mac);
4655 extern int image_no; // from node.cc
4657 static node *do_suppress(symbol nm)
4659 if (nm.is_null() || nm.is_empty()) {
4660 error("expecting an argument to escape \\O");
4661 return 0;
4663 const char *s = nm.contents();
4664 switch (*s) {
4665 case '0':
4666 if (begin_level == 1)
4667 return new suppress_node(0, 0);
4668 break;
4669 case '1':
4670 if (begin_level == 1)
4671 return new suppress_node(1, 0);
4672 break;
4673 case '2':
4674 if (begin_level == 1)
4675 return new suppress_node(1, 1);
4676 break;
4677 case '3':
4678 begin_level++;
4679 if ((begin_level == 1) && (!is_html)) {
4680 if (curdiv == topdiv) {
4681 if (topdiv->before_first_page) {
4682 if (!break_flag) {
4683 if (!topdiv->no_space_mode)
4684 topdiv->begin_page();
4686 else if (topdiv->no_space_mode)
4687 topdiv->begin_page();
4688 else {
4689 push_page_ejector();
4690 topdiv->begin_page();
4691 topdiv->set_ejecting();
4694 else {
4695 push_page_ejector();
4696 if (break_flag)
4697 curenv->do_break();
4698 if (!topdiv->no_space_mode)
4699 topdiv->set_ejecting();
4703 break;
4704 case '4':
4705 begin_level--;
4706 break;
4707 case '5':
4709 s++; // move over '5'
4710 char position = *s;
4711 if (*s == (char)0) {
4712 error("missing position and filename in \\O");
4713 return 0;
4715 if (!(position == 'l'
4716 || position == 'r'
4717 || position == 'c'
4718 || position == 'i')) {
4719 error("l, r, c, or i position expected (got %1 in \\O)", position);
4720 return 0;
4722 s++; // onto image name
4723 if (s == (char *)0) {
4724 error("missing image name for \\O");
4725 return 0;
4727 image_no++;
4728 if (begin_level == 1)
4729 return new suppress_node(symbol(s), position, image_no);
4731 break;
4732 default:
4733 error("`%1' is an invalid argument to \\O", *s);
4735 return 0;
4738 void special_node::tprint(troff_output_file *out)
4740 tprint_start(out);
4741 string_iterator iter(mac);
4742 for (;;) {
4743 int c = iter.get(0);
4744 if (c == EOF)
4745 break;
4746 for (const char *s = ::asciify(c); *s; s++)
4747 tprint_char(out, *s);
4749 tprint_end(out);
4752 int get_file_line(const char **filename, int *lineno)
4754 return input_stack::get_location(0, filename, lineno);
4757 void line_file()
4759 int n;
4760 if (get_integer(&n)) {
4761 const char *filename = 0;
4762 if (has_arg()) {
4763 symbol s = get_long_name();
4764 filename = s.contents();
4766 (void)input_stack::set_location(filename, n-1);
4768 skip_line();
4771 static int nroff_mode = 0;
4773 static void nroff_request()
4775 nroff_mode = 1;
4776 skip_line();
4779 static void troff_request()
4781 nroff_mode = 0;
4782 skip_line();
4785 static void skip_alternative()
4787 int level = 0;
4788 // ensure that ``.if 0\{'' works as expected
4789 if (tok.left_brace())
4790 level++;
4791 int c;
4792 for (;;) {
4793 c = input_stack::get(0);
4794 if (c == EOF)
4795 break;
4796 if (c == ESCAPE_LEFT_BRACE)
4797 ++level;
4798 else if (c == ESCAPE_RIGHT_BRACE)
4799 --level;
4800 else if (c == escape_char && escape_char > 0)
4801 switch(input_stack::get(0)) {
4802 case '{':
4803 ++level;
4804 break;
4805 case '}':
4806 --level;
4807 break;
4808 case '"':
4809 while ((c = input_stack::get(0)) != '\n' && c != EOF)
4813 Note that the level can properly be < 0, eg
4815 .if 1 \{\
4816 .if 0 \{\
4817 .\}\}
4819 So don't give an error message in this case.
4821 if (level <= 0 && c == '\n')
4822 break;
4824 tok.next();
4827 static void begin_alternative()
4829 while (tok.space() || tok.left_brace())
4830 tok.next();
4833 void nop_request()
4835 while (tok.space())
4836 tok.next();
4839 static int_stack if_else_stack;
4841 int do_if_request()
4843 int invert = 0;
4844 while (tok.space())
4845 tok.next();
4846 while (tok.ch() == '!') {
4847 tok.next();
4848 invert = !invert;
4850 int result;
4851 unsigned char c = tok.ch();
4852 if (c == 't') {
4853 tok.next();
4854 result = !nroff_mode;
4856 else if (c == 'n') {
4857 tok.next();
4858 result = nroff_mode;
4860 else if (c == 'v') {
4861 tok.next();
4862 result = 0;
4864 else if (c == 'o') {
4865 result = (topdiv->get_page_number() & 1);
4866 tok.next();
4868 else if (c == 'e') {
4869 result = !(topdiv->get_page_number() & 1);
4870 tok.next();
4872 else if (c == 'd' || c == 'r') {
4873 tok.next();
4874 symbol nm = get_name(1);
4875 if (nm.is_null()) {
4876 skip_alternative();
4877 return 0;
4879 result = (c == 'd'
4880 ? request_dictionary.lookup(nm) != 0
4881 : number_reg_dictionary.lookup(nm) != 0);
4883 else if (c == 'm') {
4884 tok.next();
4885 symbol nm = get_long_name(1);
4886 if (nm.is_null()) {
4887 skip_alternative();
4888 return 0;
4890 result = (nm == default_symbol
4891 || color_dictionary.lookup(nm) != 0);
4893 else if (c == 'c') {
4894 tok.next();
4895 tok.skip();
4896 charinfo *ci = tok.get_char(1);
4897 if (ci == 0) {
4898 skip_alternative();
4899 return 0;
4901 result = character_exists(ci, curenv);
4902 tok.next();
4904 else if (tok.space())
4905 result = 0;
4906 else if (tok.delimiter()) {
4907 token delim = tok;
4908 int delim_level = input_stack::get_level();
4909 environment env1(curenv);
4910 environment env2(curenv);
4911 environment *oldenv = curenv;
4912 curenv = &env1;
4913 for (int i = 0; i < 2; i++) {
4914 for (;;) {
4915 tok.next();
4916 if (tok.newline() || tok.eof()) {
4917 warning(WARN_DELIM, "missing closing delimiter");
4918 tok.next();
4919 curenv = oldenv;
4920 return 0;
4922 if (tok == delim
4923 && (compatible_flag || input_stack::get_level() == delim_level))
4924 break;
4925 tok.process();
4927 curenv = &env2;
4929 node *n1 = env1.extract_output_line();
4930 node *n2 = env2.extract_output_line();
4931 result = same_node_list(n1, n2);
4932 delete_node_list(n1);
4933 delete_node_list(n2);
4934 curenv = oldenv;
4935 tok.next();
4937 else {
4938 units n;
4939 if (!get_number(&n, 'u')) {
4940 skip_alternative();
4941 return 0;
4943 else
4944 result = n > 0;
4946 if (invert)
4947 result = !result;
4948 if (result)
4949 begin_alternative();
4950 else
4951 skip_alternative();
4952 return result;
4955 void if_else_request()
4957 if_else_stack.push(do_if_request());
4960 void if_request()
4962 do_if_request();
4965 void else_request()
4967 if (if_else_stack.is_empty()) {
4968 warning(WARN_EL, "unbalanced .el request");
4969 skip_alternative();
4971 else {
4972 if (if_else_stack.pop())
4973 skip_alternative();
4974 else
4975 begin_alternative();
4979 static int while_depth = 0;
4980 static int while_break_flag = 0;
4982 void while_request()
4984 macro mac;
4985 int escaped = 0;
4986 int level = 0;
4987 mac.append(new token_node(tok));
4988 for (;;) {
4989 node *n;
4990 int c = input_stack::get(&n);
4991 if (c == EOF)
4992 break;
4993 if (c == 0) {
4994 escaped = 0;
4995 mac.append(n);
4997 else if (escaped) {
4998 if (c == '{')
4999 level += 1;
5000 else if (c == '}')
5001 level -= 1;
5002 escaped = 0;
5003 mac.append(c);
5005 else {
5006 if (c == ESCAPE_LEFT_BRACE)
5007 level += 1;
5008 else if (c == ESCAPE_RIGHT_BRACE)
5009 level -= 1;
5010 else if (c == escape_char)
5011 escaped = 1;
5012 mac.append(c);
5013 if (c == '\n' && level <= 0)
5014 break;
5017 if (level != 0)
5018 error("unbalanced \\{ \\}");
5019 else {
5020 while_depth++;
5021 input_stack::add_boundary();
5022 for (;;) {
5023 input_stack::push(new string_iterator(mac, "while loop"));
5024 tok.next();
5025 if (!do_if_request()) {
5026 while (input_stack::get(0) != EOF)
5028 break;
5030 process_input_stack();
5031 if (while_break_flag || input_stack::is_return_boundary()) {
5032 while_break_flag = 0;
5033 break;
5036 input_stack::remove_boundary();
5037 while_depth--;
5039 tok.next();
5042 void while_break_request()
5044 if (!while_depth) {
5045 error("no while loop");
5046 skip_line();
5048 else {
5049 while_break_flag = 1;
5050 while (input_stack::get(0) != EOF)
5052 tok.next();
5056 void while_continue_request()
5058 if (!while_depth) {
5059 error("no while loop");
5060 skip_line();
5062 else {
5063 while (input_stack::get(0) != EOF)
5065 tok.next();
5069 // .so
5071 void source()
5073 symbol nm = get_long_name(1);
5074 if (nm.is_null())
5075 skip_line();
5076 else {
5077 while (!tok.newline() && !tok.eof())
5078 tok.next();
5079 errno = 0;
5080 FILE *fp = fopen(nm.contents(), "r");
5081 if (fp)
5082 input_stack::push(new file_iterator(fp, nm.contents()));
5083 else
5084 error("can't open `%1': %2", nm.contents(), strerror(errno));
5085 tok.next();
5089 // like .so but use popen()
5091 void pipe_source()
5093 if (safer_flag) {
5094 error(".pso request not allowed in safer mode");
5095 skip_line();
5097 else {
5098 #ifdef POPEN_MISSING
5099 error("pipes not available on this system");
5100 skip_line();
5101 #else /* not POPEN_MISSING */
5102 if (tok.newline() || tok.eof())
5103 error("missing command");
5104 else {
5105 int c;
5106 while ((c = get_copy(0)) == ' ' || c == '\t')
5108 int buf_size = 24;
5109 char *buf = new char[buf_size];
5110 int buf_used = 0;
5111 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5112 const char *s = asciify(c);
5113 int slen = strlen(s);
5114 if (buf_used + slen + 1> buf_size) {
5115 char *old_buf = buf;
5116 int old_buf_size = buf_size;
5117 buf_size *= 2;
5118 buf = new char[buf_size];
5119 memcpy(buf, old_buf, old_buf_size);
5120 a_delete old_buf;
5122 strcpy(buf + buf_used, s);
5123 buf_used += slen;
5125 buf[buf_used] = '\0';
5126 errno = 0;
5127 FILE *fp = popen(buf, POPEN_RT);
5128 if (fp)
5129 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5130 else
5131 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5132 a_delete buf;
5134 tok.next();
5135 #endif /* not POPEN_MISSING */
5139 // .psbb
5141 static int llx_reg_contents = 0;
5142 static int lly_reg_contents = 0;
5143 static int urx_reg_contents = 0;
5144 static int ury_reg_contents = 0;
5146 struct bounding_box {
5147 int llx, lly, urx, ury;
5150 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5151 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5153 int parse_bounding_box(char *p, bounding_box *bb)
5155 if (sscanf(p, "%d %d %d %d",
5156 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5157 return 1;
5158 else {
5159 /* The Document Structuring Conventions say that the numbers
5160 should be integers. Unfortunately some broken applications
5161 get this wrong. */
5162 double x1, x2, x3, x4;
5163 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5164 bb->llx = (int)x1;
5165 bb->lly = (int)x2;
5166 bb->urx = (int)x3;
5167 bb->ury = (int)x4;
5168 return 1;
5170 else {
5171 for (; *p == ' ' || *p == '\t'; p++)
5173 if (strncmp(p, "(atend)", 7) == 0) {
5174 return 2;
5178 bb->llx = bb->lly = bb->urx = bb->ury = 0;
5179 return 0;
5182 // This version is taken from psrm.cc
5184 #define PS_LINE_MAX 255
5185 cset white_space("\n\r \t");
5187 int ps_get_line(char *buf, FILE *fp, const char* filename)
5189 int c = getc(fp);
5190 if (c == EOF) {
5191 buf[0] = '\0';
5192 return 0;
5194 int i = 0;
5195 int err = 0;
5196 while (c != '\r' && c != '\n' && c != EOF) {
5197 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
5198 error("invalid input character code %1 in `%2'", int(c), filename);
5199 else if (i < PS_LINE_MAX)
5200 buf[i++] = c;
5201 else if (!err) {
5202 err = 1;
5203 error("PostScript file `%1' is non-conforming "
5204 "because length of line exceeds 255", filename);
5206 c = getc(fp);
5208 buf[i++] = '\n';
5209 buf[i] = '\0';
5210 if (c == '\r') {
5211 c = getc(fp);
5212 if (c != EOF && c != '\n')
5213 ungetc(c, fp);
5215 return 1;
5218 inline void assign_registers(int llx, int lly, int urx, int ury)
5220 llx_reg_contents = llx;
5221 lly_reg_contents = lly;
5222 urx_reg_contents = urx;
5223 ury_reg_contents = ury;
5226 void do_ps_file(FILE *fp, const char* filename)
5228 bounding_box bb;
5229 int bb_at_end = 0;
5230 char buf[PS_LINE_MAX];
5231 llx_reg_contents = lly_reg_contents =
5232 urx_reg_contents = ury_reg_contents = 0;
5233 if (!ps_get_line(buf, fp, filename)) {
5234 error("`%1' is empty", filename);
5235 return;
5237 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
5238 error("`%1' is not conforming to the Document Structuring Conventions",
5239 filename);
5240 return;
5242 while (ps_get_line(buf, fp, filename) != 0) {
5243 if (buf[0] != '%' || buf[1] != '%'
5244 || strncmp(buf + 2, "EndComments", 11) == 0)
5245 break;
5246 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5247 int res = parse_bounding_box(buf + 14, &bb);
5248 if (res == 1) {
5249 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5250 return;
5252 else if (res == 2) {
5253 bb_at_end = 1;
5254 break;
5256 else {
5257 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5258 filename);
5259 return;
5263 if (bb_at_end) {
5264 long offset;
5265 int last_try = 0;
5266 /* in the trailer, the last BoundingBox comment is significant */
5267 for (offset = 512; !last_try; offset *= 2) {
5268 int had_trailer = 0;
5269 int got_bb = 0;
5270 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
5271 last_try = 1;
5272 if (fseek(fp, 0L, 0) == -1)
5273 break;
5275 while (ps_get_line(buf, fp, filename) != 0) {
5276 if (buf[0] == '%' && buf[1] == '%') {
5277 if (!had_trailer) {
5278 if (strncmp(buf + 2, "Trailer", 7) == 0)
5279 had_trailer = 1;
5281 else {
5282 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5283 int res = parse_bounding_box(buf + 14, &bb);
5284 if (res == 1)
5285 got_bb = 1;
5286 else if (res == 2) {
5287 error("`(atend)' not allowed in trailer of `%1'", filename);
5288 return;
5290 else {
5291 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5292 filename);
5293 return;
5299 if (got_bb) {
5300 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5301 return;
5305 error("%%%%BoundingBox comment not found in `%1'", filename);
5308 void ps_bbox_request()
5310 symbol nm = get_long_name(1);
5311 if (nm.is_null())
5312 skip_line();
5313 else {
5314 while (!tok.newline() && !tok.eof())
5315 tok.next();
5316 errno = 0;
5317 // PS files might contain non-printable characters, such as ^Z
5318 // and CRs not followed by an LF, so open them in binary mode.
5319 FILE *fp = fopen(nm.contents(), FOPEN_RB);
5320 if (fp) {
5321 do_ps_file(fp, nm.contents());
5322 fclose(fp);
5324 else
5325 error("can't open `%1': %2", nm.contents(), strerror(errno));
5326 tok.next();
5330 const char *asciify(int c)
5332 static char buf[3];
5333 buf[0] = escape_char == '\0' ? '\\' : escape_char;
5334 buf[1] = buf[2] = '\0';
5335 switch (c) {
5336 case ESCAPE_QUESTION:
5337 buf[1] = '?';
5338 break;
5339 case ESCAPE_AMPERSAND:
5340 buf[1] = '&';
5341 break;
5342 case ESCAPE_RIGHT_PARENTHESIS:
5343 buf[1] = ')';
5344 break;
5345 case ESCAPE_UNDERSCORE:
5346 buf[1] = '_';
5347 break;
5348 case ESCAPE_BAR:
5349 buf[1] = '|';
5350 break;
5351 case ESCAPE_CIRCUMFLEX:
5352 buf[1] = '^';
5353 break;
5354 case ESCAPE_LEFT_BRACE:
5355 buf[1] = '{';
5356 break;
5357 case ESCAPE_RIGHT_BRACE:
5358 buf[1] = '}';
5359 break;
5360 case ESCAPE_LEFT_QUOTE:
5361 buf[1] = '`';
5362 break;
5363 case ESCAPE_RIGHT_QUOTE:
5364 buf[1] = '\'';
5365 break;
5366 case ESCAPE_HYPHEN:
5367 buf[1] = '-';
5368 break;
5369 case ESCAPE_BANG:
5370 buf[1] = '!';
5371 break;
5372 case ESCAPE_c:
5373 buf[1] = 'c';
5374 break;
5375 case ESCAPE_e:
5376 buf[1] = 'e';
5377 break;
5378 case ESCAPE_E:
5379 buf[1] = 'E';
5380 break;
5381 case ESCAPE_PERCENT:
5382 buf[1] = '%';
5383 break;
5384 case ESCAPE_SPACE:
5385 buf[1] = ' ';
5386 break;
5387 case ESCAPE_TILDE:
5388 buf[1] = '~';
5389 break;
5390 case ESCAPE_COLON:
5391 buf[1] = ':';
5392 break;
5393 default:
5394 if (invalid_input_char(c))
5395 buf[0] = '\0';
5396 else
5397 buf[0] = c;
5398 break;
5400 return buf;
5403 const char *input_char_description(int c)
5405 switch (c) {
5406 case '\n':
5407 return "a newline character";
5408 case '\b':
5409 return "a backspace character";
5410 case '\001':
5411 return "a leader character";
5412 case '\t':
5413 return "a tab character";
5414 case ' ':
5415 return "a space character";
5416 case '\0':
5417 return "a node";
5419 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
5420 if (invalid_input_char(c)) {
5421 const char *s = asciify(c);
5422 if (*s) {
5423 buf[0] = '`';
5424 strcpy(buf + 1, s);
5425 strcat(buf, "'");
5426 return buf;
5428 sprintf(buf, "magic character code %d", c);
5429 return buf;
5431 if (csprint(c)) {
5432 buf[0] = '`';
5433 buf[1] = c;
5434 buf[2] = '\'';
5435 return buf;
5437 sprintf(buf, "character code %d", c);
5438 return buf;
5441 // .tm, .tm1, and .tmc
5443 void do_terminal(int newline, int string_like)
5445 if (!tok.newline() && !tok.eof()) {
5446 int c;
5447 for (;;) {
5448 c = get_copy(0);
5449 if (string_like && c == '"') {
5450 c = get_copy(0);
5451 break;
5453 if (c != ' ' && c != '\t')
5454 break;
5456 for (; c != '\n' && c != EOF; c = get_copy(0))
5457 fputs(asciify(c), stderr);
5459 if (newline)
5460 fputc('\n', stderr);
5461 fflush(stderr);
5462 tok.next();
5465 void terminal()
5467 do_terminal(1, 0);
5470 void terminal1()
5472 do_terminal(1, 1);
5475 void terminal_continue()
5477 do_terminal(0, 1);
5480 dictionary stream_dictionary(20);
5482 void do_open(int append)
5484 symbol stream = get_name(1);
5485 if (!stream.is_null()) {
5486 symbol filename = get_long_name(1);
5487 if (!filename.is_null()) {
5488 errno = 0;
5489 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
5490 if (!fp) {
5491 error("can't open `%1' for %2: %3",
5492 filename.contents(),
5493 append ? "appending" : "writing",
5494 strerror(errno));
5495 fp = (FILE *)stream_dictionary.remove(stream);
5497 else
5498 fp = (FILE *)stream_dictionary.lookup(stream, fp);
5499 if (fp)
5500 fclose(fp);
5503 skip_line();
5506 void open_request()
5508 if (safer_flag) {
5509 error(".open request not allowed in safer mode");
5510 skip_line();
5512 else
5513 do_open(0);
5516 void opena_request()
5518 if (safer_flag) {
5519 error(".opena request not allowed in safer mode");
5520 skip_line();
5522 else
5523 do_open(1);
5526 void close_request()
5528 symbol stream = get_name(1);
5529 if (!stream.is_null()) {
5530 FILE *fp = (FILE *)stream_dictionary.remove(stream);
5531 if (!fp)
5532 error("no stream named `%1'", stream.contents());
5533 else
5534 fclose(fp);
5536 skip_line();
5539 // .write and .writec
5541 void do_write_request(int newline)
5543 symbol stream = get_name(1);
5544 if (stream.is_null()) {
5545 skip_line();
5546 return;
5548 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5549 if (!fp) {
5550 error("no stream named `%1'", stream.contents());
5551 skip_line();
5552 return;
5554 int c;
5555 while ((c = get_copy(0)) == ' ')
5557 if (c == '"')
5558 c = get_copy(0);
5559 for (; c != '\n' && c != EOF; c = get_copy(0))
5560 fputs(asciify(c), fp);
5561 if (newline)
5562 fputc('\n', fp);
5563 fflush(fp);
5564 tok.next();
5567 void write_request()
5569 do_write_request(1);
5572 void write_request_continue()
5574 do_write_request(0);
5577 void write_macro_request()
5579 symbol stream = get_name(1);
5580 if (stream.is_null()) {
5581 skip_line();
5582 return;
5584 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5585 if (!fp) {
5586 error("no stream named `%1'", stream.contents());
5587 skip_line();
5588 return;
5590 symbol s = get_name(1);
5591 if (s.is_null()) {
5592 skip_line();
5593 return;
5595 request_or_macro *p = lookup_request(s);
5596 macro *m = p->to_macro();
5597 if (!m)
5598 error("cannot write request");
5599 else {
5600 string_iterator iter(*m);
5601 for (;;) {
5602 int c = iter.get(0);
5603 if (c == EOF)
5604 break;
5605 fputs(asciify(c), fp);
5607 fflush(fp);
5609 skip_line();
5612 void warnscale_request()
5614 if (has_arg()) {
5615 char c = tok.ch();
5616 if (c == 'u')
5617 warn_scale = 1.0;
5618 else if (c == 'i')
5619 warn_scale = (double)units_per_inch;
5620 else if (c == 'c')
5621 warn_scale = (double)units_per_inch / 2.54;
5622 else if (c == 'p')
5623 warn_scale = (double)units_per_inch / 72.0;
5624 else if (c == 'P')
5625 warn_scale = (double)units_per_inch / 6.0;
5626 else {
5627 warning(WARN_SCALE,
5628 "invalid scaling indicator `%1', using `i' instead", c);
5629 c = 'i';
5631 warn_scaling_indicator = c;
5633 skip_line();
5636 void spreadwarn_request()
5638 hunits n;
5639 if (has_arg() && get_hunits(&n, 'm')) {
5640 if (n < 0)
5641 n = 0;
5642 hunits em = curenv->get_size();
5643 spread_limit = (double)n.to_units()
5644 / (em.is_zero() ? hresolution : em.to_units());
5646 else
5647 spread_limit = -spread_limit - 1; // no arg toggles on/off without
5648 // changing value; we mirror at
5649 // -0.5 to make zero a valid value
5650 skip_line();
5653 static void init_charset_table()
5655 char buf[16];
5656 strcpy(buf, "char");
5657 for (int i = 0; i < 256; i++) {
5658 strcpy(buf + 4, i_to_a(i));
5659 charset_table[i] = get_charinfo(symbol(buf));
5660 charset_table[i]->set_ascii_code(i);
5661 if (csalpha(i))
5662 charset_table[i]->set_hyphenation_code(cmlower(i));
5664 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
5665 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
5666 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
5667 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
5668 charset_table['"']->set_flags(charinfo::TRANSPARENT);
5669 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
5670 charset_table[')']->set_flags(charinfo::TRANSPARENT);
5671 charset_table[']']->set_flags(charinfo::TRANSPARENT);
5672 charset_table['*']->set_flags(charinfo::TRANSPARENT);
5673 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
5674 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
5675 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
5676 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5677 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5678 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5679 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
5680 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
5681 page_character = charset_table['%'];
5684 static void init_hpf_code_table()
5686 for (int i = 0; i < 256; i++)
5687 hpf_code_table[i] = i;
5690 static void do_translate(int translate_transparent, int translate_input)
5692 tok.skip();
5693 while (!tok.newline() && !tok.eof()) {
5694 if (tok.space()) {
5695 // This is a really bizarre troff feature.
5696 tok.next();
5697 translate_space_to_dummy = tok.dummy();
5698 if (tok.newline() || tok.eof())
5699 break;
5700 tok.next();
5701 continue;
5703 charinfo *ci1 = tok.get_char(1);
5704 if (ci1 == 0)
5705 break;
5706 tok.next();
5707 if (tok.newline() || tok.eof()) {
5708 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
5709 translate_transparent);
5710 break;
5712 if (tok.space())
5713 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
5714 translate_transparent);
5715 else if (tok.stretchable_space())
5716 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
5717 translate_transparent);
5718 else if (tok.dummy())
5719 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
5720 translate_transparent);
5721 else if (tok.hyphen_indicator())
5722 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
5723 translate_transparent);
5724 else {
5725 charinfo *ci2 = tok.get_char(1);
5726 if (ci2 == 0)
5727 break;
5728 if (ci1 == ci2)
5729 ci1->set_translation(0, translate_transparent, translate_input);
5730 else
5731 ci1->set_translation(ci2, translate_transparent, translate_input);
5733 tok.next();
5735 skip_line();
5738 void translate()
5740 do_translate(1, 0);
5743 void translate_no_transparent()
5745 do_translate(0, 0);
5748 void translate_input()
5750 do_translate(1, 1);
5753 void char_flags()
5755 int flags;
5756 if (get_integer(&flags))
5757 while (has_arg()) {
5758 charinfo *ci = tok.get_char(1);
5759 if (ci) {
5760 charinfo *tem = ci->get_translation();
5761 if (tem)
5762 ci = tem;
5763 ci->set_flags(flags);
5765 tok.next();
5767 skip_line();
5770 void hyphenation_code()
5772 tok.skip();
5773 while (!tok.newline() && !tok.eof()) {
5774 charinfo *ci = tok.get_char(1);
5775 if (ci == 0)
5776 break;
5777 tok.next();
5778 tok.skip();
5779 unsigned char c = tok.ch();
5780 if (c == 0) {
5781 error("hyphenation code must be ordinary character");
5782 break;
5784 if (csdigit(c)) {
5785 error("hyphenation code cannot be digit");
5786 break;
5788 ci->set_hyphenation_code(c);
5789 if (ci->get_translation()
5790 && ci->get_translation()->get_translation_input())
5791 ci->get_translation()->set_hyphenation_code(c);
5792 tok.next();
5793 tok.skip();
5795 skip_line();
5798 void hyphenation_patterns_file_code()
5800 tok.skip();
5801 while (!tok.newline() && !tok.eof()) {
5802 int n1, n2;
5803 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
5804 if (!has_arg()) {
5805 error("missing output hyphenation code");
5806 break;
5808 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
5809 hpf_code_table[n1] = n2;
5810 tok.skip();
5812 else {
5813 error("output hyphenation code must be integer in the range 0..255");
5814 break;
5817 else {
5818 error("input hyphenation code must be integer in the range 0..255");
5819 break;
5822 skip_line();
5825 charinfo *token::get_char(int required)
5827 if (type == TOKEN_CHAR)
5828 return charset_table[c];
5829 if (type == TOKEN_SPECIAL)
5830 return get_charinfo(nm);
5831 if (type == TOKEN_NUMBERED_CHAR)
5832 return get_charinfo_by_number(val);
5833 if (type == TOKEN_ESCAPE) {
5834 if (escape_char != 0)
5835 return charset_table[escape_char];
5836 else {
5837 error("`\\e' used while no current escape character");
5838 return 0;
5841 if (required) {
5842 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
5843 warning(WARN_MISSING, "missing normal or special character");
5844 else
5845 error("normal or special character expected (got %1)", description());
5847 return 0;
5850 charinfo *get_optional_char()
5852 while (tok.space())
5853 tok.next();
5854 charinfo *ci = tok.get_char();
5855 if (!ci)
5856 check_missing_character();
5857 else
5858 tok.next();
5859 return ci;
5862 void check_missing_character()
5864 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
5865 error("normal or special character expected (got %1): "
5866 "treated as missing",
5867 tok.description());
5870 // this is for \Z
5872 int token::add_to_node_list(node **pp)
5874 hunits w;
5875 int s;
5876 node *n = 0;
5877 switch (type) {
5878 case TOKEN_CHAR:
5879 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
5880 break;
5881 case TOKEN_DUMMY:
5882 n = new dummy_node;
5883 break;
5884 case TOKEN_ESCAPE:
5885 if (escape_char != 0)
5886 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
5887 break;
5888 case TOKEN_HYPHEN_INDICATOR:
5889 *pp = (*pp)->add_discretionary_hyphen();
5890 break;
5891 case TOKEN_ITALIC_CORRECTION:
5892 *pp = (*pp)->add_italic_correction(&w);
5893 break;
5894 case TOKEN_LEFT_BRACE:
5895 break;
5896 case TOKEN_MARK_INPUT:
5897 set_number_reg(nm, curenv->get_input_line_position().to_units());
5898 break;
5899 case TOKEN_NODE:
5900 n = nd;
5901 nd = 0;
5902 break;
5903 case TOKEN_NUMBERED_CHAR:
5904 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
5905 break;
5906 case TOKEN_RIGHT_BRACE:
5907 break;
5908 case TOKEN_SPACE:
5909 n = new hmotion_node(curenv->get_space_width());
5910 break;
5911 case TOKEN_SPECIAL:
5912 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
5913 break;
5914 case TOKEN_STRETCHABLE_SPACE:
5915 n = new unbreakable_space_node(curenv->get_space_width());
5916 break;
5917 case TOKEN_TRANSPARENT_DUMMY:
5918 n = new transparent_dummy_node;
5919 break;
5920 case TOKEN_ZERO_WIDTH_BREAK:
5921 n = new space_node(H0);
5922 n->freeze_space();
5923 n->is_escape_colon();
5924 break;
5925 default:
5926 return 0;
5928 if (n) {
5929 n->next = *pp;
5930 *pp = n;
5932 return 1;
5935 void token::process()
5937 if (possibly_handle_first_page_transition())
5938 return;
5939 switch (type) {
5940 case TOKEN_BACKSPACE:
5941 curenv->add_node(new hmotion_node(-curenv->get_space_width()));
5942 break;
5943 case TOKEN_CHAR:
5944 curenv->add_char(charset_table[c]);
5945 break;
5946 case TOKEN_DUMMY:
5947 curenv->add_node(new dummy_node);
5948 break;
5949 case TOKEN_EMPTY:
5950 assert(0);
5951 break;
5952 case TOKEN_EOF:
5953 assert(0);
5954 break;
5955 case TOKEN_ESCAPE:
5956 if (escape_char != 0)
5957 curenv->add_char(charset_table[escape_char]);
5958 break;
5959 case TOKEN_BEGIN_TRAP:
5960 case TOKEN_END_TRAP:
5961 case TOKEN_PAGE_EJECTOR:
5962 // these are all handled in process_input_stack()
5963 break;
5964 case TOKEN_HYPHEN_INDICATOR:
5965 curenv->add_hyphen_indicator();
5966 break;
5967 case TOKEN_INTERRUPT:
5968 curenv->interrupt();
5969 break;
5970 case TOKEN_ITALIC_CORRECTION:
5971 curenv->add_italic_correction();
5972 break;
5973 case TOKEN_LEADER:
5974 curenv->handle_tab(1);
5975 break;
5976 case TOKEN_LEFT_BRACE:
5977 break;
5978 case TOKEN_MARK_INPUT:
5979 set_number_reg(nm, curenv->get_input_line_position().to_units());
5980 break;
5981 case TOKEN_NEWLINE:
5982 curenv->newline();
5983 break;
5984 case TOKEN_NODE:
5985 curenv->add_node(nd);
5986 nd = 0;
5987 break;
5988 case TOKEN_NUMBERED_CHAR:
5989 curenv->add_char(get_charinfo_by_number(val));
5990 break;
5991 case TOKEN_REQUEST:
5992 // handled in process_input_stack()
5993 break;
5994 case TOKEN_RIGHT_BRACE:
5995 break;
5996 case TOKEN_SPACE:
5997 curenv->space();
5998 break;
5999 case TOKEN_SPECIAL:
6000 curenv->add_char(get_charinfo(nm));
6001 break;
6002 case TOKEN_SPREAD:
6003 curenv->spread();
6004 break;
6005 case TOKEN_STRETCHABLE_SPACE:
6006 curenv->add_node(new unbreakable_space_node(curenv->get_space_width()));
6007 break;
6008 case TOKEN_TAB:
6009 curenv->handle_tab(0);
6010 break;
6011 case TOKEN_TRANSPARENT:
6012 break;
6013 case TOKEN_TRANSPARENT_DUMMY:
6014 curenv->add_node(new transparent_dummy_node);
6015 break;
6016 case TOKEN_ZERO_WIDTH_BREAK:
6018 node *tmp = new space_node(H0);
6019 tmp->freeze_space();
6020 tmp->is_escape_colon();
6021 curenv->add_node(tmp);
6022 break;
6024 default:
6025 assert(0);
6029 class nargs_reg : public reg {
6030 public:
6031 const char *get_string();
6034 const char *nargs_reg::get_string()
6036 return i_to_a(input_stack::nargs());
6039 class lineno_reg : public reg {
6040 public:
6041 const char *get_string();
6044 const char *lineno_reg::get_string()
6046 int line;
6047 const char *file;
6048 if (!input_stack::get_location(0, &file, &line))
6049 line = 0;
6050 return i_to_a(line);
6053 class writable_lineno_reg : public general_reg {
6054 public:
6055 writable_lineno_reg();
6056 void set_value(units);
6057 int get_value(units *);
6060 writable_lineno_reg::writable_lineno_reg()
6064 int writable_lineno_reg::get_value(units *res)
6066 int line;
6067 const char *file;
6068 if (!input_stack::get_location(0, &file, &line))
6069 return 0;
6070 *res = line;
6071 return 1;
6074 void writable_lineno_reg::set_value(units n)
6076 input_stack::set_location(0, n);
6079 class filename_reg : public reg {
6080 public:
6081 const char *get_string();
6084 const char *filename_reg::get_string()
6086 int line;
6087 const char *file;
6088 if (input_stack::get_location(0, &file, &line))
6089 return file;
6090 else
6091 return 0;
6094 class constant_reg : public reg {
6095 const char *s;
6096 public:
6097 constant_reg(const char *);
6098 const char *get_string();
6101 constant_reg::constant_reg(const char *p) : s(p)
6105 const char *constant_reg::get_string()
6107 return s;
6110 constant_int_reg::constant_int_reg(int *q) : p(q)
6114 const char *constant_int_reg::get_string()
6116 return i_to_a(*p);
6119 void abort_request()
6121 int c;
6122 if (tok.eof())
6123 c = EOF;
6124 else if (tok.newline())
6125 c = '\n';
6126 else {
6127 while ((c = get_copy(0)) == ' ')
6130 if (c == EOF || c == '\n')
6131 fputs("User Abort.", stderr);
6132 else {
6133 for (; c != '\n' && c != EOF; c = get_copy(0))
6134 fputs(asciify(c), stderr);
6136 fputc('\n', stderr);
6137 cleanup_and_exit(1);
6140 char *read_string()
6142 int len = 256;
6143 char *s = new char[len];
6144 int c;
6145 while ((c = get_copy(0)) == ' ')
6147 int i = 0;
6148 while (c != '\n' && c != EOF) {
6149 if (!invalid_input_char(c)) {
6150 if (i + 2 > len) {
6151 char *tem = s;
6152 s = new char[len*2];
6153 memcpy(s, tem, len);
6154 len *= 2;
6155 a_delete tem;
6157 s[i++] = c;
6159 c = get_copy(0);
6161 s[i] = '\0';
6162 tok.next();
6163 if (i == 0) {
6164 a_delete s;
6165 return 0;
6167 return s;
6170 void pipe_output()
6172 if (safer_flag) {
6173 error(".pi request not allowed in safer mode");
6174 skip_line();
6176 else {
6177 #ifdef POPEN_MISSING
6178 error("pipes not available on this system");
6179 skip_line();
6180 #else /* not POPEN_MISSING */
6181 if (the_output) {
6182 error("can't pipe: output already started");
6183 skip_line();
6185 else {
6186 char *pc;
6187 if ((pc = read_string()) == 0)
6188 error("can't pipe to empty command");
6189 if (pipe_command) {
6190 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
6191 strcpy(s, pipe_command);
6192 strcat(s, "|");
6193 strcat(s, pc);
6194 a_delete pipe_command;
6195 a_delete pc;
6196 pipe_command = s;
6198 else
6199 pipe_command = pc;
6201 #endif /* not POPEN_MISSING */
6205 static int system_status;
6207 void system_request()
6209 if (safer_flag) {
6210 error(".sy request not allowed in safer mode");
6211 skip_line();
6213 else {
6214 char *command = read_string();
6215 if (!command)
6216 error("empty command");
6217 else {
6218 system_status = system(command);
6219 a_delete command;
6224 void copy_file()
6226 if (curdiv == topdiv && topdiv->before_first_page) {
6227 handle_initial_request(COPY_FILE_REQUEST);
6228 return;
6230 symbol filename = get_long_name(1);
6231 while (!tok.newline() && !tok.eof())
6232 tok.next();
6233 if (break_flag)
6234 curenv->do_break();
6235 if (!filename.is_null())
6236 curdiv->copy_file(filename.contents());
6237 tok.next();
6240 #ifdef COLUMN
6242 void vjustify()
6244 if (curdiv == topdiv && topdiv->before_first_page) {
6245 handle_initial_request(VJUSTIFY_REQUEST);
6246 return;
6248 symbol type = get_long_name(1);
6249 if (!type.is_null())
6250 curdiv->vjustify(type);
6251 skip_line();
6254 #endif /* COLUMN */
6256 void transparent_file()
6258 if (curdiv == topdiv && topdiv->before_first_page) {
6259 handle_initial_request(TRANSPARENT_FILE_REQUEST);
6260 return;
6262 symbol filename = get_long_name(1);
6263 while (!tok.newline() && !tok.eof())
6264 tok.next();
6265 if (break_flag)
6266 curenv->do_break();
6267 if (!filename.is_null()) {
6268 errno = 0;
6269 FILE *fp = fopen(filename.contents(), "r");
6270 if (!fp)
6271 error("can't open `%1': %2", filename.contents(), strerror(errno));
6272 else {
6273 int bol = 1;
6274 for (;;) {
6275 int c = getc(fp);
6276 if (c == EOF)
6277 break;
6278 if (invalid_input_char(c))
6279 warning(WARN_INPUT, "invalid input character code %1", int(c));
6280 else {
6281 curdiv->transparent_output(c);
6282 bol = c == '\n';
6285 if (!bol)
6286 curdiv->transparent_output('\n');
6287 fclose(fp);
6290 tok.next();
6293 class page_range {
6294 int first;
6295 int last;
6296 public:
6297 page_range *next;
6298 page_range(int, int, page_range *);
6299 int contains(int n);
6302 page_range::page_range(int i, int j, page_range *p)
6303 : first(i), last(j), next(p)
6307 int page_range::contains(int n)
6309 return n >= first && (last <= 0 || n <= last);
6312 page_range *output_page_list = 0;
6314 int in_output_page_list(int n)
6316 if (!output_page_list)
6317 return 1;
6318 for (page_range *p = output_page_list; p; p = p->next)
6319 if (p->contains(n))
6320 return 1;
6321 return 0;
6324 static void parse_output_page_list(char *p)
6326 for (;;) {
6327 int i;
6328 if (*p == '-')
6329 i = 1;
6330 else if (csdigit(*p)) {
6331 i = 0;
6333 i = i*10 + *p++ - '0';
6334 while (csdigit(*p));
6336 else
6337 break;
6338 int j;
6339 if (*p == '-') {
6340 p++;
6341 j = 0;
6342 if (csdigit(*p)) {
6344 j = j*10 + *p++ - '0';
6345 while (csdigit(*p));
6348 else
6349 j = i;
6350 if (j == 0)
6351 last_page_number = -1;
6352 else if (last_page_number >= 0 && j > last_page_number)
6353 last_page_number = j;
6354 output_page_list = new page_range(i, j, output_page_list);
6355 if (*p != ',')
6356 break;
6357 ++p;
6359 if (*p != '\0') {
6360 error("bad output page list");
6361 output_page_list = 0;
6365 static FILE *open_mac_file(const char *mac, char **path)
6367 // Try first FOOBAR.tmac, then tmac.FOOBAR
6368 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
6369 strcpy(s1, mac);
6370 strcat(s1, MACRO_POSTFIX);
6371 FILE *fp = mac_path->open_file(s1, path);
6372 a_delete s1;
6373 if (!fp) {
6374 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
6375 strcpy(s2, MACRO_PREFIX);
6376 strcat(s2, mac);
6377 fp = mac_path->open_file(s2, path);
6378 a_delete s2;
6380 return fp;
6383 static void process_macro_file(const char *mac)
6385 char *path;
6386 FILE *fp = open_mac_file(mac, &path);
6387 if (!fp)
6388 fatal("can't find macro file %1", mac);
6389 const char *s = symbol(path).contents();
6390 a_delete path;
6391 input_stack::push(new file_iterator(fp, s));
6392 tok.next();
6393 process_input_stack();
6396 static void process_startup_file(char *filename)
6398 char *path;
6399 search_path *orig_mac_path = mac_path;
6400 mac_path = &config_macro_path;
6401 FILE *fp = mac_path->open_file(filename, &path);
6402 if (fp) {
6403 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6404 a_delete path;
6405 tok.next();
6406 process_input_stack();
6408 mac_path = orig_mac_path;
6411 void macro_source()
6413 symbol nm = get_long_name(1);
6414 if (nm.is_null())
6415 skip_line();
6416 else {
6417 while (!tok.newline() && !tok.eof())
6418 tok.next();
6419 char *path;
6420 FILE *fp = mac_path->open_file(nm.contents(), &path);
6421 // .mso doesn't (and cannot) go through open_mac_file, so we
6422 // need to do it here manually: If we have tmac.FOOBAR, try
6423 // FOOBAR.tmac and vice versa
6424 if (!fp) {
6425 const char *fn = nm.contents();
6426 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
6427 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
6428 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
6429 strcat(s, MACRO_POSTFIX);
6430 fp = mac_path->open_file(s, &path);
6431 a_delete s;
6433 if (!fp) {
6434 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
6435 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
6436 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
6437 strcpy(s, MACRO_PREFIX);
6438 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
6439 fp = mac_path->open_file(s, &path);
6440 a_delete s;
6444 if (fp) {
6445 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6446 a_delete path;
6448 else
6449 error("can't find macro file `%1'", nm.contents());
6450 tok.next();
6454 static void process_input_file(const char *name)
6456 FILE *fp;
6457 if (strcmp(name, "-") == 0) {
6458 clearerr(stdin);
6459 fp = stdin;
6461 else {
6462 errno = 0;
6463 fp = fopen(name, "r");
6464 if (!fp)
6465 fatal("can't open `%1': %2", name, strerror(errno));
6467 input_stack::push(new file_iterator(fp, name));
6468 tok.next();
6469 process_input_stack();
6472 // make sure the_input is empty before calling this
6474 static int evaluate_expression(const char *expr, units *res)
6476 input_stack::push(make_temp_iterator(expr));
6477 tok.next();
6478 int success = get_number(res, 'u');
6479 while (input_stack::get(0) != EOF)
6481 return success;
6484 static void do_register_assignment(const char *s)
6486 const char *p = strchr(s, '=');
6487 if (!p) {
6488 char buf[2];
6489 buf[0] = s[0];
6490 buf[1] = 0;
6491 units n;
6492 if (evaluate_expression(s + 1, &n))
6493 set_number_reg(buf, n);
6495 else {
6496 char *buf = new char[p - s + 1];
6497 memcpy(buf, s, p - s);
6498 buf[p - s] = 0;
6499 units n;
6500 if (evaluate_expression(p + 1, &n))
6501 set_number_reg(buf, n);
6502 a_delete buf;
6506 static void set_string(const char *name, const char *value)
6508 macro *m = new macro;
6509 for (const char *p = value; *p; p++)
6510 if (!invalid_input_char((unsigned char)*p))
6511 m->append(*p);
6512 request_dictionary.define(name, m);
6515 static void do_string_assignment(const char *s)
6517 const char *p = strchr(s, '=');
6518 if (!p) {
6519 char buf[2];
6520 buf[0] = s[0];
6521 buf[1] = 0;
6522 set_string(buf, s + 1);
6524 else {
6525 char *buf = new char[p - s + 1];
6526 memcpy(buf, s, p - s);
6527 buf[p - s] = 0;
6528 set_string(buf, p + 1);
6529 a_delete buf;
6533 struct string_list {
6534 const char *s;
6535 string_list *next;
6536 string_list(const char *ss) : s(ss), next(0) {}
6539 #if 0
6540 static void prepend_string(const char *s, string_list **p)
6542 string_list *l = new string_list(s);
6543 l->next = *p;
6544 *p = l;
6546 #endif
6548 static void add_string(const char *s, string_list **p)
6550 while (*p)
6551 p = &((*p)->next);
6552 *p = new string_list(s);
6555 void usage(FILE *stream, const char *prog)
6557 fprintf(stream,
6558 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
6559 " -rcn -Tname -Fdir -Mdir [files...]\n",
6560 prog);
6563 int main(int argc, char **argv)
6565 program_name = argv[0];
6566 static char stderr_buf[BUFSIZ];
6567 setbuf(stderr, stderr_buf);
6568 int c;
6569 string_list *macros = 0;
6570 string_list *register_assignments = 0;
6571 string_list *string_assignments = 0;
6572 int iflag = 0;
6573 int tflag = 0;
6574 int fflag = 0;
6575 int nflag = 0;
6576 int no_rc = 0; // don't process troffrc and troffrc-end
6577 int next_page_number;
6578 opterr = 0;
6579 hresolution = vresolution = 1;
6580 // restore $PATH if called from groff
6581 char* groff_path = getenv("GROFF_PATH__");
6582 if (groff_path) {
6583 string e = "PATH";
6584 e += '=';
6585 if (*groff_path)
6586 e += groff_path;
6587 e += '\0';
6588 if (putenv(strsave(e.contents())))
6589 fatal("putenv failed");
6591 static const struct option long_options[] = {
6592 { "help", no_argument, 0, CHAR_MAX + 1 },
6593 { "version", no_argument, 0, 'v' },
6594 { 0, 0, 0, 0 }
6596 while ((c = getopt_long(argc, argv, "abcivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU",
6597 long_options, 0))
6598 != EOF)
6599 switch(c) {
6600 case 'v':
6602 printf("GNU troff (groff) version %s\n", Version_string);
6603 exit(0);
6604 break;
6606 case 'T':
6607 device = optarg;
6608 tflag = 1;
6609 is_html = (strcmp(device, "html") == 0);
6610 break;
6611 case 'C':
6612 compatible_flag = 1;
6613 // fall through
6614 case 'c':
6615 disable_color_flag = 1;
6616 break;
6617 case 'M':
6618 macro_path.command_line_dir(optarg);
6619 safer_macro_path.command_line_dir(optarg);
6620 config_macro_path.command_line_dir(optarg);
6621 break;
6622 case 'F':
6623 font::command_line_font_dir(optarg);
6624 break;
6625 case 'm':
6626 add_string(optarg, &macros);
6627 break;
6628 case 'E':
6629 inhibit_errors = 1;
6630 break;
6631 case 'R':
6632 no_rc = 1;
6633 break;
6634 case 'w':
6635 enable_warning(optarg);
6636 break;
6637 case 'W':
6638 disable_warning(optarg);
6639 break;
6640 case 'i':
6641 iflag = 1;
6642 break;
6643 case 'b':
6644 backtrace_flag = 1;
6645 break;
6646 case 'a':
6647 ascii_output_flag = 1;
6648 break;
6649 case 'z':
6650 suppress_output_flag = 1;
6651 break;
6652 case 'n':
6653 if (sscanf(optarg, "%d", &next_page_number) == 1)
6654 nflag++;
6655 else
6656 error("bad page number");
6657 break;
6658 case 'o':
6659 parse_output_page_list(optarg);
6660 break;
6661 case 'd':
6662 if (*optarg == '\0')
6663 error("`-d' requires non-empty argument");
6664 else
6665 add_string(optarg, &string_assignments);
6666 break;
6667 case 'r':
6668 if (*optarg == '\0')
6669 error("`-r' requires non-empty argument");
6670 else
6671 add_string(optarg, &register_assignments);
6672 break;
6673 case 'f':
6674 default_family = symbol(optarg);
6675 fflag = 1;
6676 break;
6677 case 'q':
6678 case 's':
6679 case 't':
6680 // silently ignore these
6681 break;
6682 case 'U':
6683 safer_flag = 0; // unsafe behaviour
6684 break;
6685 case CHAR_MAX + 1: // --help
6686 usage(stdout, argv[0]);
6687 exit(0);
6688 break;
6689 case '?':
6690 usage(stderr, argv[0]);
6691 exit(1);
6692 break; // never reached
6693 default:
6694 assert(0);
6696 if (!safer_flag)
6697 mac_path = &macro_path;
6698 set_string(".T", device);
6699 init_charset_table();
6700 init_hpf_code_table();
6701 if (!font::load_desc())
6702 fatal("sorry, I can't continue");
6703 units_per_inch = font::res;
6704 hresolution = font::hor;
6705 vresolution = font::vert;
6706 sizescale = font::sizescale;
6707 tcommand_flag = font::tcommand;
6708 warn_scale = (double)units_per_inch;
6709 warn_scaling_indicator = 'i';
6710 if (!fflag && font::family != 0 && *font::family != '\0')
6711 default_family = symbol(font::family);
6712 font_size::init_size_table(font::sizes);
6713 int i;
6714 int j = 1;
6715 if (font::style_table) {
6716 for (i = 0; font::style_table[i]; i++)
6717 mount_style(j++, symbol(font::style_table[i]));
6719 for (i = 0; font::font_name_table[i]; i++, j++)
6720 // In the DESC file a font name of 0 (zero) means leave this
6721 // position empty.
6722 if (strcmp(font::font_name_table[i], "0") != 0)
6723 mount_font(j, symbol(font::font_name_table[i]));
6724 curdiv = topdiv = new top_level_diversion;
6725 if (nflag)
6726 topdiv->set_next_page_number(next_page_number);
6727 init_input_requests();
6728 init_env_requests();
6729 init_div_requests();
6730 #ifdef COLUMN
6731 init_column_requests();
6732 #endif /* COLUMN */
6733 init_node_requests();
6734 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
6735 init_registers();
6736 init_reg_requests();
6737 init_hyphen_requests();
6738 init_environments();
6739 while (string_assignments) {
6740 do_string_assignment(string_assignments->s);
6741 string_list *tem = string_assignments;
6742 string_assignments = string_assignments->next;
6743 delete tem;
6745 while (register_assignments) {
6746 do_register_assignment(register_assignments->s);
6747 string_list *tem = register_assignments;
6748 register_assignments = register_assignments->next;
6749 delete tem;
6751 if (!no_rc)
6752 process_startup_file(INITIAL_STARTUP_FILE);
6753 while (macros) {
6754 process_macro_file(macros->s);
6755 string_list *tem = macros;
6756 macros = macros->next;
6757 delete tem;
6759 if (!no_rc)
6760 process_startup_file(FINAL_STARTUP_FILE);
6761 for (i = optind; i < argc; i++)
6762 process_input_file(argv[i]);
6763 if (optind >= argc || iflag)
6764 process_input_file("-");
6765 exit_troff();
6766 return 0; // not reached
6769 void warn_request()
6771 int n;
6772 if (has_arg() && get_integer(&n)) {
6773 if (n & ~WARN_TOTAL) {
6774 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
6775 n &= WARN_TOTAL;
6777 warning_mask = n;
6779 else
6780 warning_mask = WARN_TOTAL;
6781 skip_line();
6784 static void init_registers()
6786 #ifdef LONG_FOR_TIME_T
6787 long
6788 #else /* not LONG_FOR_TIME_T */
6789 time_t
6790 #endif /* not LONG_FOR_TIME_T */
6791 t = time(0);
6792 // Use struct here to work around misfeature in old versions of g++.
6793 struct tm *tt = localtime(&t);
6794 set_number_reg("dw", int(tt->tm_wday + 1));
6795 set_number_reg("dy", int(tt->tm_mday));
6796 set_number_reg("mo", int(tt->tm_mon + 1));
6797 set_number_reg("year", int(1900 + tt->tm_year));
6798 set_number_reg("yr", int(tt->tm_year));
6799 set_number_reg("$$", getpid());
6800 number_reg_dictionary.define(".A",
6801 new constant_reg(ascii_output_flag
6802 ? "1"
6803 : "0"));
6807 * registers associated with \O
6810 static int output_reg_minx_contents = -1;
6811 static int output_reg_miny_contents = -1;
6812 static int output_reg_maxx_contents = -1;
6813 static int output_reg_maxy_contents = -1;
6815 void check_output_limits(int x, int y)
6817 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
6818 output_reg_minx_contents = x;
6819 if (x > output_reg_maxx_contents)
6820 output_reg_maxx_contents = x;
6821 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
6822 output_reg_miny_contents = y;
6823 if (y > output_reg_maxy_contents)
6824 output_reg_maxy_contents = y;
6827 void reset_output_registers(int miny)
6829 // fprintf(stderr, "reset_output_registers\n");
6830 output_reg_minx_contents = -1;
6831 output_reg_miny_contents = -1;
6832 output_reg_maxx_contents = -1;
6833 output_reg_maxy_contents = -1;
6836 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
6838 *minx = output_reg_minx_contents;
6839 *miny = output_reg_miny_contents;
6840 *maxx = output_reg_maxx_contents;
6841 *maxy = output_reg_maxy_contents;
6844 void init_input_requests()
6846 init_request("ds", define_string);
6847 init_request("as", append_string);
6848 init_request("ds1", define_nocomp_string);
6849 init_request("as1", append_nocomp_string);
6850 init_request("de", define_macro);
6851 init_request("dei", define_indirect_macro);
6852 init_request("de1", define_nocomp_macro);
6853 init_request("am", append_macro);
6854 init_request("ami", append_indirect_macro);
6855 init_request("am1", append_nocomp_macro);
6856 init_request("ig", ignore);
6857 init_request("rm", remove_macro);
6858 init_request("rn", rename_macro);
6859 init_request("nop", nop_request);
6860 init_request("if", if_request);
6861 init_request("ie", if_else_request);
6862 init_request("el", else_request);
6863 init_request("so", source);
6864 init_request("nx", next_file);
6865 init_request("pm", print_macros);
6866 init_request("eo", escape_off);
6867 init_request("ec", set_escape_char);
6868 init_request("ecs", save_escape_char);
6869 init_request("ecr", restore_escape_char);
6870 init_request("pc", set_page_character);
6871 init_request("tm", terminal);
6872 init_request("tm1", terminal1);
6873 init_request("tmc", terminal_continue);
6874 init_request("ex", exit_request);
6875 init_request("return", return_macro_request);
6876 init_request("em", end_macro);
6877 init_request("blm", blank_line_macro);
6878 init_request("tr", translate);
6879 init_request("trnt", translate_no_transparent);
6880 init_request("trin", translate_input);
6881 init_request("ab", abort_request);
6882 init_request("pi", pipe_output);
6883 init_request("cf", copy_file);
6884 init_request("sy", system_request);
6885 init_request("lf", line_file);
6886 init_request("cflags", char_flags);
6887 init_request("shift", shift);
6888 init_request("rd", read_request);
6889 init_request("cp", compatible);
6890 init_request("char", define_character);
6891 init_request("fchar", define_fallback_character);
6892 init_request("rchar", remove_character);
6893 init_request("hcode", hyphenation_code);
6894 init_request("hpfcode", hyphenation_patterns_file_code);
6895 init_request("while", while_request);
6896 init_request("break", while_break_request);
6897 init_request("continue", while_continue_request);
6898 init_request("als", alias_macro);
6899 init_request("backtrace", backtrace_request);
6900 init_request("chop", chop_macro);
6901 init_request("substring", substring_macro);
6902 init_request("length", length_macro);
6903 init_request("asciify", asciify_macro);
6904 init_request("unformat", unformat_macro);
6905 init_request("warn", warn_request);
6906 init_request("open", open_request);
6907 init_request("opena", opena_request);
6908 init_request("close", close_request);
6909 init_request("write", write_request);
6910 init_request("writec", write_request_continue);
6911 init_request("writem", write_macro_request);
6912 init_request("trf", transparent_file);
6913 #ifdef WIDOW_CONTROL
6914 init_request("fpl", flush_pending_lines);
6915 #endif /* WIDOW_CONTROL */
6916 init_request("nroff", nroff_request);
6917 init_request("troff", troff_request);
6918 #ifdef COLUMN
6919 init_request("vj", vjustify);
6920 #endif /* COLUMN */
6921 init_request("mso", macro_source);
6922 init_request("do", do_request);
6923 #ifndef POPEN_MISSING
6924 init_request("pso", pipe_source);
6925 #endif /* not POPEN_MISSING */
6926 init_request("psbb", ps_bbox_request);
6927 init_request("defcolor", define_color);
6928 init_request("warnscale", warnscale_request);
6929 init_request("spreadwarn", spreadwarn_request);
6930 number_reg_dictionary.define("systat", new variable_reg(&system_status));
6931 number_reg_dictionary.define("slimit",
6932 new variable_reg(&input_stack::limit));
6933 number_reg_dictionary.define(".$", new nargs_reg);
6934 number_reg_dictionary.define(".c", new lineno_reg);
6935 number_reg_dictionary.define("c.", new writable_lineno_reg);
6936 number_reg_dictionary.define(".F", new filename_reg);
6937 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
6938 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
6939 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
6940 number_reg_dictionary.define(".R", new constant_reg("10000"));
6941 extern const char *major_version;
6942 number_reg_dictionary.define(".x", new constant_reg(major_version));
6943 extern const char *minor_version;
6944 number_reg_dictionary.define(".y", new constant_reg(minor_version));
6945 extern const char *revision;
6946 number_reg_dictionary.define(".Y", new constant_reg(revision));
6947 number_reg_dictionary.define(".g", new constant_reg("1"));
6948 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
6949 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
6950 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
6951 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
6952 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
6953 number_reg_dictionary.define("opminx",
6954 new variable_reg(&output_reg_minx_contents));
6955 number_reg_dictionary.define("opminy",
6956 new variable_reg(&output_reg_miny_contents));
6957 number_reg_dictionary.define("opmaxx",
6958 new variable_reg(&output_reg_maxx_contents));
6959 number_reg_dictionary.define("opmaxy",
6960 new variable_reg(&output_reg_maxy_contents));
6963 object_dictionary request_dictionary(501);
6965 void init_request(const char *s, REQUEST_FUNCP f)
6967 request_dictionary.define(s, new request(f));
6970 static request_or_macro *lookup_request(symbol nm)
6972 assert(!nm.is_null());
6973 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
6974 if (p == 0) {
6975 warning(WARN_MAC, "`%1' not defined", nm.contents());
6976 p = new macro;
6977 request_dictionary.define(nm, p);
6979 return p;
6982 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
6984 // Don't interpret character definitions in compatible mode.
6985 int old_compatible_flag = compatible_flag;
6986 compatible_flag = 0;
6987 int old_escape_char = escape_char;
6988 escape_char = '\\';
6989 macro *mac = ci->set_macro(0);
6990 assert(mac != 0);
6991 environment *oldenv = curenv;
6992 environment env(envp);
6993 curenv = &env;
6994 curenv->set_composite();
6995 token old_tok = tok;
6996 input_stack::add_boundary();
6997 string_iterator *si =
6998 new string_iterator(*mac, "composite character", ci->nm);
6999 input_stack::push(si);
7000 // we don't use process_input_stack, because we don't want to recognise
7001 // requests
7002 for (;;) {
7003 tok.next();
7004 if (tok.eof())
7005 break;
7006 if (tok.newline()) {
7007 error("composite character mustn't contain newline");
7008 while (!tok.eof())
7009 tok.next();
7010 break;
7012 else
7013 tok.process();
7015 node *n = curenv->extract_output_line();
7016 input_stack::remove_boundary();
7017 ci->set_macro(mac);
7018 tok = old_tok;
7019 curenv = oldenv;
7020 compatible_flag = old_compatible_flag;
7021 escape_char = old_escape_char;
7022 return n;
7025 static node *read_draw_node()
7027 token start;
7028 start.next();
7029 if (!start.delimiter(1)){
7030 do {
7031 tok.next();
7032 } while (tok != start && !tok.newline() && !tok.eof());
7034 else {
7035 tok.next();
7036 if (tok == start)
7037 error("missing argument");
7038 else {
7039 unsigned char type = tok.ch();
7040 tok.next();
7041 int maxpoints = 10;
7042 hvpair *point = new hvpair[maxpoints];
7043 int npoints = 0;
7044 int no_last_v = 0;
7045 int err = 0;
7046 int i;
7047 for (i = 0; tok != start; i++) {
7048 if (i == maxpoints) {
7049 hvpair *oldpoint = point;
7050 point = new hvpair[maxpoints*2];
7051 for (int j = 0; j < maxpoints; j++)
7052 point[j] = oldpoint[j];
7053 maxpoints *= 2;
7054 a_delete oldpoint;
7056 if (!get_hunits(&point[i].h,
7057 type == 'f' || type == 't' ? 'u' : 'm')) {
7058 err = 1;
7059 break;
7061 ++npoints;
7062 tok.skip();
7063 point[i].v = V0;
7064 if (tok == start) {
7065 no_last_v = 1;
7066 break;
7068 if (!get_vunits(&point[i].v, 'v')) {
7069 err = 1;
7070 break;
7072 tok.skip();
7074 while (tok != start && !tok.newline() && !tok.eof())
7075 tok.next();
7076 if (!err) {
7077 switch (type) {
7078 case 'l':
7079 if (npoints != 1 || no_last_v) {
7080 error("two arguments needed for line");
7081 npoints = 1;
7083 break;
7084 case 'c':
7085 if (npoints != 1 || !no_last_v) {
7086 error("one argument needed for circle");
7087 npoints = 1;
7088 point[0].v = V0;
7090 break;
7091 case 'e':
7092 if (npoints != 1 || no_last_v) {
7093 error("two arguments needed for ellipse");
7094 npoints = 1;
7096 break;
7097 case 'a':
7098 if (npoints != 2 || no_last_v) {
7099 error("four arguments needed for arc");
7100 npoints = 2;
7102 break;
7103 case '~':
7104 if (no_last_v)
7105 error("even number of arguments needed for spline");
7106 break;
7107 case 'f':
7108 if (npoints != 1 || !no_last_v) {
7109 error("one argument needed for gray shade");
7110 npoints = 1;
7111 point[0].v = V0;
7113 default:
7114 // silently pass it through
7115 break;
7117 draw_node *dn = new draw_node(type, point, npoints,
7118 curenv->get_font_size());
7119 a_delete point;
7120 return dn;
7122 else {
7123 a_delete point;
7127 return 0;
7130 static struct {
7131 const char *name;
7132 int mask;
7133 } warning_table[] = {
7134 { "char", WARN_CHAR },
7135 { "range", WARN_RANGE },
7136 { "break", WARN_BREAK },
7137 { "delim", WARN_DELIM },
7138 { "el", WARN_EL },
7139 { "scale", WARN_SCALE },
7140 { "number", WARN_NUMBER },
7141 { "syntax", WARN_SYNTAX },
7142 { "tab", WARN_TAB },
7143 { "right-brace", WARN_RIGHT_BRACE },
7144 { "missing", WARN_MISSING },
7145 { "input", WARN_INPUT },
7146 { "escape", WARN_ESCAPE },
7147 { "space", WARN_SPACE },
7148 { "font", WARN_FONT },
7149 { "di", WARN_DI },
7150 { "mac", WARN_MAC },
7151 { "reg", WARN_REG },
7152 { "ig", WARN_IG },
7153 { "color", WARN_COLOR },
7154 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
7155 { "w", WARN_TOTAL },
7156 { "default", DEFAULT_WARNING_MASK },
7159 static int lookup_warning(const char *name)
7161 for (unsigned int i = 0;
7162 i < sizeof(warning_table)/sizeof(warning_table[0]);
7163 i++)
7164 if (strcmp(name, warning_table[i].name) == 0)
7165 return warning_table[i].mask;
7166 return 0;
7169 static void enable_warning(const char *name)
7171 int mask = lookup_warning(name);
7172 if (mask)
7173 warning_mask |= mask;
7174 else
7175 error("unknown warning `%1'", name);
7178 static void disable_warning(const char *name)
7180 int mask = lookup_warning(name);
7181 if (mask)
7182 warning_mask &= ~mask;
7183 else
7184 error("unknown warning `%1'", name);
7187 static void copy_mode_error(const char *format,
7188 const errarg &arg1,
7189 const errarg &arg2,
7190 const errarg &arg3)
7192 if (ignoring) {
7193 static const char prefix[] = "(in ignored input) ";
7194 char *s = new char[sizeof(prefix) + strlen(format)];
7195 strcpy(s, prefix);
7196 strcat(s, format);
7197 warning(WARN_IG, s, arg1, arg2, arg3);
7198 a_delete s;
7200 else
7201 error(format, arg1, arg2, arg3);
7204 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
7206 static void do_error(error_type type,
7207 const char *format,
7208 const errarg &arg1,
7209 const errarg &arg2,
7210 const errarg &arg3)
7212 const char *filename;
7213 int lineno;
7214 if (inhibit_errors && type < FATAL)
7215 return;
7216 if (backtrace_flag)
7217 input_stack::backtrace();
7218 if (!get_file_line(&filename, &lineno))
7219 filename = 0;
7220 if (filename)
7221 errprint("%1:%2: ", filename, lineno);
7222 else if (program_name)
7223 fprintf(stderr, "%s: ", program_name);
7224 switch (type) {
7225 case FATAL:
7226 fputs("fatal error: ", stderr);
7227 break;
7228 case ERROR:
7229 break;
7230 case WARNING:
7231 fputs("warning: ", stderr);
7232 break;
7233 case OUTPUT_WARNING:
7234 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
7235 fprintf(stderr, "warning [p %d, %.1f%c",
7236 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
7237 if (topdiv != curdiv) {
7238 double fromtop1 = curdiv->get_vertical_position().to_units()
7239 / warn_scale;
7240 fprintf(stderr, ", div `%s', %.1f%c",
7241 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
7243 fprintf(stderr, "]: ");
7244 break;
7246 errprint(format, arg1, arg2, arg3);
7247 fputc('\n', stderr);
7248 fflush(stderr);
7249 if (type == FATAL)
7250 cleanup_and_exit(1);
7253 int warning(warning_type t,
7254 const char *format,
7255 const errarg &arg1,
7256 const errarg &arg2,
7257 const errarg &arg3)
7259 if ((t & warning_mask) != 0) {
7260 do_error(WARNING, format, arg1, arg2, arg3);
7261 return 1;
7263 else
7264 return 0;
7267 int output_warning(warning_type t,
7268 const char *format,
7269 const errarg &arg1,
7270 const errarg &arg2,
7271 const errarg &arg3)
7273 if ((t & warning_mask) != 0) {
7274 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
7275 return 1;
7277 else
7278 return 0;
7281 void error(const char *format,
7282 const errarg &arg1,
7283 const errarg &arg2,
7284 const errarg &arg3)
7286 do_error(ERROR, format, arg1, arg2, arg3);
7289 void fatal(const char *format,
7290 const errarg &arg1,
7291 const errarg &arg2,
7292 const errarg &arg3)
7294 do_error(FATAL, format, arg1, arg2, arg3);
7297 void fatal_with_file_and_line(const char *filename, int lineno,
7298 const char *format,
7299 const errarg &arg1,
7300 const errarg &arg2,
7301 const errarg &arg3)
7303 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
7304 errprint(format, arg1, arg2, arg3);
7305 fputc('\n', stderr);
7306 fflush(stderr);
7307 cleanup_and_exit(1);
7310 void error_with_file_and_line(const char *filename, int lineno,
7311 const char *format,
7312 const errarg &arg1,
7313 const errarg &arg2,
7314 const errarg &arg3)
7316 fprintf(stderr, "%s:%d: error: ", filename, lineno);
7317 errprint(format, arg1, arg2, arg3);
7318 fputc('\n', stderr);
7319 fflush(stderr);
7322 dictionary charinfo_dictionary(501);
7324 charinfo *get_charinfo(symbol nm)
7326 void *p = charinfo_dictionary.lookup(nm);
7327 if (p != 0)
7328 return (charinfo *)p;
7329 charinfo *cp = new charinfo(nm);
7330 (void)charinfo_dictionary.lookup(nm, cp);
7331 return cp;
7334 int charinfo::next_index = 0;
7336 charinfo::charinfo(symbol s)
7337 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
7338 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
7339 not_found(0), transparent_translate(1), translate_input(0),
7340 fallback(0), nm(s)
7342 index = next_index++;
7345 void charinfo::set_hyphenation_code(unsigned char c)
7347 hyphenation_code = c;
7350 void charinfo::set_translation(charinfo *ci, int tt, int ti)
7352 translation = ci;
7353 if (ci && ti) {
7354 if (hyphenation_code != 0)
7355 ci->set_hyphenation_code(hyphenation_code);
7356 if (asciify_code != 0)
7357 ci->set_asciify_code(asciify_code);
7358 else if (ascii_code != 0)
7359 ci->set_asciify_code(ascii_code);
7360 ci->set_translation_input();
7362 special_translation = TRANSLATE_NONE;
7363 transparent_translate = tt;
7366 void charinfo::set_special_translation(int c, int tt)
7368 special_translation = c;
7369 translation = 0;
7370 transparent_translate = tt;
7373 void charinfo::set_ascii_code(unsigned char c)
7375 ascii_code = c;
7378 void charinfo::set_asciify_code(unsigned char c)
7380 asciify_code = c;
7383 macro *charinfo::set_macro(macro *m, int f)
7385 macro *tem = mac;
7386 mac = m;
7387 fallback = f;
7388 return tem;
7391 void charinfo::set_number(int n)
7393 number = n;
7394 flags |= NUMBERED;
7397 int charinfo::get_number()
7399 assert(flags & NUMBERED);
7400 return number;
7403 symbol UNNAMED_SYMBOL("---");
7405 // For numbered characters not between 0 and 255, we make a symbol out
7406 // of the number and store them in this dictionary.
7408 dictionary numbered_charinfo_dictionary(11);
7410 charinfo *get_charinfo_by_number(int n)
7412 static charinfo *number_table[256];
7414 if (n >= 0 && n < 256) {
7415 charinfo *ci = number_table[n];
7416 if (!ci) {
7417 ci = new charinfo(UNNAMED_SYMBOL);
7418 ci->set_number(n);
7419 number_table[n] = ci;
7421 return ci;
7423 else {
7424 symbol ns(i_to_a(n));
7425 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
7426 if (!ci) {
7427 ci = new charinfo(UNNAMED_SYMBOL);
7428 ci->set_number(n);
7429 numbered_charinfo_dictionary.lookup(ns, ci);
7431 return ci;
7435 int font::name_to_index(const char *nm)
7437 charinfo *ci;
7438 if (nm[1] == 0)
7439 ci = charset_table[nm[0] & 0xff];
7440 else if (nm[0] == '\\' && nm[2] == 0)
7441 ci = get_charinfo(symbol(nm + 1));
7442 else
7443 ci = get_charinfo(symbol(nm));
7444 if (ci == 0)
7445 return -1;
7446 else
7447 return ci->get_index();
7450 int font::number_to_index(int n)
7452 return get_charinfo_by_number(n)->get_index();