2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
4 Free Software Foundation, Inc.
5 Written by James Clark (jjc@jclark.com)
7 This file is part of groff.
9 groff is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 groff is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
25 #include "dictionary.h"
27 #include "stringclass.h"
37 #include "macropath.h"
42 // Needed for getpid() and isatty()
47 #ifdef NEED_DECLARATION_PUTENV
49 int putenv(const char *);
51 #endif /* NEED_DECLARATION_PUTENV */
53 #define MACRO_PREFIX "tmac."
54 #define MACRO_POSTFIX ".tmac"
55 #define INITIAL_STARTUP_FILE "troffrc"
56 #define FINAL_STARTUP_FILE "troffrc-end"
57 #define DEFAULT_INPUT_STACK_LIMIT 1000
59 #ifndef DEFAULT_WARNING_MASK
60 // warnings that are enabled by default
61 #define DEFAULT_WARNING_MASK \
62 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
65 // initial size of buffer for reading names; expanded as necessary
68 extern "C" const char *program_name;
69 extern "C" const char *Version_string;
72 void init_column_requests();
75 static node *read_draw_node();
76 static void read_color_draw_node(token &);
77 static void push_token(const token &);
82 void transparent_file();
86 int color_flag = 1; // colors are on by default
87 static int backtrace_flag = 0;
89 char *pipe_command = 0;
91 charinfo *charset_table[256];
92 unsigned char hpf_code_table[256];
94 static int warning_mask = DEFAULT_WARNING_MASK;
95 static int inhibit_errors = 0;
96 static int ignoring = 0;
98 static void enable_warning(const char *);
99 static void disable_warning(const char *);
101 static int escape_char = '\\';
102 static symbol end_macro_name;
103 static symbol blank_line_macro_name;
104 static int compatible_flag = 0;
105 int ascii_output_flag = 0;
106 int suppress_output_flag = 0;
108 int begin_level = 0; // number of nested \O escapes
110 int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
111 // \O[345], \R, \s, or \S has been processed
113 int old_have_input = 0; // value of have_input right before \n
114 int tcommand_flag = 0;
115 int unsafe_flag = 0; // safer by default
117 int have_string_arg = 0; // whether we have \*[foo bar...]
119 double spread_limit = -3.0 - 1.0; // negative means deactivated
122 char warn_scaling_indicator;
123 int debug_state = 0; // turns on debugging of the html troff state
125 search_path *mac_path = &safer_macro_path;
127 // Defaults to the current directory.
128 search_path include_search_path(0, 0, 0, 1);
130 static int get_copy(node**, int = 0, int = 0);
131 static void copy_mode_error(const char *,
132 const errarg & = empty_errarg,
133 const errarg & = empty_errarg,
134 const errarg & = empty_errarg);
136 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
137 static symbol read_escape_name(read_mode = NO_ARGS);
138 static symbol read_long_escape_name(read_mode = NO_ARGS);
139 static void interpolate_string(symbol);
140 static void interpolate_string_with_args(symbol);
141 static void interpolate_macro(symbol, int = 0);
142 static void interpolate_number_format(symbol);
143 static void interpolate_environment_variable(symbol);
145 static symbol composite_glyph_name(symbol);
146 static void interpolate_arg(symbol);
147 static request_or_macro *lookup_request(symbol);
148 static int get_delim_number(units *, unsigned char);
149 static int get_delim_number(units *, unsigned char, units);
150 static symbol do_get_long_name(int, char);
151 static int get_line_arg(units *res, unsigned char si, charinfo **cp);
152 static int read_size(int *);
153 static symbol get_delim_name();
154 static void init_registers();
155 static void trapping_blank_line();
157 class input_iterator;
158 input_iterator *make_temp_iterator(const char *);
159 const char *input_char_description(int);
161 void process_input_stack();
162 void chop_macro(); // declare to avoid friend name injection
165 void set_escape_char()
169 error("bad escape character");
173 escape_char = tok.ch();
186 static int saved_escape_char = '\\';
188 void save_escape_char()
190 saved_escape_char = escape_char;
194 void restore_escape_char()
196 escape_char = saved_escape_char;
202 class input_iterator {
205 input_iterator(int is_div);
206 virtual ~input_iterator() {}
208 friend class input_stack;
210 statem *diversion_state;
212 const unsigned char *ptr;
213 const unsigned char *eptr;
214 input_iterator *next;
216 virtual int fill(node **);
218 virtual int has_args() { return 0; }
219 virtual int nargs() { return 0; }
220 virtual input_iterator *get_arg(int) { return 0; }
221 virtual arg_list *get_arg_list() { return 0; }
222 virtual symbol get_macro_name() { return NULL_SYMBOL; }
223 virtual int space_follows_arg(int) { return 0; }
224 virtual int get_break_flag() { return 0; }
225 virtual int get_location(int, const char **, int *) { return 0; }
226 virtual void backtrace() {}
227 virtual int set_location(const char *, int) { return 0; }
228 virtual int next_file(FILE *, const char *) { return 0; }
229 virtual void shift(int) {}
230 virtual int is_boundary() {return 0; }
231 virtual int is_file() { return 0; }
232 virtual int is_macro() { return 0; }
233 virtual void save_compatible_flag(int) {}
234 virtual int get_compatible_flag() { return 0; }
237 input_iterator::input_iterator()
238 : is_diversion(0), ptr(0), eptr(0)
242 input_iterator::input_iterator(int is_div)
243 : is_diversion(is_div), ptr(0), eptr(0)
247 int input_iterator::fill(node **)
252 int input_iterator::peek()
257 inline int input_iterator::get(node **p)
259 return ptr < eptr ? *ptr++ : fill(p);
262 class input_boundary : public input_iterator {
264 int is_boundary() { return 1; }
267 class input_return_boundary : public input_iterator {
269 int is_boundary() { return 2; }
272 class file_iterator : public input_iterator {
275 const char *filename;
279 enum { BUF_SIZE = 512 };
280 unsigned char buf[BUF_SIZE];
283 file_iterator(FILE *, const char *, int = 0);
287 int get_location(int, const char **, int *);
289 int set_location(const char *, int);
290 int next_file(FILE *, const char *);
294 file_iterator::file_iterator(FILE *f, const char *fn, int po)
295 : fp(f), lineno(1), filename(fn), popened(po),
296 newline_flag(0), seen_escape(0)
298 if ((font::use_charnames_in_special) && (fn != 0)) {
301 the_output->put_filename(fn, po);
305 file_iterator::~file_iterator()
310 void file_iterator::close()
314 #ifndef POPEN_MISSING
317 #endif /* not POPEN_MISSING */
322 int file_iterator::is_file()
327 int file_iterator::next_file(FILE *f, const char *s)
341 int file_iterator::fill(node **)
346 unsigned char *p = buf;
348 unsigned char *e = p + BUF_SIZE;
353 if (invalid_input_char(c))
354 warning(WARN_INPUT, "invalid input character code %1", int(c));
362 seen_escape = (c == '\\');
375 int file_iterator::peek()
378 while (invalid_input_char(c)) {
379 warning(WARN_INPUT, "invalid input character code %1", int(c));
387 int file_iterator::get_location(int /*allow_macro*/,
388 const char **filenamep, int *linenop)
391 if (filename != 0 && strcmp(filename, "-") == 0)
392 *filenamep = "<standard input>";
394 *filenamep = filename;
398 void file_iterator::backtrace()
400 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
401 popened ? "process" : "file");
404 int file_iterator::set_location(const char *f, int ln)
410 the_output->put_filename(f, 0);
416 input_iterator nil_iterator;
420 static int get(node **);
422 static void push(input_iterator *);
423 static input_iterator *get_arg(int);
424 static arg_list *get_arg_list();
425 static symbol get_macro_name();
426 static int space_follows_arg(int);
427 static int get_break_flag();
429 static int get_location(int, const char **, int *);
430 static int set_location(const char *, int);
431 static void backtrace();
432 static void backtrace_all();
433 static void next_file(FILE *, const char *);
434 static void end_file();
435 static void shift(int n);
436 static void add_boundary();
437 static void add_return_boundary();
438 static int is_return_boundary();
439 static void remove_boundary();
440 static int get_level();
441 static int get_div_level();
442 static void increase_level();
443 static void decrease_level();
445 static void pop_macro();
446 static void save_compatible_flag(int);
447 static int get_compatible_flag();
448 static statem *get_diversion_state();
449 static void check_end_diversion(input_iterator *t);
451 static int div_level;
452 static statem *diversion_state;
454 static input_iterator *top;
456 static int finish_get(node **);
457 static int finish_peek();
460 input_iterator *input_stack::top = &nil_iterator;
461 int input_stack::level = 0;
462 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
463 int input_stack::div_level = 0;
464 statem *input_stack::diversion_state = NULL;
468 inline int input_stack::get_level()
473 inline void input_stack::increase_level()
478 inline void input_stack::decrease_level()
483 inline int input_stack::get_div_level()
488 inline int input_stack::get(node **np)
490 int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
492 old_have_input = have_input;
498 int input_stack::finish_get(node **np)
501 int c = top->fill(np);
502 if (c != EOF || top->is_boundary())
504 if (top == &nil_iterator)
506 input_iterator *tem = top;
507 check_end_diversion(tem);
508 #if defined(DEBUGGING)
510 if (tem->is_diversion)
512 "in diversion level = %d\n", input_stack::get_div_level());
517 if (top->ptr < top->eptr)
524 inline int input_stack::peek()
526 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
529 void input_stack::check_end_diversion(input_iterator *t)
531 if (t->is_diversion) {
533 diversion_state = t->diversion_state;
537 int input_stack::finish_peek()
541 if (c != EOF || top->is_boundary())
543 if (top == &nil_iterator)
545 input_iterator *tem = top;
546 check_end_diversion(tem);
550 if (top->ptr < top->eptr)
557 void input_stack::add_boundary()
559 push(new input_boundary);
562 void input_stack::add_return_boundary()
564 push(new input_return_boundary);
567 int input_stack::is_return_boundary()
569 return top->is_boundary() == 2;
572 void input_stack::remove_boundary()
574 assert(top->is_boundary());
575 input_iterator *temp = top->next;
576 check_end_diversion(top);
583 void input_stack::push(input_iterator *in)
587 if (++level > limit && limit > 0)
588 fatal("input stack limit exceeded (probable infinite loop)");
591 if (top->is_diversion) {
593 in->diversion_state = diversion_state;
594 diversion_state = curenv->construct_state(0);
595 #if defined(DEBUGGING)
597 curenv->dump_troff_state();
602 #if defined(DEBUGGING)
604 if (top->is_diversion) {
606 "in diversion level = %d\n", input_stack::get_div_level());
612 statem *get_diversion_state()
614 return input_stack::get_diversion_state();
617 statem *input_stack::get_diversion_state()
619 if (diversion_state == NULL)
622 return new statem(diversion_state);
625 input_iterator *input_stack::get_arg(int i)
628 for (p = top; p != 0; p = p->next)
630 return p->get_arg(i);
634 arg_list *input_stack::get_arg_list()
637 for (p = top; p != 0; p = p->next)
639 return p->get_arg_list();
643 symbol input_stack::get_macro_name()
646 for (p = top; p != 0; p = p->next)
648 return p->get_macro_name();
652 int input_stack::space_follows_arg(int i)
655 for (p = top; p != 0; p = p->next)
657 return p->space_follows_arg(i);
661 int input_stack::get_break_flag()
663 return top->get_break_flag();
666 void input_stack::shift(int n)
668 for (input_iterator *p = top; p; p = p->next)
675 int input_stack::nargs()
677 for (input_iterator *p =top; p != 0; p = p->next)
683 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
685 for (input_iterator *p = top; p; p = p->next)
686 if (p->get_location(allow_macro, filenamep, linenop))
691 void input_stack::backtrace()
695 // only backtrace down to (not including) the topmost file
696 for (input_iterator *p = top;
697 p && !p->get_location(0, &f, &n);
702 void input_stack::backtrace_all()
704 for (input_iterator *p = top; p; p = p->next)
708 int input_stack::set_location(const char *filename, int lineno)
710 for (input_iterator *p = top; p; p = p->next)
711 if (p->set_location(filename, lineno))
716 void input_stack::next_file(FILE *fp, const char *s)
719 for (pp = ⊤ *pp != &nil_iterator; pp = &(*pp)->next)
720 if ((*pp)->next_file(fp, s))
722 if (++level > limit && limit > 0)
723 fatal("input stack limit exceeded");
724 *pp = new file_iterator(fp, s);
725 (*pp)->next = &nil_iterator;
728 void input_stack::end_file()
730 for (input_iterator **pp = ⊤ *pp != &nil_iterator; pp = &(*pp)->next)
731 if ((*pp)->is_file()) {
732 input_iterator *tem = *pp;
733 check_end_diversion(tem);
741 void input_stack::clear()
744 while (top != &nil_iterator) {
745 if (top->is_boundary())
747 input_iterator *tem = top;
748 check_end_diversion(tem);
753 // Keep while_request happy.
754 for (; nboundaries > 0; --nboundaries)
755 add_return_boundary();
758 void input_stack::pop_macro()
763 if (top->next == &nil_iterator)
765 if (top->is_boundary())
767 is_macro = top->is_macro();
768 input_iterator *tem = top;
769 check_end_diversion(tem);
774 // Keep while_request happy.
775 for (; nboundaries > 0; --nboundaries)
776 add_return_boundary();
779 inline void input_stack::save_compatible_flag(int f)
781 top->save_compatible_flag(f);
784 inline int input_stack::get_compatible_flag()
786 return top->get_compatible_flag();
789 void backtrace_request()
791 input_stack::backtrace_all();
798 symbol nm = get_long_name();
799 while (!tok.newline() && !tok.eof())
802 input_stack::end_file();
805 FILE *fp = include_search_path.open_file_cautious(nm.contents());
807 error("can't open `%1': %2", nm.contents(), strerror(errno));
809 input_stack::next_file(fp, nm.contents());
817 if (!has_arg() || !get_integer(&n))
819 input_stack::shift(n);
823 static char get_char_for_escape_name(int allow_space = 0)
825 int c = get_copy(0, 0, 1);
828 copy_mode_error("end of input in escape name");
831 if (!invalid_input_char(c))
836 input_stack::push(make_temp_iterator("\n"));
839 if (c == ' ' && allow_space)
845 copy_mode_error("%1 is not allowed in an escape name",
846 input_char_description(c));
852 static symbol read_two_char_escape_name()
855 buf[0] = get_char_for_escape_name();
856 if (buf[0] != '\0') {
857 buf[1] = get_char_for_escape_name();
866 static symbol read_long_escape_name(read_mode mode)
868 int start_level = input_stack::get_level();
869 char abuf[ABUF_SIZE];
871 int buf_size = ABUF_SIZE;
876 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
883 if (mode == WITH_ARGS && c == ' ')
885 if (i + 2 > buf_size) {
887 buf = new char[ABUF_SIZE*2];
888 memcpy(buf, abuf, buf_size);
889 buf_size = ABUF_SIZE*2;
893 buf = new char[buf_size*2];
894 memcpy(buf, old_buf, buf_size);
899 if (c == ']' && input_stack::get_level() == start_level)
908 if (mode != ALLOW_EMPTY)
909 copy_mode_error("empty escape name");
921 static symbol read_escape_name(read_mode mode)
923 char c = get_char_for_escape_name();
927 return read_two_char_escape_name();
928 if (c == '[' && !compatible_flag)
929 return read_long_escape_name(mode);
936 static symbol read_increment_and_escape_name(int *incp)
938 char c = get_char_for_escape_name();
945 return read_two_char_escape_name();
948 return read_escape_name();
951 return read_escape_name();
953 if (!compatible_flag) {
955 return read_long_escape_name();
966 static int get_copy(node **nd, int defining, int handle_escape_E)
969 int c = input_stack::get(nd);
970 if (c == PUSH_GROFF_MODE) {
971 input_stack::save_compatible_flag(compatible_flag);
975 if (c == PUSH_COMP_MODE) {
976 input_stack::save_compatible_flag(compatible_flag);
980 if (c == POP_GROFFCOMP_MODE) {
981 compatible_flag = input_stack::get_compatible_flag();
984 if (c == BEGIN_QUOTE) {
985 input_stack::increase_level();
988 if (c == END_QUOTE) {
989 input_stack::decrease_level();
992 if (c == DOUBLE_QUOTE)
994 if (c == ESCAPE_E && handle_escape_E)
996 if (c == ESCAPE_NEWLINE) {
1000 c = input_stack::get(nd);
1001 } while (c == ESCAPE_NEWLINE);
1003 if (c != escape_char || escape_char <= 0)
1006 c = input_stack::peek();
1011 (void)input_stack::get(0);
1012 while ((c = input_stack::get(0)) != '\n' && c != EOF)
1015 case '#': // Like \" but newline is ignored.
1016 (void)input_stack::get(0);
1017 while ((c = input_stack::get(0)) != '\n')
1023 (void)input_stack::get(0);
1024 symbol s = read_escape_name();
1025 if (!(s.is_null() || s.is_empty()))
1031 (void)input_stack::get(0);
1032 symbol s = read_escape_name(WITH_ARGS);
1033 if (!(s.is_null() || s.is_empty())) {
1034 if (have_string_arg) {
1035 have_string_arg = 0;
1036 interpolate_string_with_args(s);
1039 interpolate_string(s);
1044 (void)input_stack::get(0);
1047 (void)input_stack::get(0);
1050 (void)input_stack::get(0);
1051 if (handle_escape_E)
1056 (void)input_stack::get(0);
1058 symbol s = read_increment_and_escape_name(&inc);
1059 if (!(s.is_null() || s.is_empty()))
1060 interpolate_number_reg(s, inc);
1065 (void)input_stack::get(0);
1066 symbol s = read_escape_name();
1067 if (!(s.is_null() || s.is_empty()))
1068 interpolate_number_format(s);
1072 (void)input_stack::get(0);
1076 (void)input_stack::get(0);
1077 symbol s = read_escape_name();
1078 if (!(s.is_null() || s.is_empty()))
1079 interpolate_environment_variable(s);
1083 (void)input_stack::get(0);
1085 return ESCAPE_NEWLINE;
1088 (void)input_stack::get(0);
1089 return ESCAPE_SPACE;
1091 (void)input_stack::get(0);
1092 return ESCAPE_TILDE;
1094 (void)input_stack::get(0);
1095 return ESCAPE_COLON;
1097 (void)input_stack::get(0);
1100 (void)input_stack::get(0);
1101 return ESCAPE_CIRCUMFLEX;
1103 (void)input_stack::get(0);
1104 return ESCAPE_LEFT_BRACE;
1106 (void)input_stack::get(0);
1107 return ESCAPE_RIGHT_BRACE;
1109 (void)input_stack::get(0);
1110 return ESCAPE_LEFT_QUOTE;
1112 (void)input_stack::get(0);
1113 return ESCAPE_RIGHT_QUOTE;
1115 (void)input_stack::get(0);
1116 return ESCAPE_HYPHEN;
1118 (void)input_stack::get(0);
1119 return ESCAPE_UNDERSCORE;
1121 (void)input_stack::get(0);
1124 (void)input_stack::get(0);
1127 (void)input_stack::get(0);
1128 return ESCAPE_QUESTION;
1130 (void)input_stack::get(0);
1131 return ESCAPE_AMPERSAND;
1133 (void)input_stack::get(0);
1134 return ESCAPE_RIGHT_PARENTHESIS;
1136 (void)input_stack::get(0);
1139 (void)input_stack::get(0);
1140 return ESCAPE_PERCENT;
1142 if (c == escape_char) {
1143 (void)input_stack::get(0);
1152 class non_interpreted_char_node : public node {
1155 non_interpreted_char_node(unsigned char);
1157 int interpret(macro *);
1164 int non_interpreted_char_node::same(node *nd)
1166 return c == ((non_interpreted_char_node *)nd)->c;
1169 const char *non_interpreted_char_node::type()
1171 return "non_interpreted_char_node";
1174 int non_interpreted_char_node::force_tprint()
1179 int non_interpreted_char_node::is_tag()
1184 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1189 node *non_interpreted_char_node::copy()
1191 return new non_interpreted_char_node(c);
1194 int non_interpreted_char_node::interpret(macro *mac)
1200 static void do_width();
1201 static node *do_non_interpreted();
1202 static node *do_special();
1203 static node *do_suppress(symbol nm);
1204 static void do_register();
1206 dictionary color_dictionary(501);
1208 static color *lookup_color(symbol nm)
1210 assert(!nm.is_null());
1211 if (nm == default_symbol)
1212 return &default_color;
1213 color *c = (color *)color_dictionary.lookup(nm);
1215 warning(WARN_COLOR, "color `%1' not defined", nm.contents());
1219 void do_glyph_color(symbol nm)
1224 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1226 color *tem = lookup_color(nm);
1228 curenv->set_glyph_color(tem);
1230 (void)color_dictionary.lookup(nm, new color(nm));
1234 void do_fill_color(symbol nm)
1239 curenv->set_fill_color(curenv->get_prev_fill_color());
1241 color *tem = lookup_color(nm);
1243 curenv->set_fill_color(tem);
1245 (void)color_dictionary.lookup(nm, new color(nm));
1249 static unsigned int get_color_element(const char *scheme, const char *col)
1252 if (!get_number(&val, 'f')) {
1253 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1258 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1261 if (val > color::MAX_COLOR_VAL+1) {
1262 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1263 // we change 0x10000 to 0xffff
1264 return color::MAX_COLOR_VAL;
1266 return (unsigned int)val;
1269 static color *read_rgb(char end = 0)
1271 symbol component = do_get_long_name(0, end);
1272 if (component.is_null()) {
1273 warning(WARN_COLOR, "missing rgb color values");
1276 const char *s = component.contents();
1277 color *col = new color;
1279 if (!col->read_rgb(s)) {
1280 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1287 input_stack::push(make_temp_iterator(" "));
1288 input_stack::push(make_temp_iterator(s));
1290 unsigned int r = get_color_element("rgb color", "red component");
1291 unsigned int g = get_color_element("rgb color", "green component");
1292 unsigned int b = get_color_element("rgb color", "blue component");
1293 col->set_rgb(r, g, b);
1298 static color *read_cmy(char end = 0)
1300 symbol component = do_get_long_name(0, end);
1301 if (component.is_null()) {
1302 warning(WARN_COLOR, "missing cmy color values");
1305 const char *s = component.contents();
1306 color *col = new color;
1308 if (!col->read_cmy(s)) {
1309 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1316 input_stack::push(make_temp_iterator(" "));
1317 input_stack::push(make_temp_iterator(s));
1319 unsigned int c = get_color_element("cmy color", "cyan component");
1320 unsigned int m = get_color_element("cmy color", "magenta component");
1321 unsigned int y = get_color_element("cmy color", "yellow component");
1322 col->set_cmy(c, m, y);
1327 static color *read_cmyk(char end = 0)
1329 symbol component = do_get_long_name(0, end);
1330 if (component.is_null()) {
1331 warning(WARN_COLOR, "missing cmyk color values");
1334 const char *s = component.contents();
1335 color *col = new color;
1337 if (!col->read_cmyk(s)) {
1338 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1345 input_stack::push(make_temp_iterator(" "));
1346 input_stack::push(make_temp_iterator(s));
1348 unsigned int c = get_color_element("cmyk color", "cyan component");
1349 unsigned int m = get_color_element("cmyk color", "magenta component");
1350 unsigned int y = get_color_element("cmyk color", "yellow component");
1351 unsigned int k = get_color_element("cmyk color", "black component");
1352 col->set_cmyk(c, m, y, k);
1357 static color *read_gray(char end = 0)
1359 symbol component = do_get_long_name(0, end);
1360 if (component.is_null()) {
1361 warning(WARN_COLOR, "missing gray values");
1364 const char *s = component.contents();
1365 color *col = new color;
1367 if (!col->read_gray(s)) {
1368 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1375 input_stack::push(make_temp_iterator("\n"));
1376 input_stack::push(make_temp_iterator(s));
1378 unsigned int g = get_color_element("gray", "gray value");
1384 static void activate_color()
1387 if (has_arg() && get_integer(&n))
1388 color_flag = n != 0;
1394 static void define_color()
1396 symbol color_name = get_long_name(1);
1397 if (color_name.is_null()) {
1401 if (color_name == default_symbol) {
1402 warning(WARN_COLOR, "default color can't be redefined");
1406 symbol style = get_long_name(1);
1407 if (style.is_null()) {
1412 if (strcmp(style.contents(), "rgb") == 0)
1414 else if (strcmp(style.contents(), "cmyk") == 0)
1416 else if (strcmp(style.contents(), "gray") == 0)
1418 else if (strcmp(style.contents(), "grey") == 0)
1420 else if (strcmp(style.contents(), "cmy") == 0)
1424 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1430 col->nm = color_name;
1431 (void)color_dictionary.lookup(color_name, col);
1436 static node *do_overstrike()
1439 overstrike_node *on = new overstrike_node;
1440 int start_level = input_stack::get_level();
1444 if (tok.newline() || tok.eof()) {
1445 warning(WARN_DELIM, "missing closing delimiter");
1446 input_stack::push(make_temp_iterator("\n"));
1450 && (compatible_flag || input_stack::get_level() == start_level))
1452 charinfo *ci = tok.get_char(1);
1454 node *n = curenv->make_char_node(ci);
1462 static node *do_bracket()
1465 bracket_node *bn = new bracket_node;
1467 int start_level = input_stack::get_level();
1471 warning(WARN_DELIM, "missing closing delimiter");
1474 if (tok.newline()) {
1475 warning(WARN_DELIM, "missing closing delimiter");
1476 input_stack::push(make_temp_iterator("\n"));
1480 && (compatible_flag || input_stack::get_level() == start_level))
1482 charinfo *ci = tok.get_char(1);
1484 node *n = curenv->make_char_node(ci);
1492 static int do_name_test()
1496 int start_level = input_stack::get_level();
1501 if (tok.newline() || tok.eof()) {
1502 warning(WARN_DELIM, "missing closing delimiter");
1503 input_stack::push(make_temp_iterator("\n"));
1507 && (compatible_flag || input_stack::get_level() == start_level))
1513 return some_char && !bad_char;
1516 static int do_expr_test()
1520 int start_level = input_stack::get_level();
1521 if (!start.delimiter(1))
1524 // disable all warning and error messages temporarily
1525 int saved_warning_mask = warning_mask;
1526 int saved_inhibit_errors = inhibit_errors;
1530 int result = get_number_rigidly(&dummy, 'u');
1531 warning_mask = saved_warning_mask;
1532 inhibit_errors = saved_inhibit_errors;
1533 if (tok == start && input_stack::get_level() == start_level)
1535 // ignore everything up to the delimiter in case we aren't right there
1538 if (tok.newline() || tok.eof()) {
1539 warning(WARN_DELIM, "missing closing delimiter");
1540 input_stack::push(make_temp_iterator("\n"));
1543 if (tok == start && input_stack::get_level() == start_level)
1550 static node *do_zero_width()
1554 int start_level = input_stack::get_level();
1555 environment env(curenv);
1556 environment *oldenv = curenv;
1560 if (tok.newline() || tok.eof()) {
1561 error("missing closing delimiter");
1565 && (compatible_flag || input_stack::get_level() == start_level))
1570 node *rev = env.extract_output_line();
1578 return new zero_width_node(n);
1583 // It's undesirable for \Z to change environments, because then
1584 // \n(.w won't work as expected.
1586 static node *do_zero_width()
1588 node *rev = new dummy_node;
1591 int start_level = input_stack::get_level();
1594 if (tok.newline() || tok.eof()) {
1595 warning(WARN_DELIM, "missing closing delimiter");
1596 input_stack::push(make_temp_iterator("\n"));
1600 && (compatible_flag || input_stack::get_level() == start_level))
1602 if (!tok.add_to_node_list(&rev))
1603 error("invalid token in argument to \\Z");
1612 return new zero_width_node(n);
1617 token_node *node::get_token_node()
1622 class token_node : public node {
1625 token_node(const token &t);
1627 token_node *get_token_node();
1634 token_node::token_node(const token &t) : tk(t)
1638 node *token_node::copy()
1640 return new token_node(tk);
1643 token_node *token_node::get_token_node()
1648 int token_node::same(node *nd)
1650 return tk == ((token_node *)nd)->tk;
1653 const char *token_node::type()
1655 return "token_node";
1658 int token_node::force_tprint()
1663 int token_node::is_tag()
1668 token::token() : nd(0), type(TOKEN_EMPTY)
1677 token::token(const token &t)
1678 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1680 // Use two statements to work around bug in SGI C++.
1682 nd = tem ? tem->copy() : 0;
1685 void token::operator=(const token &t)
1689 // Use two statements to work around bug in SGI C++.
1691 nd = tem ? tem->copy() : 0;
1708 return !tok.newline();
1711 void token::make_space()
1716 void token::make_newline()
1718 type = TOKEN_NEWLINE;
1730 int cc = input_stack::get(&n);
1731 if (cc != escape_char || escape_char == 0) {
1734 case PUSH_GROFF_MODE:
1735 input_stack::save_compatible_flag(compatible_flag);
1736 compatible_flag = 0;
1738 case PUSH_COMP_MODE:
1739 input_stack::save_compatible_flag(compatible_flag);
1740 compatible_flag = 1;
1742 case POP_GROFFCOMP_MODE:
1743 compatible_flag = input_stack::get_compatible_flag();
1746 input_stack::increase_level();
1749 input_stack::decrease_level();
1756 case TRANSPARENT_FILE_REQUEST:
1758 case COPY_FILE_REQUEST:
1760 case VJUSTIFY_REQUEST:
1762 type = TOKEN_REQUEST;
1766 type = TOKEN_BEGIN_TRAP;
1769 type = TOKEN_END_TRAP;
1771 case LAST_PAGE_EJECTOR:
1772 seen_last_page_ejector = 1;
1775 type = TOKEN_PAGE_EJECTOR;
1777 case ESCAPE_PERCENT:
1779 type = TOKEN_HYPHEN_INDICATOR;
1783 type = TOKEN_UNSTRETCHABLE_SPACE;
1787 type = TOKEN_STRETCHABLE_SPACE;
1791 type = TOKEN_ZERO_WIDTH_BREAK;
1795 type = TOKEN_ESCAPE;
1798 goto handle_escape_char;
1802 nd = new hmotion_node(curenv->get_narrow_space_width(),
1803 curenv->get_fill_color());
1805 case ESCAPE_CIRCUMFLEX:
1808 nd = new hmotion_node(curenv->get_half_narrow_space_width(),
1809 curenv->get_fill_color());
1811 case ESCAPE_NEWLINE:
1814 case ESCAPE_LEFT_BRACE:
1816 type = TOKEN_LEFT_BRACE;
1818 case ESCAPE_RIGHT_BRACE:
1820 type = TOKEN_RIGHT_BRACE;
1822 case ESCAPE_LEFT_QUOTE:
1824 type = TOKEN_SPECIAL;
1827 case ESCAPE_RIGHT_QUOTE:
1829 type = TOKEN_SPECIAL;
1834 type = TOKEN_SPECIAL;
1837 case ESCAPE_UNDERSCORE:
1839 type = TOKEN_SPECIAL;
1844 type = TOKEN_INTERRUPT;
1848 type = TOKEN_TRANSPARENT;
1850 case ESCAPE_QUESTION:
1852 nd = do_non_interpreted();
1858 case ESCAPE_AMPERSAND:
1862 case ESCAPE_RIGHT_PARENTHESIS:
1863 ESCAPE_RIGHT_PARENTHESIS:
1864 type = TOKEN_TRANSPARENT_DUMMY;
1867 type = TOKEN_BACKSPACE;
1876 type = TOKEN_NEWLINE;
1879 type = TOKEN_LEADER;
1884 token_node *tn = n->get_token_node();
1903 cc = input_stack::get(&n);
1906 nm = read_two_char_escape_name();
1907 type = TOKEN_SPECIAL;
1911 error("end of input after escape character");
1914 goto ESCAPE_LEFT_QUOTE;
1916 goto ESCAPE_RIGHT_QUOTE;
1920 goto ESCAPE_UNDERSCORE;
1922 goto ESCAPE_PERCENT;
1926 nd = new hmotion_node(curenv->get_digit_width(),
1927 curenv->get_fill_color());
1933 goto ESCAPE_CIRCUMFLEX;
1935 type = TOKEN_ITALIC_CORRECTION;
1939 nd = new left_italic_corrected_node;
1942 goto ESCAPE_AMPERSAND;
1944 goto ESCAPE_RIGHT_PARENTHESIS;
1948 goto ESCAPE_QUESTION;
1954 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1957 type = TOKEN_NEWLINE;
1961 case '#': // Like \" but newline is ignored.
1962 while ((cc = input_stack::get(0)) != '\n')
1970 symbol s = read_escape_name();
1971 if (!(s.is_null() || s.is_empty()))
1977 symbol s = read_escape_name(WITH_ARGS);
1978 if (!(s.is_null() || s.is_empty())) {
1979 if (have_string_arg) {
1980 have_string_arg = 0;
1981 interpolate_string_with_args(s);
1984 interpolate_string(s);
1989 nd = new non_interpreted_char_node('\001');
1993 c = '0' + do_name_test();
2001 c = '0' + do_expr_test();
2007 nm = get_delim_name();
2010 type = TOKEN_SPECIAL;
2014 nd = new vmotion_node(curenv->get_size() / 2,
2015 curenv->get_fill_color());
2018 nd = read_draw_node();
2026 goto handle_escape_char;
2029 symbol s = read_escape_name(ALLOW_EMPTY);
2033 for (p = s.contents(); *p != '\0'; p++)
2036 if (*p || s.is_empty())
2037 curenv->set_font(s);
2039 curenv->set_font(atoi(s.contents()));
2040 if (!compatible_flag)
2046 symbol s = read_escape_name(ALLOW_EMPTY);
2049 curenv->set_family(s);
2055 symbol s = read_escape_name();
2056 if (!(s.is_null() || s.is_empty()))
2057 interpolate_number_format(s);
2061 if (!get_delim_number(&x, 'm'))
2064 nd = new hmotion_node(x, curenv->get_fill_color());
2067 // don't take height increments relative to previous height if
2068 // in compatibility mode
2069 if (!compatible_flag && curenv->get_char_height()) {
2070 if (get_delim_number(&x, 'z', curenv->get_char_height()))
2071 curenv->set_char_height(x);
2074 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
2075 curenv->set_char_height(x);
2077 if (!compatible_flag)
2081 nm = read_escape_name();
2082 if (nm.is_null() || nm.is_empty())
2084 type = TOKEN_MARK_INPUT;
2090 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
2093 s = get_charinfo(cc == 'l' ? "ru" : "br");
2095 node *char_node = curenv->make_char_node(s);
2097 nd = new hline_node(x, char_node);
2099 nd = new vline_node(x, char_node);
2103 do_glyph_color(read_escape_name(ALLOW_EMPTY));
2104 if (!compatible_flag)
2108 do_fill_color(read_escape_name(ALLOW_EMPTY));
2109 if (!compatible_flag)
2115 symbol s = read_increment_and_escape_name(&inc);
2116 if (!(s.is_null() || s.is_empty()))
2117 interpolate_number_reg(s, inc);
2121 if (!get_delim_number(&val, 0))
2124 warning(WARN_CHAR, "invalid numbered character %1", val);
2127 type = TOKEN_NUMBERED_CHAR;
2130 nd = do_overstrike();
2134 nd = do_suppress(read_escape_name());
2140 type = TOKEN_SPREAD;
2144 nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
2148 if (!compatible_flag)
2153 curenv->set_size(x);
2154 if (!compatible_flag)
2158 if (get_delim_number(&x, 0))
2159 curenv->set_char_slant(x);
2160 if (!compatible_flag)
2165 nd = new non_interpreted_char_node('\t');
2169 nd = new vmotion_node(-curenv->get_size() / 2,
2170 curenv->get_fill_color());
2173 if (!get_delim_number(&x, 'v'))
2176 nd = new vmotion_node(x, curenv->get_fill_color());
2180 symbol s = read_escape_name();
2181 if (!(s.is_null() || s.is_empty()))
2182 interpolate_environment_variable(s);
2189 if (!get_delim_number(&x, 'v'))
2192 nd = new extra_size_node(x);
2202 symbol s = read_escape_name();
2203 if (s.is_null() || s.is_empty())
2205 request_or_macro *p = lookup_request(s);
2206 macro *m = p->to_macro();
2208 error("can't transparently throughput a request");
2211 nd = new special_node(*m);
2218 if (type == TOKEN_NODE)
2219 nd = new zero_width_node(nd);
2221 charinfo *ci = get_char(1);
2224 node *gn = curenv->make_char_node(ci);
2227 nd = new zero_width_node(gn);
2233 nd = do_zero_width();
2239 goto ESCAPE_LEFT_BRACE;
2241 goto ESCAPE_RIGHT_BRACE;
2245 if (!compatible_flag) {
2246 symbol s = read_long_escape_name(WITH_ARGS);
2247 if (s.is_null() || s.is_empty())
2249 if (have_string_arg) {
2250 have_string_arg = 0;
2251 nm = composite_glyph_name(s);
2254 const char *gn = check_unicode_name(s.contents());
2256 const char *gn_decomposed = decompose_unicode(gn);
2258 gn = &gn_decomposed[1];
2259 const char *groff_gn = unicode_to_glyph_name(gn);
2261 nm = symbol(groff_gn);
2263 char *buf = new char[strlen(gn) + 1 + 1];
2271 nm = symbol(s.contents());
2273 type = TOKEN_SPECIAL;
2276 goto handle_normal_char;
2278 if (cc != escape_char && cc != '.')
2279 warning(WARN_ESCAPE, "escape character ignored before %1",
2280 input_char_description(cc));
2281 goto handle_normal_char;
2287 int token::operator==(const token &t)
2296 case TOKEN_NUMBERED_CHAR:
2297 return val == t.val;
2303 int token::operator!=(const token &t)
2305 return !(*this == t);
2308 // is token a suitable delimiter (like ')?
2310 int token::delimiter(int err)
2339 error("cannot use character `%1' as a starting delimiter", char(c));
2346 case TOKEN_STRETCHABLE_SPACE:
2347 case TOKEN_UNSTRETCHABLE_SPACE:
2351 error("cannot use %1 as a starting delimiter", description());
2358 const char *token::description()
2362 case TOKEN_BACKSPACE:
2363 return "a backspace character";
2374 case TOKEN_HYPHEN_INDICATOR:
2376 case TOKEN_INTERRUPT:
2378 case TOKEN_ITALIC_CORRECTION:
2381 return "a leader character";
2382 case TOKEN_LEFT_BRACE:
2384 case TOKEN_MARK_INPUT:
2390 case TOKEN_NUMBERED_CHAR:
2392 case TOKEN_RIGHT_BRACE:
2397 return "a special character";
2400 case TOKEN_STRETCHABLE_SPACE:
2402 case TOKEN_UNSTRETCHABLE_SPACE:
2405 return "a tab character";
2406 case TOKEN_TRANSPARENT:
2408 case TOKEN_TRANSPARENT_DUMMY:
2410 case TOKEN_ZERO_WIDTH_BREAK:
2413 return "end of input";
2417 return "a magic token";
2422 while (!tok.newline())
2433 if (has_arg() && get_integer(&n))
2434 compatible_flag = n != 0;
2436 compatible_flag = 1;
2440 static void empty_name_warning(int required)
2442 if (tok.newline() || tok.eof()) {
2444 warning(WARN_MISSING, "missing name");
2446 else if (tok.right_brace() || tok.tab()) {
2447 const char *start = tok.description();
2450 } while (tok.space() || tok.right_brace() || tok.tab());
2451 if (!tok.newline() && !tok.eof())
2452 error("%1 is not allowed before an argument", start);
2454 warning(WARN_MISSING, "missing name");
2457 error("name expected (got %1)", tok.description());
2459 error("name expected (got %1): treated as missing", tok.description());
2462 static void non_empty_name_warning()
2464 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2465 && !tok.right_brace()
2466 // We don't want to give a warning for .el\{
2467 && !tok.left_brace())
2468 error("%1 is not allowed in a name", tok.description());
2471 symbol get_name(int required)
2473 if (compatible_flag) {
2476 if ((buf[0] = tok.ch()) != 0) {
2478 if ((buf[1] = tok.ch()) != 0) {
2483 non_empty_name_warning();
2487 empty_name_warning(required);
2492 return get_long_name(required);
2495 symbol get_long_name(int required)
2497 return do_get_long_name(required, 0);
2500 static symbol do_get_long_name(int required, char end)
2504 char abuf[ABUF_SIZE];
2506 int buf_size = ABUF_SIZE;
2509 // If end != 0 we normally have to append a null byte
2510 if (i + 2 > buf_size) {
2512 buf = new char[ABUF_SIZE*2];
2513 memcpy(buf, abuf, buf_size);
2514 buf_size = ABUF_SIZE*2;
2517 char *old_buf = buf;
2518 buf = new char[buf_size*2];
2519 memcpy(buf, old_buf, buf_size);
2524 if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
2530 empty_name_warning(required);
2533 if (end && buf[i] == end)
2536 non_empty_name_warning();
2549 topdiv->set_last_page();
2550 if (!end_macro_name.is_null()) {
2551 spring_trap(end_macro_name);
2553 process_input_stack();
2555 curenv->final_break();
2557 process_input_stack();
2559 if (topdiv->get_page_length() > 0) {
2561 topdiv->set_ejecting();
2562 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2563 input_stack::push(make_temp_iterator((char *)buf));
2564 topdiv->space(topdiv->get_page_length(), 1);
2566 process_input_stack();
2567 seen_last_page_ejector = 1; // should be set already
2568 topdiv->set_ejecting();
2569 push_page_ejector();
2570 topdiv->space(topdiv->get_page_length(), 1);
2572 process_input_stack();
2574 // This will only happen if a trap-invoked macro starts a diversion,
2575 // or if vertical position traps have been disabled.
2576 cleanup_and_exit(0);
2579 // This implements .ex. The input stack must be cleared before calling
2584 input_stack::clear();
2591 void return_macro_request()
2593 if (has_arg() && tok.ch())
2594 input_stack::pop_macro();
2595 input_stack::pop_macro();
2601 end_macro_name = get_name();
2605 void blank_line_macro()
2607 blank_line_macro_name = get_name();
2611 static void trapping_blank_line()
2613 if (!blank_line_macro_name.is_null())
2614 spring_trap(blank_line_macro_name);
2621 int old_compatible_flag = compatible_flag;
2622 compatible_flag = 0;
2623 symbol nm = get_name();
2627 interpolate_macro(nm, 1);
2628 compatible_flag = old_compatible_flag;
2629 request_or_macro *p = lookup_request(nm);
2630 macro *m = p->to_macro();
2635 inline int possibly_handle_first_page_transition()
2637 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2638 handle_first_page_transition();
2645 static int transparent_translate(int cc)
2647 if (!invalid_input_char(cc)) {
2648 charinfo *ci = charset_table[cc];
2649 switch (ci->get_special_translation(1)) {
2650 case charinfo::TRANSLATE_SPACE:
2652 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2653 return ESCAPE_TILDE;
2654 case charinfo::TRANSLATE_DUMMY:
2655 return ESCAPE_AMPERSAND;
2656 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2657 return ESCAPE_PERCENT;
2659 // This is really ugly.
2660 ci = ci->get_translation(1);
2662 int c = ci->get_ascii_code();
2665 error("can't translate %1 to special character `%2'"
2666 " in transparent throughput",
2667 input_char_description(cc),
2675 struct int_stack_element {
2677 int_stack_element *next;
2687 int_stack::int_stack()
2692 int_stack::~int_stack()
2695 int_stack_element *temp = top;
2701 int int_stack::is_empty()
2706 void int_stack::push(int n)
2708 int_stack_element *p = new int_stack_element;
2714 int int_stack::pop()
2717 int_stack_element *p = top;
2724 int node::reread(int *)
2729 int global_diverted_space = 0;
2731 int diverted_space_node::reread(int *bolp)
2733 global_diverted_space = 1;
2734 if (curenv->get_fill())
2735 trapping_blank_line();
2738 global_diverted_space = 0;
2743 int diverted_copy_file_node::reread(int *bolp)
2745 curdiv->copy_file(filename.contents());
2750 int word_space_node::reread(int *)
2753 for (width_list *w = orig_width; w; w = w->next)
2754 curenv->space(w->width, w->sentence_width);
2761 int unbreakable_space_node::reread(int *)
2766 int hmotion_node::reread(int *)
2768 if (unformat && was_tab) {
2769 curenv->handle_tab(0);
2776 void process_input_stack()
2778 int_stack trap_bol_stack;
2781 int suppress_next = 0;
2783 case token::TOKEN_CHAR:
2785 unsigned char ch = tok.c;
2786 if (bol && !have_input
2787 && (ch == curenv->control_char
2788 || ch == curenv->no_break_control_char)) {
2789 break_flag = ch == curenv->control_char;
2790 // skip tabs as well as spaces here
2793 } while (tok.white_space());
2794 symbol nm = get_name();
2795 #if defined(DEBUGGING)
2797 if (! nm.is_null()) {
2798 if (strcmp(nm.contents(), "test") == 0) {
2799 fprintf(stderr, "found it!\n");
2802 fprintf(stderr, "interpreting [%s]", nm.contents());
2803 if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
2804 fprintf(stderr, " currently in diversion: %s",
2805 curdiv->get_diversion_name());
2806 fprintf(stderr, "\n");
2814 interpolate_macro(nm);
2815 #if defined(DEBUGGING)
2817 fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
2818 curenv->dump_troff_state();
2825 if (possibly_handle_first_page_transition())
2829 #if defined(DEBUGGING)
2831 fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
2834 curenv->add_char(charset_table[ch]);
2836 if (tok.type != token::TOKEN_CHAR)
2846 case token::TOKEN_TRANSPARENT:
2849 if (possibly_handle_first_page_transition())
2858 curdiv->transparent_output(transparent_translate(cc));
2860 curdiv->transparent_output(n);
2861 } while (cc != '\n' && cc != EOF);
2863 curdiv->transparent_output('\n');
2868 case token::TOKEN_NEWLINE:
2870 if (bol && !old_have_input
2871 && !curenv->get_prev_line_interrupted())
2872 trapping_blank_line();
2879 case token::TOKEN_REQUEST:
2881 int request_code = tok.c;
2883 switch (request_code) {
2887 case COPY_FILE_REQUEST:
2890 case TRANSPARENT_FILE_REQUEST:
2894 case VJUSTIFY_REQUEST:
2905 case token::TOKEN_SPACE:
2907 if (possibly_handle_first_page_transition())
2909 else if (bol && !curenv->get_prev_line_interrupted()) {
2911 // save space_width now so that it isn't changed by \f or \s
2912 // which we wouldn't notice here
2913 hunits space_width = curenv->get_space_width();
2915 nspaces += tok.nspaces();
2917 } while (tok.space());
2919 trapping_blank_line();
2923 curenv->add_node(new hmotion_node(space_width * nspaces,
2924 curenv->get_fill_color()));
2934 case token::TOKEN_EOF:
2936 case token::TOKEN_NODE:
2938 if (possibly_handle_first_page_transition())
2940 else if (tok.nd->reread(&bol)) {
2945 curenv->add_node(tok.nd);
2948 curenv->possibly_break_line(1);
2952 case token::TOKEN_PAGE_EJECTOR:
2954 continue_page_eject();
2955 // I think we just want to preserve bol.
2959 case token::TOKEN_BEGIN_TRAP:
2961 trap_bol_stack.push(bol);
2966 case token::TOKEN_END_TRAP:
2968 if (trap_bol_stack.is_empty())
2969 error("spurious end trap token detected!");
2971 bol = trap_bol_stack.pop();
2974 /* I'm not totally happy about this. But I can't think of any other
2975 way to do it. Doing an output_pending_lines() whenever a
2976 TOKEN_END_TRAP is detected doesn't work: for example,
2989 a\%very\%very\%long\%word
2991 will print all but the first lines from the word immediately
2992 after the footer, rather than on the next page. */
2994 if (trap_bol_stack.is_empty())
2995 curenv->output_pending_lines();
3007 trap_sprung_flag = 0;
3011 #ifdef WIDOW_CONTROL
3013 void flush_pending_lines()
3015 while (!tok.newline() && !tok.eof())
3017 curenv->output_pending_lines();
3021 #endif /* WIDOW_CONTROL */
3023 request_or_macro::request_or_macro()
3027 macro *request_or_macro::to_macro()
3032 request::request(REQUEST_FUNCP pp) : p(pp)
3036 void request::invoke(symbol, int)
3042 enum { SIZE = 128 };
3043 unsigned char s[SIZE];
3048 char_block::char_block()
3057 void append(unsigned char);
3058 void set(unsigned char, int);
3059 unsigned char get(int);
3066 friend class macro_header;
3067 friend class string_iterator;
3070 char_list::char_list()
3071 : ptr(0), len(0), head(0), tail(0)
3075 char_list::~char_list()
3078 char_block *tem = head;
3084 int char_list::length()
3089 void char_list::append(unsigned char c)
3092 head = tail = new char_block;
3096 if (ptr >= tail->s + char_block::SIZE) {
3097 tail->next = new char_block;
3106 void char_list::set(unsigned char c, int offset)
3108 assert(len > offset);
3109 // optimization for access at the end
3110 int boundary = len - len % char_block::SIZE;
3111 if (offset >= boundary) {
3112 *(tail->s + offset - boundary) = c;
3115 char_block *tem = head;
3118 l += char_block::SIZE;
3120 *(tem->s + offset % char_block::SIZE) = c;
3127 unsigned char char_list::get(int offset)
3129 assert(len > offset);
3130 // optimization for access at the end
3131 int boundary = len - len % char_block::SIZE;
3132 if (offset >= boundary)
3133 return *(tail->s + offset - boundary);
3134 char_block *tem = head;
3137 l += char_block::SIZE;
3139 return *(tem->s + offset % char_block::SIZE);
3150 void append(node *);
3154 friend class macro_header;
3155 friend class string_iterator;
3158 void node_list::append(node *n)
3166 tail = tail->next = n;
3170 int node_list::length()
3173 for (node *n = head; n != 0; n = n->next)
3178 node_list::node_list()
3183 node *node_list::extract()
3190 node_list::~node_list()
3192 delete_node_list(head);
3195 class macro_header {
3200 macro_header() { count = 1; }
3201 macro_header *copy(int);
3206 if (p != 0 && --(p->count) <= 0)
3211 : is_a_diversion(0), is_a_string(1)
3213 if (!input_stack::get_location(1, &filename, &lineno)) {
3222 macro::macro(const macro &m)
3223 : filename(m.filename), lineno(m.lineno), len(m.len),
3224 empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion),
3225 is_a_string(m.is_a_string), p(m.p)
3231 macro::macro(int is_div)
3232 : is_a_diversion(is_div)
3234 if (!input_stack::get_location(1, &filename, &lineno)) {
3244 int macro::is_diversion()
3246 return is_a_diversion;
3249 int macro::is_string()
3254 void macro::clear_string_flag()
3259 macro ¯o::operator=(const macro &m)
3261 // don't assign object
3264 if (p != 0 && --(p->count) <= 0)
3267 filename = m.filename;
3270 empty_macro = m.empty_macro;
3271 is_a_diversion = m.is_a_diversion;
3272 is_a_string = m.is_a_string;
3276 void macro::append(unsigned char c)
3280 p = new macro_header;
3281 if (p->cl.length() != len) {
3282 macro_header *tem = p->copy(len);
3283 if (--(p->count) <= 0)
3289 if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
3293 void macro::set(unsigned char c, int offset)
3297 p->cl.set(c, offset);
3300 unsigned char macro::get(int offset)
3303 return p->cl.get(offset);
3311 void macro::append_str(const char *s)
3316 while (s[i] != (char)0) {
3323 void macro::append(node *n)
3327 p = new macro_header;
3328 if (p->cl.length() != len) {
3329 macro_header *tem = p->copy(len);
3330 if (--(p->count) <= 0)
3340 void macro::append_unsigned(unsigned int i)
3342 unsigned int j = i / 10;
3345 append(((unsigned char)(((int)'0') + i % 10)));
3348 void macro::append_int(int i)
3354 append_unsigned((unsigned int)i);
3357 void macro::print_size()
3359 errprint("%1", len);
3362 // make a copy of the first n bytes
3364 macro_header *macro_header::copy(int n)
3366 macro_header *p = new macro_header;
3367 char_block *bp = cl.head;
3368 unsigned char *ptr = bp->s;
3371 if (ptr >= bp->s + char_block::SIZE) {
3375 unsigned char c = *ptr++;
3378 p->nl.append(nd->copy());
3387 object_dictionary_iterator iter(request_dictionary);
3388 request_or_macro *rm;
3390 while (iter.get(&s, (object **)&rm)) {
3391 assert(!s.is_null());
3392 macro *m = rm->to_macro();
3394 errprint("%1\t", s.contents());
3403 class string_iterator : public input_iterator {
3405 const char *how_invoked;
3409 int count; // of characters remaining
3411 int saved_compatible_flag;
3412 int with_break; // inherited from the caller
3417 string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
3420 int get_location(int, const char **, int *);
3422 int get_break_flag() { return with_break; }
3423 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3424 int get_compatible_flag() { return saved_compatible_flag; }
3428 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3429 : input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
3434 bp = mac.p->cl.head;
3435 nd = mac.p->nl.head;
3443 with_break = input_stack::get_break_flag();
3446 string_iterator::string_iterator()
3455 with_break = input_stack::get_break_flag();
3458 int string_iterator::is_diversion()
3460 return mac.is_diversion();
3463 int string_iterator::fill(node **np)
3470 const unsigned char *p = eptr;
3471 if (p >= bp->s + char_block::SIZE) {
3479 (*np)->div_nest_level = input_stack::get_div_level();
3481 (*np)->div_nest_level = 0;
3488 const unsigned char *e = bp->s + char_block::SIZE;
3493 unsigned char c = *p;
3494 if (c == '\n' || c == ESCAPE_NEWLINE) {
3508 int string_iterator::peek()
3512 const unsigned char *p = eptr;
3513 if (p >= bp->s + char_block::SIZE) {
3519 int string_iterator::get_location(int allow_macro,
3520 const char **filep, int *linep)
3524 if (mac.filename == 0)
3526 *filep = mac.filename;
3527 *linep = mac.lineno + lineno - 1;
3531 void string_iterator::backtrace()
3534 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3537 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3539 errprint(": %1\n", how_invoked);
3546 class temp_iterator : public input_iterator {
3547 unsigned char *base;
3548 temp_iterator(const char *, int len);
3551 friend input_iterator *make_temp_iterator(const char *);
3557 temp_iterator::temp_iterator(const char *s, int len)
3559 base = new unsigned char[len];
3560 memcpy(base, s, len);
3565 temp_iterator::~temp_iterator()
3570 class small_temp_iterator : public input_iterator {
3572 small_temp_iterator(const char *, int);
3573 ~small_temp_iterator();
3574 enum { BLOCK = 16 };
3575 static small_temp_iterator *free_list;
3576 void *operator new(size_t);
3577 void operator delete(void *);
3579 unsigned char buf[SIZE];
3580 friend input_iterator *make_temp_iterator(const char *);
3583 small_temp_iterator *small_temp_iterator::free_list = 0;
3585 void *small_temp_iterator::operator new(size_t n)
3587 assert(n == sizeof(small_temp_iterator));
3590 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3591 for (int i = 0; i < BLOCK - 1; i++)
3592 free_list[i].next = free_list + i + 1;
3593 free_list[BLOCK-1].next = 0;
3595 small_temp_iterator *p = free_list;
3596 free_list = (small_temp_iterator *)(free_list->next);
3604 void small_temp_iterator::operator delete(void *p)
3607 ((small_temp_iterator *)p)->next = free_list;
3608 free_list = (small_temp_iterator *)p;
3612 small_temp_iterator::~small_temp_iterator()
3619 small_temp_iterator::small_temp_iterator(const char *s, int len)
3621 for (int i = 0; i < len; i++)
3627 input_iterator *make_temp_iterator(const char *s)
3630 return new small_temp_iterator(s, 0);
3633 if (n <= small_temp_iterator::SIZE)
3634 return new small_temp_iterator(s, n);
3636 return new temp_iterator(s, n);
3640 // this is used when macros with arguments are interpolated
3646 arg_list(const macro &, int);
3647 arg_list(const arg_list *);
3651 arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
3655 arg_list::arg_list(const arg_list *al)
3659 space_follows = al->space_follows;
3660 arg_list **a = &next;
3661 arg_list *p = al->next;
3663 *a = new arg_list(p->mac, p->space_follows);
3669 arg_list::~arg_list()
3673 class macro_iterator : public string_iterator {
3676 int with_break; // whether called as .foo or 'foo
3678 macro_iterator(symbol, macro &, const char * = "macro", int = 0);
3681 int has_args() { return 1; }
3682 input_iterator *get_arg(int);
3683 arg_list *get_arg_list();
3684 symbol get_macro_name();
3685 int space_follows_arg(int);
3686 int get_break_flag() { return with_break; }
3687 int nargs() { return argc; }
3688 void add_arg(const macro &, int);
3690 int is_macro() { return 1; }
3694 input_iterator *macro_iterator::get_arg(int i)
3697 return make_temp_iterator(nm.contents());
3698 if (i > 0 && i <= argc) {
3700 for (int j = 1; j < i; j++) {
3704 return new string_iterator(p->mac);
3710 arg_list *macro_iterator::get_arg_list()
3715 symbol macro_iterator::get_macro_name()
3720 int macro_iterator::space_follows_arg(int i)
3722 if (i > 0 && i <= argc) {
3724 for (int j = 1; j < i; j++) {
3728 return p->space_follows;
3734 void macro_iterator::add_arg(const macro &m, int s)
3737 for (p = &args; *p; p = &((*p)->next))
3739 *p = new arg_list(m, s);
3743 void macro_iterator::shift(int n)
3745 while (n > 0 && argc > 0) {
3746 arg_list *tem = args;
3754 // This gets used by eg .if '\?xxx\?''.
3756 int operator==(const macro &m1, const macro &m2)
3758 if (m1.len != m2.len)
3760 string_iterator iter1(m1);
3761 string_iterator iter2(m2);
3765 int c1 = iter1.get(&nd1);
3768 int c2 = iter2.get(&nd2);
3780 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3790 static void interpolate_macro(symbol nm, int no_next)
3792 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3795 const char *s = nm.contents();
3796 if (strlen(s) > 2) {
3797 request_or_macro *r;
3802 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3804 macro *m = r->to_macro();
3805 if (!m || !m->empty())
3806 warned = warning(WARN_SPACE,
3807 "macro `%1' not defined "
3808 "(possibly missing space after `%2')",
3809 nm.contents(), buf);
3813 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
3815 request_dictionary.define(nm, p);
3819 p->invoke(nm, no_next);
3826 static void decode_args(macro_iterator *mi)
3828 if (!tok.newline() && !tok.eof()) {
3830 int c = get_copy(&n);
3834 if (c == '\n' || c == EOF)
3837 int quote_input_level = 0;
3838 int done_tab_warning = 0;
3839 arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
3840 // we store discarded double quotes for \$^
3842 arg.append(DOUBLE_QUOTE);
3843 quote_input_level = input_stack::get_level();
3846 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3847 if (quote_input_level > 0 && c == '"'
3849 || input_stack::get_level() == quote_input_level)) {
3850 arg.append(DOUBLE_QUOTE);
3863 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3864 warning(WARN_TAB, "tab character in unquoted macro argument");
3865 done_tab_warning = 1;
3872 arg.append(POP_GROFFCOMP_MODE);
3873 mi->add_arg(arg, (c == ' '));
3878 static void decode_string_args(macro_iterator *mi)
3881 int c = get_copy(&n);
3885 if (c == '\n' || c == EOF) {
3886 error("missing `]'");
3892 int quote_input_level = 0;
3893 int done_tab_warning = 0;
3895 quote_input_level = input_stack::get_level();
3898 while (c != EOF && c != '\n'
3899 && !(c == ']' && quote_input_level == 0)
3900 && !(c == ' ' && quote_input_level == 0)) {
3901 if (quote_input_level > 0 && c == '"'
3902 && input_stack::get_level() == quote_input_level) {
3915 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3916 warning(WARN_TAB, "tab character in unquoted string argument");
3917 done_tab_warning = 1;
3924 mi->add_arg(arg, (c == ' '));
3928 void macro::invoke(symbol nm, int no_next)
3930 macro_iterator *mi = new macro_iterator(nm, *this);
3932 input_stack::push(mi);
3933 // we must delay tok.next() in case the function has been called by
3934 // do_request to assure proper handling of compatible_flag
3939 macro *macro::to_macro()
3946 return empty_macro == 1;
3949 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called,
3951 : string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
3954 arg_list *al = input_stack::get_arg_list();
3956 args = new arg_list(al);
3957 argc = input_stack::nargs();
3962 macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
3966 macro_iterator::~macro_iterator()
3969 arg_list *tem = args;
3975 dictionary composite_dictionary(17);
3977 void composite_request()
3979 symbol from = get_name(1);
3980 if (!from.is_null()) {
3981 const char *from_gn = glyph_name_to_unicode(from.contents());
3983 from_gn = check_unicode_name(from.contents());
3985 error("invalid composite glyph name `%1'", from.contents());
3990 const char *from_decomposed = decompose_unicode(from_gn);
3991 if (from_decomposed)
3992 from_gn = &from_decomposed[1];
3993 symbol to = get_name(1);
3995 composite_dictionary.remove(symbol(from_gn));
3997 const char *to_gn = glyph_name_to_unicode(to.contents());
3999 to_gn = check_unicode_name(to.contents());
4001 error("invalid composite glyph name `%1'", to.contents());
4006 const char *to_decomposed = decompose_unicode(to_gn);
4008 to_gn = &to_decomposed[1];
4009 if (strcmp(from_gn, to_gn) == 0)
4010 composite_dictionary.remove(symbol(from_gn));
4012 (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
4018 static symbol composite_glyph_name(symbol nm)
4020 macro_iterator *mi = new macro_iterator();
4021 decode_string_args(mi);
4022 input_stack::push(mi);
4023 const char *gn = glyph_name_to_unicode(nm.contents());
4025 gn = check_unicode_name(nm.contents());
4027 error("invalid base glyph `%1' in composite glyph name", nm.contents());
4028 return EMPTY_SYMBOL;
4031 const char *gn_decomposed = decompose_unicode(gn);
4032 string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
4034 int n = input_stack::nargs();
4035 for (int i = 1; i <= n; i++) {
4037 input_iterator *p = input_stack::get_arg(i);
4040 while ((c = p->get(0)) != EOF)
4041 if (c != DOUBLE_QUOTE)
4044 const char *u = glyph_name_to_unicode(gl.contents());
4046 u = check_unicode_name(gl.contents());
4048 error("invalid component `%1' in composite glyph name",
4050 return EMPTY_SYMBOL;
4053 const char *decomposed = decompose_unicode(u);
4056 void *mapped_composite = composite_dictionary.lookup(symbol(u));
4057 if (mapped_composite)
4058 u = (const char *)mapped_composite;
4062 const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
4064 return symbol(groff_gn);
4068 return symbol(gl.contents());
4071 int trap_sprung_flag = 0;
4072 int postpone_traps_flag = 0;
4073 symbol postponed_trap;
4075 void spring_trap(symbol nm)
4077 assert(!nm.is_null());
4078 trap_sprung_flag = 1;
4079 if (postpone_traps_flag) {
4080 postponed_trap = nm;
4083 static char buf[2] = { BEGIN_TRAP, 0 };
4084 static char buf2[2] = { END_TRAP, '\0' };
4085 input_stack::push(make_temp_iterator(buf2));
4086 request_or_macro *p = lookup_request(nm);
4087 macro *m = p->to_macro();
4089 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
4091 error("you can't invoke a request with a trap");
4092 input_stack::push(make_temp_iterator(buf));
4095 void postpone_traps()
4097 postpone_traps_flag = 1;
4100 int unpostpone_traps()
4102 postpone_traps_flag = 0;
4103 if (!postponed_trap.is_null()) {
4104 spring_trap(postponed_trap);
4105 postponed_trap = NULL_SYMBOL;
4114 macro_iterator *mi = new macro_iterator;
4115 int reading_from_terminal = isatty(fileno(stdin));
4117 if (!tok.newline() && !tok.eof()) {
4118 int c = get_copy(0);
4121 while (c != EOF && c != '\n' && c != ' ') {
4122 if (!invalid_input_char(c)) {
4123 if (reading_from_terminal)
4134 if (reading_from_terminal) {
4135 fputc(had_prompt ? ':' : '\a', stderr);
4138 input_stack::push(mi);
4142 while ((c = getchar()) != EOF) {
4143 if (invalid_input_char(c))
4144 warning(WARN_INPUT, "invalid input character code %1", int(c));
4157 if (reading_from_terminal)
4159 input_stack::push(new string_iterator(mac));
4163 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
4164 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
4165 enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
4167 void do_define_string(define_mode mode, comp_mode comp)
4170 node *n = 0; // pacify compiler
4181 else if (!tok.space()) {
4182 error("bad string definition");
4193 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
4194 macro *mm = rm ? rm->to_macro() : 0;
4195 if (mode == DEFINE_APPEND && mm)
4197 if (comp == COMP_DISABLE)
4198 mac.append(PUSH_GROFF_MODE);
4199 else if (comp == COMP_ENABLE)
4200 mac.append(PUSH_COMP_MODE);
4201 while (c != '\n' && c != EOF) {
4205 mac.append((unsigned char)c);
4208 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4209 mac.append(POP_GROFFCOMP_MODE);
4212 request_dictionary.define(nm, mm);
4218 void define_string()
4220 do_define_string(DEFINE_NORMAL,
4221 compatible_flag ? COMP_ENABLE: COMP_IGNORE);
4224 void define_nocomp_string()
4226 do_define_string(DEFINE_NORMAL, COMP_DISABLE);
4229 void append_string()
4231 do_define_string(DEFINE_APPEND,
4232 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4235 void append_nocomp_string()
4237 do_define_string(DEFINE_APPEND, COMP_DISABLE);
4240 void do_define_character(char_mode mode, const char *font_name)
4242 node *n = 0; // pacify compiler
4245 charinfo *ci = tok.get_char(1);
4251 string s(font_name);
4253 s += ci->nm.contents();
4255 ci = get_charinfo(symbol(s.contents()));
4262 else if (!tok.space()) {
4263 error("bad character definition");
4269 while (c == ' ' || c == '\t')
4273 macro *m = new macro;
4274 while (c != '\n' && c != EOF) {
4278 m->append((unsigned char)c);
4281 m = ci->setx_macro(m, mode);
4287 void define_character()
4289 do_define_character(CHAR_NORMAL);
4292 void define_fallback_character()
4294 do_define_character(CHAR_FALLBACK);
4297 void define_special_character()
4299 do_define_character(CHAR_SPECIAL);
4302 static void remove_character()
4305 while (!tok.newline() && !tok.eof()) {
4306 if (!tok.space() && !tok.tab()) {
4307 charinfo *ci = tok.get_char(1);
4310 macro *m = ci->set_macro(0);
4319 static void interpolate_string(symbol nm)
4321 request_or_macro *p = lookup_request(nm);
4322 macro *m = p->to_macro();
4324 error("you can only invoke a string or macro using \\*");
4326 if (m->is_string()) {
4327 string_iterator *si = new string_iterator(*m, "string", nm);
4328 input_stack::push(si);
4331 // if a macro is called as a string, \$0 doesn't get changed
4332 macro_iterator *mi = new macro_iterator(input_stack::get_macro_name(),
4334 input_stack::push(mi);
4339 static void interpolate_string_with_args(symbol s)
4341 request_or_macro *p = lookup_request(s);
4342 macro *m = p->to_macro();
4344 error("you can only invoke a string or macro using \\*");
4346 macro_iterator *mi = new macro_iterator(s, *m);
4347 decode_string_args(mi);
4348 input_stack::push(mi);
4352 static void interpolate_arg(symbol nm)
4354 const char *s = nm.contents();
4355 if (!s || *s == '\0')
4356 copy_mode_error("missing argument name");
4357 else if (s[1] == 0 && csdigit(s[0]))
4358 input_stack::push(input_stack::get_arg(s[0] - '0'));
4359 else if (s[0] == '*' && s[1] == '\0') {
4360 int limit = input_stack::nargs();
4362 for (int i = 1; i <= limit; i++) {
4363 input_iterator *p = input_stack::get_arg(i);
4365 while ((c = p->get(0)) != EOF)
4366 if (c != DOUBLE_QUOTE)
4373 input_stack::push(make_temp_iterator(args.contents()));
4376 else if (s[0] == '@' && s[1] == '\0') {
4377 int limit = input_stack::nargs();
4379 for (int i = 1; i <= limit; i++) {
4381 args += char(BEGIN_QUOTE);
4382 input_iterator *p = input_stack::get_arg(i);
4384 while ((c = p->get(0)) != EOF)
4385 if (c != DOUBLE_QUOTE)
4387 args += char(END_QUOTE);
4394 input_stack::push(make_temp_iterator(args.contents()));
4397 else if (s[0] == '^' && s[1] == '\0') {
4398 int limit = input_stack::nargs();
4400 int c = input_stack::peek();
4401 for (int i = 1; i <= limit; i++) {
4402 input_iterator *p = input_stack::get_arg(i);
4403 while ((c = p->get(0)) != EOF) {
4404 if (c == DOUBLE_QUOTE)
4408 if (input_stack::space_follows_arg(i))
4413 input_stack::push(make_temp_iterator(args.contents()));
4418 for (p = s; *p && csdigit(*p); p++)
4421 copy_mode_error("bad argument name `%1'", s);
4423 input_stack::push(input_stack::get_arg(atoi(s)));
4427 void handle_first_page_transition()
4430 topdiv->begin_page();
4433 // We push back a token by wrapping it up in a token_node, and
4434 // wrapping that up in a string_iterator.
4436 static void push_token(const token &t)
4439 m.append(new token_node(t));
4440 input_stack::push(new string_iterator(m));
4443 void push_page_ejector()
4445 static char buf[2] = { PAGE_EJECTOR, '\0' };
4446 input_stack::push(make_temp_iterator(buf));
4449 void handle_initial_request(unsigned char code)
4455 mac.append(new token_node(tok));
4456 input_stack::push(new string_iterator(mac));
4457 input_stack::push(make_temp_iterator(buf));
4458 topdiv->begin_page();
4462 void handle_initial_title()
4464 handle_initial_request(TITLE_REQUEST);
4467 // this should be local to define_macro, but cfront 1.2 doesn't support that
4468 static symbol dot_symbol(".");
4470 void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
4473 if (calling == CALLING_INDIRECT) {
4474 symbol temp1 = get_name(1);
4475 if (temp1.is_null()) {
4479 symbol temp2 = get_name();
4480 input_stack::push(make_temp_iterator("\n"));
4481 if (!temp2.is_null()) {
4482 interpolate_string(temp2);
4483 input_stack::push(make_temp_iterator(" "));
4485 interpolate_string(temp1);
4486 input_stack::push(make_temp_iterator(" "));
4489 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4496 term = get_name(); // the request that terminates the definition
4499 while (!tok.newline() && !tok.eof())
4501 const char *start_filename;
4503 int have_start_location = input_stack::get_location(0, &start_filename,
4506 // doing this here makes the line numbers come out right
4507 int c = get_copy(&n, 1);
4510 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4511 request_or_macro *rm =
4512 (request_or_macro *)request_dictionary.lookup(nm);
4514 mm = rm->to_macro();
4515 if (mm && mode == DEFINE_APPEND)
4519 if (comp == COMP_DISABLE)
4520 mac.append(PUSH_GROFF_MODE);
4521 else if (comp == COMP_ENABLE)
4522 mac.append(PUSH_COMP_MODE);
4525 mac.clear_string_flag();
4526 while (c == ESCAPE_NEWLINE) {
4527 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
4529 c = get_copy(&n, 1);
4531 if (bol && c == '.') {
4532 const char *s = term.contents();
4534 // see if it matches term
4537 while ((d = get_copy(&n)) == ' ' || d == '\t')
4539 if ((unsigned char)s[0] == d) {
4540 for (i = 1; s[i] != 0; i++) {
4542 if ((unsigned char)s[i] != d)
4548 && ((i == 2 && compatible_flag)
4549 || (d = get_copy(&n)) == ' '
4550 || d == '\n')) { // we found it
4555 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4558 request_dictionary.define(nm, mm);
4560 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4561 mac.append(POP_GROFFCOMP_MODE);
4564 if (term != dot_symbol) {
4566 interpolate_macro(term);
4572 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4574 for (int j = 0; j < i; j++)
4580 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4581 if (have_start_location)
4582 error_with_file_and_line(start_filename, start_lineno,
4583 "end of file while defining macro `%1'",
4586 error("end of file while defining macro `%1'", nm.contents());
4589 if (have_start_location)
4590 error_with_file_and_line(start_filename, start_lineno,
4591 "end of file while ignoring input lines");
4593 error("end of file while ignoring input lines");
4598 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4605 c = get_copy(&n, 1);
4611 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
4612 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4615 void define_nocomp_macro()
4617 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
4620 void define_indirect_macro()
4622 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
4623 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4626 void define_indirect_nocomp_macro()
4628 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
4633 do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
4634 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4637 void append_nocomp_macro()
4639 do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
4642 void append_indirect_macro()
4644 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
4645 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4648 void append_indirect_nocomp_macro()
4650 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
4656 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
4663 symbol s = get_name();
4666 request_dictionary.remove(s);
4673 symbol s1 = get_name(1);
4674 if (!s1.is_null()) {
4675 symbol s2 = get_name(1);
4677 request_dictionary.rename(s1, s2);
4684 symbol s1 = get_name(1);
4685 if (!s1.is_null()) {
4686 symbol s2 = get_name(1);
4687 if (!s2.is_null()) {
4688 if (!request_dictionary.alias(s1, s2))
4689 warning(WARN_MAC, "macro `%1' not defined", s2.contents());
4697 symbol s = get_name(1);
4699 request_or_macro *p = lookup_request(s);
4700 macro *m = p->to_macro();
4702 error("cannot chop request");
4703 else if (m->empty())
4704 error("cannot chop empty macro");
4706 int have_restore = 0;
4707 // we have to check for additional save/restore pairs which could be
4708 // there due to empty am1 requests.
4710 if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
4714 if (m->get(m->len - 1) != PUSH_GROFF_MODE
4715 && m->get(m->len - 1) != PUSH_COMP_MODE)
4723 error("cannot chop empty macro");
4726 m->set(POP_GROFFCOMP_MODE, m->len - 1);
4735 void substring_request()
4737 int start; // 0, 1, ..., n-1 or -1, -2, ...
4738 symbol s = get_name(1);
4739 if (!s.is_null() && get_integer(&start)) {
4740 request_or_macro *p = lookup_request(s);
4741 macro *m = p->to_macro();
4743 error("cannot apply `substring' on a request");
4746 if (!has_arg() || get_integer(&end)) {
4747 int real_length = 0; // 1, 2, ..., n
4748 string_iterator iter1(*m);
4749 for (int l = 0; l < m->len; l++) {
4750 int c = iter1.get(0);
4751 if (c == PUSH_GROFF_MODE
4752 || c == PUSH_COMP_MODE
4753 || c == POP_GROFFCOMP_MODE)
4760 start += real_length;
4768 if (start >= real_length || end < 0) {
4770 "start and end index of substring out of range");
4773 if (--(m->p->count) <= 0)
4782 "start index of substring out of range, set to 0");
4785 if (end >= real_length) {
4787 "end index of substring out of range, set to string length");
4788 end = real_length - 1;
4790 // now extract the substring
4791 string_iterator iter(*m);
4793 for (i = 0; i < start; i++) {
4794 int c = iter.get(0);
4795 while (c == PUSH_GROFF_MODE
4796 || c == PUSH_COMP_MODE
4797 || c == POP_GROFFCOMP_MODE)
4803 for (; i <= end; i++) {
4804 node *nd = 0; // pacify compiler
4805 int c = iter.get(&nd);
4806 while (c == PUSH_GROFF_MODE
4807 || c == PUSH_COMP_MODE
4808 || c == POP_GROFFCOMP_MODE)
4815 mac.append((unsigned char)c);
4824 void length_request()
4828 if (ret.is_null()) {
4838 else if (!tok.space()) {
4839 error("bad string definition");
4850 while (c != '\n' && c != EOF) {
4854 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4858 set_number_reg(ret, len);
4862 void asciify_macro()
4864 symbol s = get_name(1);
4866 request_or_macro *p = lookup_request(s);
4867 macro *m = p->to_macro();
4869 error("cannot asciify request");
4872 string_iterator iter(*m);
4874 node *nd = 0; // pacify compiler
4875 int c = iter.get(&nd);
4889 void unformat_macro()
4891 symbol s = get_name(1);
4893 request_or_macro *p = lookup_request(s);
4894 macro *m = p->to_macro();
4896 error("cannot unformat request");
4899 string_iterator iter(*m);
4901 node *nd = 0; // pacify compiler
4902 int c = iter.get(&nd);
4908 if (nd->set_unformat_flag())
4918 static void interpolate_environment_variable(symbol nm)
4920 const char *s = getenv(nm.contents());
4922 input_stack::push(make_temp_iterator(s));
4925 void interpolate_number_reg(symbol nm, int inc)
4927 reg *r = lookup_number_reg(nm);
4932 input_stack::push(make_temp_iterator(r->get_string()));
4935 static void interpolate_number_format(symbol nm)
4937 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4939 input_stack::push(make_temp_iterator(r->get_format()));
4942 static int get_delim_number(units *n, unsigned char si, int prev_value)
4946 if (start.delimiter(1)) {
4948 if (get_number(n, si, prev_value)) {
4950 warning(WARN_DELIM, "closing delimiter does not match");
4957 static int get_delim_number(units *n, unsigned char si)
4961 if (start.delimiter(1)) {
4963 if (get_number(n, si)) {
4965 warning(WARN_DELIM, "closing delimiter does not match");
4972 static int get_line_arg(units *n, unsigned char si, charinfo **cp)
4976 int start_level = input_stack::get_level();
4977 if (!start.delimiter(1))
4980 if (get_number(n, si)) {
4981 if (tok.dummy() || tok.transparent_dummy())
4983 if (!(start == tok && input_stack::get_level() == start_level)) {
4984 *cp = tok.get_char(1);
4987 if (!(start == tok && input_stack::get_level() == start_level))
4988 warning(WARN_DELIM, "closing delimiter does not match");
4994 static int read_size(int *x)
5004 else if (c == '+') {
5009 int val = 0; // pacify compiler
5015 // allow an increment either before or after the left parenthesis
5021 else if (c == '+') {
5036 val = val*10 + (c - '0');
5041 else if (csdigit(c)) {
5043 if (!inc && c != '0' && c < '4') {
5049 val = val*10 + (c - '0');
5053 else if (!tok.delimiter(1))
5059 if (!inc && (c == '-' || c == '+')) {
5060 inc = c == '+' ? 1 : -1;
5063 if (!get_number(&val, 'z'))
5065 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
5066 if (start.ch() == '[')
5067 error("missing `]'");
5069 error("missing closing delimiter");
5077 // special case -- \s[0] and \s0 means to revert to previous size
5084 *x = curenv->get_requested_point_size() + val;
5087 *x = curenv->get_requested_point_size() - val;
5094 "\\s escape results in non-positive point size; set to 1");
5100 error("bad digit in point size");
5105 static symbol get_delim_name()
5110 error("end of input at start of delimited name");
5113 if (start.newline()) {
5114 error("can't delimit name with a newline");
5117 int start_level = input_stack::get_level();
5118 char abuf[ABUF_SIZE];
5120 int buf_size = ABUF_SIZE;
5123 if (i + 1 > buf_size) {
5125 buf = new char[ABUF_SIZE*2];
5126 memcpy(buf, abuf, buf_size);
5127 buf_size = ABUF_SIZE*2;
5130 char *old_buf = buf;
5131 buf = new char[buf_size*2];
5132 memcpy(buf, old_buf, buf_size);
5139 && (compatible_flag || input_stack::get_level() == start_level))
5141 if ((buf[i] = tok.ch()) == 0) {
5142 error("missing delimiter (got %1)", tok.description());
5152 error("empty delimited name");
5167 static void do_register()
5171 if (!start.delimiter(1))
5174 symbol nm = get_long_name(1);
5179 reg *r = (reg *)number_reg_dictionary.lookup(nm);
5181 if (!r || !r->get_value(&prev_value))
5184 if (!get_number(&val, 'u', prev_value))
5187 warning(WARN_DELIM, "closing delimiter does not match");
5191 set_number_reg(nm, val);
5194 // this implements the \w escape sequence
5196 static void do_width()
5200 int start_level = input_stack::get_level();
5201 environment env(curenv);
5202 environment *oldenv = curenv;
5207 warning(WARN_DELIM, "missing closing delimiter");
5210 if (tok.newline()) {
5211 warning(WARN_DELIM, "missing closing delimiter");
5212 input_stack::push(make_temp_iterator("\n"));
5216 && (compatible_flag || input_stack::get_level() == start_level))
5221 units x = env.get_input_line_position().to_units();
5222 input_stack::push(make_temp_iterator(i_to_a(x)));
5223 env.width_registers();
5228 charinfo *page_character;
5230 void set_page_character()
5232 page_character = get_optional_char();
5236 static const symbol percent_symbol("%");
5238 void read_title_parts(node **part, hunits *part_width)
5241 if (tok.newline() || tok.eof())
5244 int start_level = input_stack::get_level();
5246 for (int i = 0; i < 3; i++) {
5247 while (!tok.newline() && !tok.eof()) {
5249 && (compatible_flag || input_stack::get_level() == start_level)) {
5253 if (page_character != 0 && tok.get_char() == page_character)
5254 interpolate_number_reg(percent_symbol, 0);
5259 curenv->wrap_up_tab();
5260 part_width[i] = curenv->get_input_line_position();
5261 part[i] = curenv->extract_output_line();
5263 while (!tok.newline() && !tok.eof())
5267 class non_interpreted_node : public node {
5270 non_interpreted_node(const macro &);
5271 int interpret(macro *);
5273 int ends_sentence();
5280 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
5284 int non_interpreted_node::ends_sentence()
5289 int non_interpreted_node::same(node *nd)
5291 return mac == ((non_interpreted_node *)nd)->mac;
5294 const char *non_interpreted_node::type()
5296 return "non_interpreted_node";
5299 int non_interpreted_node::force_tprint()
5304 int non_interpreted_node::is_tag()
5309 node *non_interpreted_node::copy()
5311 return new non_interpreted_node(mac);
5314 int non_interpreted_node::interpret(macro *m)
5316 string_iterator si(mac);
5317 node *n = 0; // pacify compiler
5330 static node *do_non_interpreted()
5335 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
5340 if (c == EOF || c == '\n') {
5341 error("missing \\?");
5344 return new non_interpreted_node(mac);
5347 static void encode_char(macro *mac, char c)
5350 if ((font::use_charnames_in_special) && tok.special()) {
5351 charinfo *ci = tok.get_char(1);
5352 const char *s = ci->get_symbol()->contents();
5353 if (s[0] != (char)0) {
5357 while (s[i] != (char)0) {
5364 else if (tok.stretchable_space()
5365 || tok.unstretchable_space())
5367 else if (!(tok.hyphen_indicator()
5369 || tok.transparent_dummy()
5370 || tok.zero_width_break()))
5371 error("%1 is invalid within \\X", tok.description());
5374 if ((font::use_charnames_in_special) && (c == '\\')) {
5376 * add escape escape sequence
5388 int start_level = input_stack::get_level();
5391 tok != start || input_stack::get_level() != start_level;
5394 warning(WARN_DELIM, "missing closing delimiter");
5397 if (tok.newline()) {
5398 input_stack::push(make_temp_iterator("\n"));
5399 warning(WARN_DELIM, "missing closing delimiter");
5407 else if (tok.leader())
5409 else if (tok.backspace())
5413 encode_char(&mac, c);
5415 return new special_node(mac);
5418 void device_request()
5420 if (!tok.newline() && !tok.eof()) {
5429 if (c != ' ' && c != '\t')
5432 for (; c != '\n' && c != EOF; c = get_copy(0))
5434 curenv->add_node(new special_node(mac));
5439 void device_macro_request()
5441 symbol s = get_name(1);
5442 if (!(s.is_null() || s.is_empty())) {
5443 request_or_macro *p = lookup_request(s);
5444 macro *m = p->to_macro();
5446 curenv->add_node(new special_node(*m));
5448 error("can't transparently throughput a request");
5453 void output_request()
5455 if (!tok.newline() && !tok.eof()) {
5463 if (c != ' ' && c != '\t')
5466 for (; c != '\n' && c != EOF; c = get_copy(0))
5467 topdiv->transparent_output(c);
5468 topdiv->transparent_output('\n');
5473 extern int image_no; // from node.cpp
5475 static node *do_suppress(symbol nm)
5477 if (nm.is_null() || nm.is_empty()) {
5478 error("expecting an argument to escape \\O");
5481 const char *s = nm.contents();
5484 if (begin_level == 0)
5485 // suppress generation of glyphs
5486 return new suppress_node(0, 0);
5489 if (begin_level == 0)
5490 // enable generation of glyphs
5491 return new suppress_node(1, 0);
5494 if (begin_level == 0)
5495 return new suppress_node(1, 1);
5507 s++; // move over '5'
5509 if (*s == (char)0) {
5510 error("missing position and filename in \\O");
5513 if (!(position == 'l'
5516 || position == 'i')) {
5517 error("l, r, c, or i position expected (got %1 in \\O)", position);
5520 s++; // onto image name
5521 if (s == (char *)0) {
5522 error("missing image name for \\O");
5526 if (begin_level == 0)
5527 return new suppress_node(symbol(s), position, image_no);
5533 error("`%1' is an invalid argument to \\O", *s);
5538 void special_node::tprint(troff_output_file *out)
5541 string_iterator iter(mac);
5543 int c = iter.get(0);
5546 for (const char *s = ::asciify(c); *s; s++)
5547 tprint_char(out, *s);
5552 int get_file_line(const char **filename, int *lineno)
5554 return input_stack::get_location(0, filename, lineno);
5560 if (get_integer(&n)) {
5561 const char *filename = 0;
5563 symbol s = get_long_name();
5564 filename = s.contents();
5566 (void)input_stack::set_location(filename, n-1);
5571 static int nroff_mode = 0;
5573 static void nroff_request()
5579 static void troff_request()
5585 static void skip_alternative()
5588 // ensure that ``.if 0\{'' works as expected
5589 if (tok.left_brace())
5593 c = input_stack::get(0);
5596 if (c == ESCAPE_LEFT_BRACE)
5598 else if (c == ESCAPE_RIGHT_BRACE)
5600 else if (c == escape_char && escape_char > 0)
5601 switch(input_stack::get(0)) {
5609 while ((c = input_stack::get(0)) != '\n' && c != EOF)
5613 Note that the level can properly be < 0, eg
5619 So don't give an error message in this case.
5621 if (level <= 0 && c == '\n')
5627 static void begin_alternative()
5629 while (tok.space() || tok.left_brace())
5639 static int_stack if_else_stack;
5646 while (tok.ch() == '!') {
5651 unsigned char c = tok.ch();
5654 result = !nroff_mode;
5656 else if (c == 'n') {
5658 result = nroff_mode;
5660 else if (c == 'v') {
5664 else if (c == 'o') {
5665 result = (topdiv->get_page_number() & 1);
5668 else if (c == 'e') {
5669 result = !(topdiv->get_page_number() & 1);
5672 else if (c == 'd' || c == 'r') {
5674 symbol nm = get_name(1);
5680 ? request_dictionary.lookup(nm) != 0
5681 : number_reg_dictionary.lookup(nm) != 0);
5683 else if (c == 'm') {
5685 symbol nm = get_long_name(1);
5690 result = (nm == default_symbol
5691 || color_dictionary.lookup(nm) != 0);
5693 else if (c == 'c') {
5696 charinfo *ci = tok.get_char(1);
5701 result = character_exists(ci, curenv);
5704 else if (c == 'F') {
5706 symbol nm = get_long_name(1);
5711 result = check_font(curenv->get_family()->nm, nm);
5713 else if (c == 'S') {
5715 symbol nm = get_long_name(1);
5720 result = check_style(nm);
5722 else if (tok.space())
5724 else if (tok.delimiter()) {
5726 int delim_level = input_stack::get_level();
5727 environment env1(curenv);
5728 environment env2(curenv);
5729 environment *oldenv = curenv;
5732 for (int i = 0; i < 2; i++) {
5735 if (tok.newline() || tok.eof()) {
5736 warning(WARN_DELIM, "missing closing delimiter");
5742 && (compatible_flag || input_stack::get_level() == delim_level))
5748 node *n1 = env1.extract_output_line();
5749 node *n2 = env2.extract_output_line();
5750 result = same_node_list(n1, n2);
5751 delete_node_list(n1);
5752 delete_node_list(n2);
5760 if (!get_number(&n, 'u')) {
5770 begin_alternative();
5776 void if_else_request()
5778 if_else_stack.push(do_if_request());
5788 if (if_else_stack.is_empty()) {
5789 warning(WARN_EL, "unbalanced .el request");
5793 if (if_else_stack.pop())
5796 begin_alternative();
5800 static int while_depth = 0;
5801 static int while_break_flag = 0;
5803 void while_request()
5808 mac.append(new token_node(tok));
5810 node *n = 0; // pacify compiler
5811 int c = input_stack::get(&n);
5827 if (c == ESCAPE_LEFT_BRACE)
5829 else if (c == ESCAPE_RIGHT_BRACE)
5831 else if (c == escape_char)
5834 if (c == '\n' && level <= 0)
5839 error("unbalanced \\{ \\}");
5842 input_stack::add_boundary();
5844 input_stack::push(new string_iterator(mac, "while loop"));
5846 if (!do_if_request()) {
5847 while (input_stack::get(0) != EOF)
5851 process_input_stack();
5852 if (while_break_flag || input_stack::is_return_boundary()) {
5853 while_break_flag = 0;
5857 input_stack::remove_boundary();
5863 void while_break_request()
5866 error("no while loop");
5870 while_break_flag = 1;
5871 while (input_stack::get(0) != EOF)
5877 void while_continue_request()
5880 error("no while loop");
5884 while (input_stack::get(0) != EOF)
5894 symbol nm = get_long_name(1);
5898 while (!tok.newline() && !tok.eof())
5901 FILE *fp = include_search_path.open_file_cautious(nm.contents());
5903 input_stack::push(new file_iterator(fp, nm.contents()));
5905 error("can't open `%1': %2", nm.contents(), strerror(errno));
5910 // like .so but use popen()
5915 error(".pso request not allowed in safer mode");
5919 #ifdef POPEN_MISSING
5920 error("pipes not available on this system");
5922 #else /* not POPEN_MISSING */
5923 if (tok.newline() || tok.eof())
5924 error("missing command");
5927 while ((c = get_copy(0)) == ' ' || c == '\t')
5930 char *buf = new char[buf_size];
5932 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5933 const char *s = asciify(c);
5934 int slen = strlen(s);
5935 if (buf_used + slen + 1> buf_size) {
5936 char *old_buf = buf;
5937 int old_buf_size = buf_size;
5939 buf = new char[buf_size];
5940 memcpy(buf, old_buf, old_buf_size);
5943 strcpy(buf + buf_used, s);
5946 buf[buf_used] = '\0';
5948 FILE *fp = popen(buf, POPEN_RT);
5950 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5952 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5956 #endif /* not POPEN_MISSING */
5962 static int llx_reg_contents = 0;
5963 static int lly_reg_contents = 0;
5964 static int urx_reg_contents = 0;
5965 static int ury_reg_contents = 0;
5967 struct bounding_box {
5968 int llx, lly, urx, ury;
5971 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5972 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5974 int parse_bounding_box(char *p, bounding_box *bb)
5976 if (sscanf(p, "%d %d %d %d",
5977 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5980 /* The Document Structuring Conventions say that the numbers
5981 should be integers. Unfortunately some broken applications
5983 double x1, x2, x3, x4;
5984 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5992 for (; *p == ' ' || *p == '\t'; p++)
5994 if (strncmp(p, "(atend)", 7) == 0) {
5999 bb->llx = bb->lly = bb->urx = bb->ury = 0;
6003 // This version is taken from psrm.cpp
6005 #define PS_LINE_MAX 255
6006 cset white_space("\n\r \t");
6008 int ps_get_line(char *buf, FILE *fp, const char* filename)
6017 while (c != '\r' && c != '\n' && c != EOF) {
6018 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
6019 error("invalid input character code %1 in `%2'", int(c), filename);
6020 else if (i < PS_LINE_MAX)
6024 error("PostScript file `%1' is non-conforming "
6025 "because length of line exceeds 255", filename);
6033 if (c != EOF && c != '\n')
6039 inline void assign_registers(int llx, int lly, int urx, int ury)
6041 llx_reg_contents = llx;
6042 lly_reg_contents = lly;
6043 urx_reg_contents = urx;
6044 ury_reg_contents = ury;
6047 void do_ps_file(FILE *fp, const char* filename)
6051 char buf[PS_LINE_MAX];
6052 llx_reg_contents = lly_reg_contents =
6053 urx_reg_contents = ury_reg_contents = 0;
6054 if (!ps_get_line(buf, fp, filename)) {
6055 error("`%1' is empty", filename);
6058 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
6059 error("`%1' is not conforming to the Document Structuring Conventions",
6063 while (ps_get_line(buf, fp, filename) != 0) {
6064 // in header comments, `%X' (`X' any printable character except
6065 // whitespace) is possible too
6066 if (buf[0] == '%') {
6067 if (strncmp(buf + 1, "%EndComments", 12) == 0)
6069 if (white_space(buf[1]))
6074 if (strncmp(buf + 1, "%BoundingBox:", 13) == 0) {
6075 int res = parse_bounding_box(buf + 14, &bb);
6077 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6080 else if (res == 2) {
6085 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6094 // in the trailer, the last BoundingBox comment is significant
6095 for (offset = 512; !last_try; offset *= 2) {
6096 int had_trailer = 0;
6098 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
6100 if (fseek(fp, 0L, 0) == -1)
6103 while (ps_get_line(buf, fp, filename) != 0) {
6104 if (buf[0] == '%' && buf[1] == '%') {
6106 if (strncmp(buf + 2, "Trailer", 7) == 0)
6110 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
6111 int res = parse_bounding_box(buf + 14, &bb);
6114 else if (res == 2) {
6115 error("`(atend)' not allowed in trailer of `%1'", filename);
6119 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6128 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6133 error("%%%%BoundingBox comment not found in `%1'", filename);
6136 void ps_bbox_request()
6138 symbol nm = get_long_name(1);
6142 while (!tok.newline() && !tok.eof())
6145 // PS files might contain non-printable characters, such as ^Z
6146 // and CRs not followed by an LF, so open them in binary mode.
6147 FILE *fp = include_search_path.open_file_cautious(nm.contents(),
6150 do_ps_file(fp, nm.contents());
6154 error("can't open `%1': %2", nm.contents(), strerror(errno));
6159 const char *asciify(int c)
6162 buf[0] = escape_char == '\0' ? '\\' : escape_char;
6163 buf[1] = buf[2] = '\0';
6165 case ESCAPE_QUESTION:
6168 case ESCAPE_AMPERSAND:
6171 case ESCAPE_RIGHT_PARENTHESIS:
6174 case ESCAPE_UNDERSCORE:
6180 case ESCAPE_CIRCUMFLEX:
6183 case ESCAPE_LEFT_BRACE:
6186 case ESCAPE_RIGHT_BRACE:
6189 case ESCAPE_LEFT_QUOTE:
6192 case ESCAPE_RIGHT_QUOTE:
6210 case ESCAPE_PERCENT:
6222 case PUSH_GROFF_MODE:
6223 case PUSH_COMP_MODE:
6224 case POP_GROFFCOMP_MODE:
6228 if (invalid_input_char(c))
6237 const char *input_char_description(int c)
6241 return "a newline character";
6243 return "a backspace character";
6245 return "a leader character";
6247 return "a tab character";
6249 return "a space character";
6253 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
6254 if (invalid_input_char(c)) {
6255 const char *s = asciify(c);
6262 sprintf(buf, "magic character code %d", c);
6271 sprintf(buf, "character code %d", c);
6277 if (!tok.newline() && !tok.eof()) {
6286 if (c != ' ' && c != '\t')
6290 for (; c != '\n' && c != EOF; c = get_copy(0))
6293 curenv->add_node(new tag_node(s, 0));
6300 if (!tok.newline() && !tok.eof()) {
6309 if (c != ' ' && c != '\t')
6313 for (; c != '\n' && c != EOF; c = get_copy(0))
6316 curenv->add_node(new tag_node(s, 1));
6321 // .tm, .tm1, and .tmc
6323 void do_terminal(int newline, int string_like)
6325 if (!tok.newline() && !tok.eof()) {
6329 if (string_like && c == '"') {
6333 if (c != ' ' && c != '\t')
6336 for (; c != '\n' && c != EOF; c = get_copy(0))
6337 fputs(asciify(c), stderr);
6340 fputc('\n', stderr);
6355 void terminal_continue()
6360 dictionary stream_dictionary(20);
6362 void do_open(int append)
6364 symbol stream = get_name(1);
6365 if (!stream.is_null()) {
6366 symbol filename = get_long_name(1);
6367 if (!filename.is_null()) {
6369 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
6371 error("can't open `%1' for %2: %3",
6372 filename.contents(),
6373 append ? "appending" : "writing",
6375 fp = (FILE *)stream_dictionary.remove(stream);
6378 fp = (FILE *)stream_dictionary.lookup(stream, fp);
6389 error(".open request not allowed in safer mode");
6396 void opena_request()
6399 error(".opena request not allowed in safer mode");
6406 void close_request()
6408 symbol stream = get_name(1);
6409 if (!stream.is_null()) {
6410 FILE *fp = (FILE *)stream_dictionary.remove(stream);
6412 error("no stream named `%1'", stream.contents());
6419 // .write and .writec
6421 void do_write_request(int newline)
6423 symbol stream = get_name(1);
6424 if (stream.is_null()) {
6428 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6430 error("no stream named `%1'", stream.contents());
6435 while ((c = get_copy(0)) == ' ')
6439 for (; c != '\n' && c != EOF; c = get_copy(0))
6440 fputs(asciify(c), fp);
6447 void write_request()
6449 do_write_request(1);
6452 void write_request_continue()
6454 do_write_request(0);
6457 void write_macro_request()
6459 symbol stream = get_name(1);
6460 if (stream.is_null()) {
6464 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6466 error("no stream named `%1'", stream.contents());
6470 symbol s = get_name(1);
6475 request_or_macro *p = lookup_request(s);
6476 macro *m = p->to_macro();
6478 error("cannot write request");
6480 string_iterator iter(*m);
6482 int c = iter.get(0);
6485 fputs(asciify(c), fp);
6492 void warnscale_request()
6499 warn_scale = (double)units_per_inch;
6501 warn_scale = (double)units_per_inch / 2.54;
6503 warn_scale = (double)units_per_inch / 72.0;
6505 warn_scale = (double)units_per_inch / 6.0;
6508 "invalid scaling indicator `%1', using `i' instead", c);
6511 warn_scaling_indicator = c;
6516 void spreadwarn_request()
6519 if (has_arg() && get_hunits(&n, 'm')) {
6522 hunits em = curenv->get_size();
6523 spread_limit = (double)n.to_units()
6524 / (em.is_zero() ? hresolution : em.to_units());
6527 spread_limit = -spread_limit - 1; // no arg toggles on/off without
6528 // changing value; we mirror at
6529 // -0.5 to make zero a valid value
6533 static void init_charset_table()
6536 strcpy(buf, "char");
6537 for (int i = 0; i < 256; i++) {
6538 strcpy(buf + 4, i_to_a(i));
6539 charset_table[i] = get_charinfo(symbol(buf));
6540 charset_table[i]->set_ascii_code(i);
6542 charset_table[i]->set_hyphenation_code(cmlower(i));
6544 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
6545 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
6546 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
6547 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
6548 charset_table['"']->set_flags(charinfo::TRANSPARENT);
6549 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
6550 charset_table[')']->set_flags(charinfo::TRANSPARENT);
6551 charset_table[']']->set_flags(charinfo::TRANSPARENT);
6552 charset_table['*']->set_flags(charinfo::TRANSPARENT);
6553 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
6554 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
6555 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
6556 get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
6557 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6558 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6559 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6560 get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6561 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6562 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
6563 page_character = charset_table['%'];
6566 static void init_hpf_code_table()
6568 for (int i = 0; i < 256; i++)
6569 hpf_code_table[i] = i;
6572 static void do_translate(int translate_transparent, int translate_input)
6575 while (!tok.newline() && !tok.eof()) {
6577 // This is a really bizarre troff feature.
6579 translate_space_to_dummy = tok.dummy();
6580 if (tok.newline() || tok.eof())
6585 charinfo *ci1 = tok.get_char(1);
6589 if (tok.newline() || tok.eof()) {
6590 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6591 translate_transparent);
6595 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6596 translate_transparent);
6597 else if (tok.stretchable_space())
6598 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
6599 translate_transparent);
6600 else if (tok.dummy())
6601 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
6602 translate_transparent);
6603 else if (tok.hyphen_indicator())
6604 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
6605 translate_transparent);
6607 charinfo *ci2 = tok.get_char(1);
6611 ci1->set_translation(0, translate_transparent, translate_input);
6613 ci1->set_translation(ci2, translate_transparent, translate_input);
6625 void translate_no_transparent()
6630 void translate_input()
6638 if (get_integer(&flags))
6640 charinfo *ci = tok.get_char(1);
6642 charinfo *tem = ci->get_translation();
6645 ci->set_flags(flags);
6652 void hyphenation_code()
6655 while (!tok.newline() && !tok.eof()) {
6656 charinfo *ci = tok.get_char(1);
6661 unsigned char c = tok.ch();
6663 error("hyphenation code must be ordinary character");
6667 error("hyphenation code cannot be digit");
6670 ci->set_hyphenation_code(c);
6671 if (ci->get_translation()
6672 && ci->get_translation()->get_translation_input())
6673 ci->get_translation()->set_hyphenation_code(c);
6680 void hyphenation_patterns_file_code()
6683 while (!tok.newline() && !tok.eof()) {
6685 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
6687 error("missing output hyphenation code");
6690 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
6691 hpf_code_table[n1] = n2;
6695 error("output hyphenation code must be integer in the range 0..255");
6700 error("input hyphenation code must be integer in the range 0..255");
6707 charinfo *token::get_char(int required)
6709 if (type == TOKEN_CHAR)
6710 return charset_table[c];
6711 if (type == TOKEN_SPECIAL)
6712 return get_charinfo(nm);
6713 if (type == TOKEN_NUMBERED_CHAR)
6714 return get_charinfo_by_number(val);
6715 if (type == TOKEN_ESCAPE) {
6716 if (escape_char != 0)
6717 return charset_table[escape_char];
6719 error("`\\e' used while no current escape character");
6724 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6725 warning(WARN_MISSING, "missing normal or special character");
6727 error("normal or special character expected (got %1)", description());
6732 charinfo *get_optional_char()
6736 charinfo *ci = tok.get_char();
6738 check_missing_character();
6744 void check_missing_character()
6746 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6747 error("normal or special character expected (got %1): "
6748 "treated as missing",
6754 int token::add_to_node_list(node **pp)
6761 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6767 if (escape_char != 0)
6768 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6770 case TOKEN_HYPHEN_INDICATOR:
6771 *pp = (*pp)->add_discretionary_hyphen();
6773 case TOKEN_ITALIC_CORRECTION:
6774 *pp = (*pp)->add_italic_correction(&w);
6776 case TOKEN_LEFT_BRACE:
6778 case TOKEN_MARK_INPUT:
6779 set_number_reg(nm, curenv->get_input_line_position().to_units());
6785 case TOKEN_NUMBERED_CHAR:
6786 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6788 case TOKEN_RIGHT_BRACE:
6791 n = new hmotion_node(curenv->get_space_width(),
6792 curenv->get_fill_color());
6795 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6797 case TOKEN_STRETCHABLE_SPACE:
6798 n = new unbreakable_space_node(curenv->get_space_width(),
6799 curenv->get_fill_color());
6801 case TOKEN_UNSTRETCHABLE_SPACE:
6802 n = new space_char_hmotion_node(curenv->get_space_width(),
6803 curenv->get_fill_color());
6805 case TOKEN_TRANSPARENT_DUMMY:
6806 n = new transparent_dummy_node;
6808 case TOKEN_ZERO_WIDTH_BREAK:
6809 n = new space_node(H0, curenv->get_fill_color());
6811 n->is_escape_colon();
6823 void token::process()
6825 if (possibly_handle_first_page_transition())
6828 case TOKEN_BACKSPACE:
6829 curenv->add_node(new hmotion_node(-curenv->get_space_width(),
6830 curenv->get_fill_color()));
6833 curenv->add_char(charset_table[c]);
6836 curenv->add_node(new dummy_node);
6845 if (escape_char != 0)
6846 curenv->add_char(charset_table[escape_char]);
6848 case TOKEN_BEGIN_TRAP:
6849 case TOKEN_END_TRAP:
6850 case TOKEN_PAGE_EJECTOR:
6851 // these are all handled in process_input_stack()
6853 case TOKEN_HYPHEN_INDICATOR:
6854 curenv->add_hyphen_indicator();
6856 case TOKEN_INTERRUPT:
6857 curenv->interrupt();
6859 case TOKEN_ITALIC_CORRECTION:
6860 curenv->add_italic_correction();
6863 curenv->handle_tab(1);
6865 case TOKEN_LEFT_BRACE:
6867 case TOKEN_MARK_INPUT:
6868 set_number_reg(nm, curenv->get_input_line_position().to_units());
6874 curenv->add_node(nd);
6877 case TOKEN_NUMBERED_CHAR:
6878 curenv->add_char(get_charinfo_by_number(val));
6881 // handled in process_input_stack()
6883 case TOKEN_RIGHT_BRACE:
6889 curenv->add_char(get_charinfo(nm));
6894 case TOKEN_STRETCHABLE_SPACE:
6895 curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
6896 curenv->get_fill_color()));
6898 case TOKEN_UNSTRETCHABLE_SPACE:
6899 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
6900 curenv->get_fill_color()));
6903 curenv->handle_tab(0);
6905 case TOKEN_TRANSPARENT:
6907 case TOKEN_TRANSPARENT_DUMMY:
6908 curenv->add_node(new transparent_dummy_node);
6910 case TOKEN_ZERO_WIDTH_BREAK:
6912 node *tmp = new space_node(H0, curenv->get_fill_color());
6913 tmp->freeze_space();
6914 tmp->is_escape_colon();
6915 curenv->add_node(tmp);
6923 class nargs_reg : public reg {
6925 const char *get_string();
6928 const char *nargs_reg::get_string()
6930 return i_to_a(input_stack::nargs());
6933 class lineno_reg : public reg {
6935 const char *get_string();
6938 const char *lineno_reg::get_string()
6942 if (!input_stack::get_location(0, &file, &line))
6944 return i_to_a(line);
6947 class writable_lineno_reg : public general_reg {
6949 writable_lineno_reg();
6950 void set_value(units);
6951 int get_value(units *);
6954 writable_lineno_reg::writable_lineno_reg()
6958 int writable_lineno_reg::get_value(units *res)
6962 if (!input_stack::get_location(0, &file, &line))
6968 void writable_lineno_reg::set_value(units n)
6970 input_stack::set_location(0, n);
6973 class filename_reg : public reg {
6975 const char *get_string();
6978 const char *filename_reg::get_string()
6982 if (input_stack::get_location(0, &file, &line))
6988 class break_flag_reg : public reg {
6990 const char *get_string();
6993 const char *break_flag_reg::get_string()
6995 return i_to_a(input_stack::get_break_flag());
6998 class constant_reg : public reg {
7001 constant_reg(const char *);
7002 const char *get_string();
7005 constant_reg::constant_reg(const char *p) : s(p)
7009 const char *constant_reg::get_string()
7014 constant_int_reg::constant_int_reg(int *q) : p(q)
7018 const char *constant_int_reg::get_string()
7023 void abort_request()
7028 else if (tok.newline())
7031 while ((c = get_copy(0)) == ' ')
7034 if (c == EOF || c == '\n')
7035 fputs("User Abort.", stderr);
7037 for (; c != '\n' && c != EOF; c = get_copy(0))
7038 fputs(asciify(c), stderr);
7040 fputc('\n', stderr);
7041 cleanup_and_exit(1);
7047 char *s = new char[len];
7049 while ((c = get_copy(0)) == ' ')
7052 while (c != '\n' && c != EOF) {
7053 if (!invalid_input_char(c)) {
7056 s = new char[len*2];
7057 memcpy(s, tem, len);
7077 error(".pi request not allowed in safer mode");
7081 #ifdef POPEN_MISSING
7082 error("pipes not available on this system");
7084 #else /* not POPEN_MISSING */
7086 error("can't pipe: output already started");
7091 if ((pc = read_string()) == 0)
7092 error("can't pipe to empty command");
7094 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
7095 strcpy(s, pipe_command);
7098 a_delete pipe_command;
7105 #endif /* not POPEN_MISSING */
7109 static int system_status;
7111 void system_request()
7114 error(".sy request not allowed in safer mode");
7118 char *command = read_string();
7120 error("empty command");
7122 system_status = system(command);
7130 if (curdiv == topdiv && topdiv->before_first_page) {
7131 handle_initial_request(COPY_FILE_REQUEST);
7134 symbol filename = get_long_name(1);
7135 while (!tok.newline() && !tok.eof())
7139 if (!filename.is_null())
7140 curdiv->copy_file(filename.contents());
7148 if (curdiv == topdiv && topdiv->before_first_page) {
7149 handle_initial_request(VJUSTIFY_REQUEST);
7152 symbol type = get_long_name(1);
7153 if (!type.is_null())
7154 curdiv->vjustify(type);
7160 void transparent_file()
7162 if (curdiv == topdiv && topdiv->before_first_page) {
7163 handle_initial_request(TRANSPARENT_FILE_REQUEST);
7166 symbol filename = get_long_name(1);
7167 while (!tok.newline() && !tok.eof())
7171 if (!filename.is_null()) {
7173 FILE *fp = include_search_path.open_file_cautious(filename.contents());
7175 error("can't open `%1': %2", filename.contents(), strerror(errno));
7182 if (invalid_input_char(c))
7183 warning(WARN_INPUT, "invalid input character code %1", int(c));
7185 curdiv->transparent_output(c);
7190 curdiv->transparent_output('\n');
7202 page_range(int, int, page_range *);
7203 int contains(int n);
7206 page_range::page_range(int i, int j, page_range *p)
7207 : first(i), last(j), next(p)
7211 int page_range::contains(int n)
7213 return n >= first && (last <= 0 || n <= last);
7216 page_range *output_page_list = 0;
7218 int in_output_page_list(int n)
7220 if (!output_page_list)
7222 for (page_range *p = output_page_list; p; p = p->next)
7228 static void parse_output_page_list(char *p)
7234 else if (csdigit(*p)) {
7237 i = i*10 + *p++ - '0';
7238 while (csdigit(*p));
7248 j = j*10 + *p++ - '0';
7249 while (csdigit(*p));
7255 last_page_number = -1;
7256 else if (last_page_number >= 0 && j > last_page_number)
7257 last_page_number = j;
7258 output_page_list = new page_range(i, j, output_page_list);
7264 error("bad output page list");
7265 output_page_list = 0;
7269 static FILE *open_mac_file(const char *mac, char **path)
7271 // Try first FOOBAR.tmac, then tmac.FOOBAR
7272 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
7274 strcat(s1, MACRO_POSTFIX);
7275 FILE *fp = mac_path->open_file(s1, path);
7278 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
7279 strcpy(s2, MACRO_PREFIX);
7281 fp = mac_path->open_file(s2, path);
7287 static void process_macro_file(const char *mac)
7290 FILE *fp = open_mac_file(mac, &path);
7292 fatal("can't find macro file %1", mac);
7293 const char *s = symbol(path).contents();
7295 input_stack::push(new file_iterator(fp, s));
7297 process_input_stack();
7300 static void process_startup_file(const char *filename)
7303 search_path *orig_mac_path = mac_path;
7304 mac_path = &config_macro_path;
7305 FILE *fp = mac_path->open_file(filename, &path);
7307 input_stack::push(new file_iterator(fp, symbol(path).contents()));
7310 process_input_stack();
7312 mac_path = orig_mac_path;
7317 symbol nm = get_long_name(1);
7321 while (!tok.newline() && !tok.eof())
7324 FILE *fp = mac_path->open_file(nm.contents(), &path);
7325 // .mso doesn't (and cannot) go through open_mac_file, so we
7326 // need to do it here manually: If we have tmac.FOOBAR, try
7327 // FOOBAR.tmac and vice versa
7329 const char *fn = nm.contents();
7330 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
7331 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
7332 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
7333 strcat(s, MACRO_POSTFIX);
7334 fp = mac_path->open_file(s, &path);
7338 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
7339 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
7340 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
7341 strcpy(s, MACRO_PREFIX);
7342 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
7343 fp = mac_path->open_file(s, &path);
7349 input_stack::push(new file_iterator(fp, symbol(path).contents()));
7353 error("can't find macro file `%1'", nm.contents());
7358 static void process_input_file(const char *name)
7361 if (strcmp(name, "-") == 0) {
7367 fp = include_search_path.open_file_cautious(name);
7369 fatal("can't open `%1': %2", name, strerror(errno));
7371 input_stack::push(new file_iterator(fp, name));
7373 process_input_stack();
7376 // make sure the_input is empty before calling this
7378 static int evaluate_expression(const char *expr, units *res)
7380 input_stack::push(make_temp_iterator(expr));
7382 int success = get_number(res, 'u');
7383 while (input_stack::get(0) != EOF)
7388 static void do_register_assignment(const char *s)
7390 const char *p = strchr(s, '=');
7396 if (evaluate_expression(s + 1, &n))
7397 set_number_reg(buf, n);
7400 char *buf = new char[p - s + 1];
7401 memcpy(buf, s, p - s);
7404 if (evaluate_expression(p + 1, &n))
7405 set_number_reg(buf, n);
7410 static void set_string(const char *name, const char *value)
7412 macro *m = new macro;
7413 for (const char *p = value; *p; p++)
7414 if (!invalid_input_char((unsigned char)*p))
7416 request_dictionary.define(name, m);
7419 static void do_string_assignment(const char *s)
7421 const char *p = strchr(s, '=');
7426 set_string(buf, s + 1);
7429 char *buf = new char[p - s + 1];
7430 memcpy(buf, s, p - s);
7432 set_string(buf, p + 1);
7437 struct string_list {
7440 string_list(const char *ss) : s(ss), next(0) {}
7444 static void prepend_string(const char *s, string_list **p)
7446 string_list *l = new string_list(s);
7452 static void add_string(const char *s, string_list **p)
7456 *p = new string_list(s);
7459 void usage(FILE *stream, const char *prog)
7462 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
7463 " -rcn -Tname -Fdir -Idir -Mdir [files...]\n",
7467 int main(int argc, char **argv)
7469 program_name = argv[0];
7470 static char stderr_buf[BUFSIZ];
7471 setbuf(stderr, stderr_buf);
7473 string_list *macros = 0;
7474 string_list *register_assignments = 0;
7475 string_list *string_assignments = 0;
7480 int no_rc = 0; // don't process troffrc and troffrc-end
7481 int next_page_number = 0; // pacify compiler
7483 hresolution = vresolution = 1;
7484 // restore $PATH if called from groff
7485 char* groff_path = getenv("GROFF_PATH__");
7492 if (putenv(strsave(e.contents())))
7493 fatal("putenv failed");
7495 static const struct option long_options[] = {
7496 { "help", no_argument, 0, CHAR_MAX + 1 },
7497 { "version", no_argument, 0, 'v' },
7500 #if defined(DEBUGGING)
7501 #define DEBUG_OPTION "D"
7503 while ((c = getopt_long(argc, argv,
7504 "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
7505 DEBUG_OPTION, long_options, 0))
7510 printf("GNU troff (groff) version %s\n", Version_string);
7515 // Search path for .psbb files
7516 // and most other non-system input files.
7517 include_search_path.command_line_dir(optarg);
7522 is_html = (strcmp(device, "html") == 0);
7525 compatible_flag = 1;
7531 macro_path.command_line_dir(optarg);
7532 safer_macro_path.command_line_dir(optarg);
7533 config_macro_path.command_line_dir(optarg);
7536 font::command_line_font_dir(optarg);
7539 add_string(optarg, ¯os);
7548 enable_warning(optarg);
7551 disable_warning(optarg);
7560 ascii_output_flag = 1;
7563 suppress_output_flag = 1;
7566 if (sscanf(optarg, "%d", &next_page_number) == 1)
7569 error("bad page number");
7572 parse_output_page_list(optarg);
7575 if (*optarg == '\0')
7576 error("`-d' requires non-empty argument");
7578 add_string(optarg, &string_assignments);
7581 if (*optarg == '\0')
7582 error("`-r' requires non-empty argument");
7584 add_string(optarg, ®ister_assignments);
7587 default_family = symbol(optarg);
7593 // silently ignore these
7596 unsafe_flag = 1; // unsafe behaviour
7598 #if defined(DEBUGGING)
7603 case CHAR_MAX + 1: // --help
7604 usage(stdout, argv[0]);
7608 usage(stderr, argv[0]);
7610 break; // never reached
7615 mac_path = ¯o_path;
7616 set_string(".T", device);
7617 init_charset_table();
7618 init_hpf_code_table();
7619 if (!font::load_desc())
7620 fatal("sorry, I can't continue");
7621 units_per_inch = font::res;
7622 hresolution = font::hor;
7623 vresolution = font::vert;
7624 sizescale = font::sizescale;
7625 tcommand_flag = font::tcommand;
7626 warn_scale = (double)units_per_inch;
7627 warn_scaling_indicator = 'i';
7628 if (!fflag && font::family != 0 && *font::family != '\0')
7629 default_family = symbol(font::family);
7630 font_size::init_size_table(font::sizes);
7633 if (font::style_table) {
7634 for (i = 0; font::style_table[i]; i++)
7635 mount_style(j++, symbol(font::style_table[i]));
7637 for (i = 0; font::font_name_table[i]; i++, j++)
7638 // In the DESC file a font name of 0 (zero) means leave this
7640 if (strcmp(font::font_name_table[i], "0") != 0)
7641 mount_font(j, symbol(font::font_name_table[i]));
7642 curdiv = topdiv = new top_level_diversion;
7644 topdiv->set_next_page_number(next_page_number);
7645 init_input_requests();
7646 init_env_requests();
7647 init_div_requests();
7649 init_column_requests();
7651 init_node_requests();
7652 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
7654 init_reg_requests();
7655 init_hyphen_requests();
7656 init_environments();
7657 while (string_assignments) {
7658 do_string_assignment(string_assignments->s);
7659 string_list *tem = string_assignments;
7660 string_assignments = string_assignments->next;
7663 while (register_assignments) {
7664 do_register_assignment(register_assignments->s);
7665 string_list *tem = register_assignments;
7666 register_assignments = register_assignments->next;
7670 process_startup_file(INITIAL_STARTUP_FILE);
7672 process_macro_file(macros->s);
7673 string_list *tem = macros;
7674 macros = macros->next;
7678 process_startup_file(FINAL_STARTUP_FILE);
7679 for (i = optind; i < argc; i++)
7680 process_input_file(argv[i]);
7681 if (optind >= argc || iflag)
7682 process_input_file("-");
7684 return 0; // not reached
7690 if (has_arg() && get_integer(&n)) {
7691 if (n & ~WARN_TOTAL) {
7692 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
7698 warning_mask = WARN_TOTAL;
7702 static void init_registers()
7704 #ifdef LONG_FOR_TIME_T
7706 #else /* not LONG_FOR_TIME_T */
7708 #endif /* not LONG_FOR_TIME_T */
7710 // Use struct here to work around misfeature in old versions of g++.
7711 struct tm *tt = localtime(&t);
7712 set_number_reg("seconds", int(tt->tm_sec));
7713 set_number_reg("minutes", int(tt->tm_min));
7714 set_number_reg("hours", int(tt->tm_hour));
7715 set_number_reg("dw", int(tt->tm_wday + 1));
7716 set_number_reg("dy", int(tt->tm_mday));
7717 set_number_reg("mo", int(tt->tm_mon + 1));
7718 set_number_reg("year", int(1900 + tt->tm_year));
7719 set_number_reg("yr", int(tt->tm_year));
7720 set_number_reg("$$", getpid());
7721 number_reg_dictionary.define(".A",
7722 new constant_reg(ascii_output_flag
7728 * registers associated with \O
7731 static int output_reg_minx_contents = -1;
7732 static int output_reg_miny_contents = -1;
7733 static int output_reg_maxx_contents = -1;
7734 static int output_reg_maxy_contents = -1;
7736 void check_output_limits(int x, int y)
7738 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
7739 output_reg_minx_contents = x;
7740 if (x > output_reg_maxx_contents)
7741 output_reg_maxx_contents = x;
7742 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
7743 output_reg_miny_contents = y;
7744 if (y > output_reg_maxy_contents)
7745 output_reg_maxy_contents = y;
7748 void reset_output_registers()
7750 output_reg_minx_contents = -1;
7751 output_reg_miny_contents = -1;
7752 output_reg_maxx_contents = -1;
7753 output_reg_maxy_contents = -1;
7756 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
7758 *minx = output_reg_minx_contents;
7759 *miny = output_reg_miny_contents;
7760 *maxx = output_reg_maxx_contents;
7761 *maxy = output_reg_maxy_contents;
7764 void init_input_requests()
7766 init_request("ab", abort_request);
7767 init_request("als", alias_macro);
7768 init_request("am", append_macro);
7769 init_request("am1", append_nocomp_macro);
7770 init_request("ami", append_indirect_macro);
7771 init_request("ami1", append_indirect_nocomp_macro);
7772 init_request("as", append_string);
7773 init_request("as1", append_nocomp_string);
7774 init_request("asciify", asciify_macro);
7775 init_request("backtrace", backtrace_request);
7776 init_request("blm", blank_line_macro);
7777 init_request("break", while_break_request);
7778 init_request("cf", copy_file);
7779 init_request("cflags", char_flags);
7780 init_request("char", define_character);
7781 init_request("chop", chop_macro);
7782 init_request("close", close_request);
7783 init_request("color", activate_color);
7784 init_request("composite", composite_request);
7785 init_request("continue", while_continue_request);
7786 init_request("cp", compatible);
7787 init_request("de", define_macro);
7788 init_request("de1", define_nocomp_macro);
7789 init_request("defcolor", define_color);
7790 init_request("dei", define_indirect_macro);
7791 init_request("dei1", define_indirect_nocomp_macro);
7792 init_request("device", device_request);
7793 init_request("devicem", device_macro_request);
7794 init_request("do", do_request);
7795 init_request("ds", define_string);
7796 init_request("ds1", define_nocomp_string);
7797 init_request("ec", set_escape_char);
7798 init_request("ecr", restore_escape_char);
7799 init_request("ecs", save_escape_char);
7800 init_request("el", else_request);
7801 init_request("em", end_macro);
7802 init_request("eo", escape_off);
7803 init_request("ex", exit_request);
7804 init_request("fchar", define_fallback_character);
7805 #ifdef WIDOW_CONTROL
7806 init_request("fpl", flush_pending_lines);
7807 #endif /* WIDOW_CONTROL */
7808 init_request("hcode", hyphenation_code);
7809 init_request("hpfcode", hyphenation_patterns_file_code);
7810 init_request("ie", if_else_request);
7811 init_request("if", if_request);
7812 init_request("ig", ignore);
7813 init_request("length", length_request);
7814 init_request("lf", line_file);
7815 init_request("mso", macro_source);
7816 init_request("nop", nop_request);
7817 init_request("nroff", nroff_request);
7818 init_request("nx", next_file);
7819 init_request("open", open_request);
7820 init_request("opena", opena_request);
7821 init_request("output", output_request);
7822 init_request("pc", set_page_character);
7823 init_request("pi", pipe_output);
7824 init_request("pm", print_macros);
7825 init_request("psbb", ps_bbox_request);
7826 #ifndef POPEN_MISSING
7827 init_request("pso", pipe_source);
7828 #endif /* not POPEN_MISSING */
7829 init_request("rchar", remove_character);
7830 init_request("rd", read_request);
7831 init_request("return", return_macro_request);
7832 init_request("rm", remove_macro);
7833 init_request("rn", rename_macro);
7834 init_request("schar", define_special_character);
7835 init_request("shift", shift);
7836 init_request("so", source);
7837 init_request("spreadwarn", spreadwarn_request);
7838 init_request("substring", substring_request);
7839 init_request("sy", system_request);
7840 init_request("tag", tag);
7841 init_request("taga", taga);
7842 init_request("tm", terminal);
7843 init_request("tm1", terminal1);
7844 init_request("tmc", terminal_continue);
7845 init_request("tr", translate);
7846 init_request("trf", transparent_file);
7847 init_request("trin", translate_input);
7848 init_request("trnt", translate_no_transparent);
7849 init_request("troff", troff_request);
7850 init_request("unformat", unformat_macro);
7852 init_request("vj", vjustify);
7854 init_request("warn", warn_request);
7855 init_request("warnscale", warnscale_request);
7856 init_request("while", while_request);
7857 init_request("write", write_request);
7858 init_request("writec", write_request_continue);
7859 init_request("writem", write_macro_request);
7860 number_reg_dictionary.define(".$", new nargs_reg);
7861 number_reg_dictionary.define(".br", new break_flag_reg);
7862 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
7863 number_reg_dictionary.define(".O", new variable_reg(&begin_level));
7864 number_reg_dictionary.define(".c", new lineno_reg);
7865 number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
7866 number_reg_dictionary.define(".F", new filename_reg);
7867 number_reg_dictionary.define(".g", new constant_reg("1"));
7868 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
7869 number_reg_dictionary.define(".R", new constant_reg("10000"));
7870 number_reg_dictionary.define(".U", new constant_int_reg(&unsafe_flag));
7871 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
7872 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
7873 extern const char *major_version;
7874 number_reg_dictionary.define(".x", new constant_reg(major_version));
7875 extern const char *revision;
7876 number_reg_dictionary.define(".Y", new constant_reg(revision));
7877 extern const char *minor_version;
7878 number_reg_dictionary.define(".y", new constant_reg(minor_version));
7879 number_reg_dictionary.define("c.", new writable_lineno_reg);
7880 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
7881 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
7882 number_reg_dictionary.define("opmaxx",
7883 new variable_reg(&output_reg_maxx_contents));
7884 number_reg_dictionary.define("opmaxy",
7885 new variable_reg(&output_reg_maxy_contents));
7886 number_reg_dictionary.define("opminx",
7887 new variable_reg(&output_reg_minx_contents));
7888 number_reg_dictionary.define("opminy",
7889 new variable_reg(&output_reg_miny_contents));
7890 number_reg_dictionary.define("slimit",
7891 new variable_reg(&input_stack::limit));
7892 number_reg_dictionary.define("systat", new variable_reg(&system_status));
7893 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
7894 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
7897 object_dictionary request_dictionary(501);
7899 void init_request(const char *s, REQUEST_FUNCP f)
7901 request_dictionary.define(s, new request(f));
7904 static request_or_macro *lookup_request(symbol nm)
7906 assert(!nm.is_null());
7907 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
7909 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
7911 request_dictionary.define(nm, p);
7916 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7918 // Don't interpret character definitions in compatible mode.
7919 int old_compatible_flag = compatible_flag;
7920 compatible_flag = 0;
7921 int old_escape_char = escape_char;
7923 macro *mac = ci->set_macro(0);
7925 environment *oldenv = curenv;
7926 environment env(envp);
7928 curenv->set_composite();
7929 token old_tok = tok;
7930 input_stack::add_boundary();
7931 string_iterator *si =
7932 new string_iterator(*mac, "composite character", ci->nm);
7933 input_stack::push(si);
7934 // we don't use process_input_stack, because we don't want to recognise
7940 if (tok.newline()) {
7941 error("composite character mustn't contain newline");
7949 node *n = curenv->extract_output_line();
7950 input_stack::remove_boundary();
7954 compatible_flag = old_compatible_flag;
7955 escape_char = old_escape_char;
7960 static node *read_draw_node()
7964 if (!start.delimiter(1)){
7967 } while (tok != start && !tok.newline() && !tok.eof());
7972 error("missing argument");
7974 unsigned char type = tok.ch();
7976 read_color_draw_node(start);
7981 hvpair *point = new hvpair[maxpoints];
7986 for (i = 0; tok != start; i++) {
7987 if (i == maxpoints) {
7988 hvpair *oldpoint = point;
7989 point = new hvpair[maxpoints*2];
7990 for (int j = 0; j < maxpoints; j++)
7991 point[j] = oldpoint[j];
7995 if (!get_hunits(&point[i].h,
7996 type == 'f' || type == 't' ? 'u' : 'm')) {
8007 if (!get_vunits(&point[i].v, 'v')) {
8013 while (tok != start && !tok.newline() && !tok.eof())
8018 if (npoints != 1 || no_last_v) {
8019 error("two arguments needed for line");
8024 if (npoints != 1 || !no_last_v) {
8025 error("one argument needed for circle");
8031 if (npoints != 1 || no_last_v) {
8032 error("two arguments needed for ellipse");
8037 if (npoints != 2 || no_last_v) {
8038 error("four arguments needed for arc");
8044 error("even number of arguments needed for spline");
8047 if (npoints != 1 || !no_last_v) {
8048 error("one argument needed for gray shade");
8053 // silently pass it through
8056 draw_node *dn = new draw_node(type, point, npoints,
8057 curenv->get_font_size(),
8058 curenv->get_glyph_color(),
8059 curenv->get_fill_color());
8071 static void read_color_draw_node(token &start)
8075 error("missing color scheme");
8078 unsigned char scheme = tok.ch();
8081 char end = start.ch();
8084 col = read_cmy(end);
8087 col = &default_color;
8090 col = read_gray(end);
8093 col = read_cmyk(end);
8096 col = read_rgb(end);
8100 curenv->set_fill_color(col);
8101 while (tok != start) {
8102 if (tok.newline() || tok.eof()) {
8103 warning(WARN_DELIM, "missing closing delimiter");
8104 input_stack::push(make_temp_iterator("\n"));
8115 } warning_table[] = {
8116 { "char", WARN_CHAR },
8117 { "range", WARN_RANGE },
8118 { "break", WARN_BREAK },
8119 { "delim", WARN_DELIM },
8121 { "scale", WARN_SCALE },
8122 { "number", WARN_NUMBER },
8123 { "syntax", WARN_SYNTAX },
8124 { "tab", WARN_TAB },
8125 { "right-brace", WARN_RIGHT_BRACE },
8126 { "missing", WARN_MISSING },
8127 { "input", WARN_INPUT },
8128 { "escape", WARN_ESCAPE },
8129 { "space", WARN_SPACE },
8130 { "font", WARN_FONT },
8132 { "mac", WARN_MAC },
8133 { "reg", WARN_REG },
8135 { "color", WARN_COLOR },
8136 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
8137 { "w", WARN_TOTAL },
8138 { "default", DEFAULT_WARNING_MASK },
8141 static int lookup_warning(const char *name)
8143 for (unsigned int i = 0;
8144 i < sizeof(warning_table)/sizeof(warning_table[0]);
8146 if (strcmp(name, warning_table[i].name) == 0)
8147 return warning_table[i].mask;
8151 static void enable_warning(const char *name)
8153 int mask = lookup_warning(name);
8155 warning_mask |= mask;
8157 error("unknown warning `%1'", name);
8160 static void disable_warning(const char *name)
8162 int mask = lookup_warning(name);
8164 warning_mask &= ~mask;
8166 error("unknown warning `%1'", name);
8169 static void copy_mode_error(const char *format,
8175 static const char prefix[] = "(in ignored input) ";
8176 char *s = new char[sizeof(prefix) + strlen(format)];
8179 warning(WARN_IG, s, arg1, arg2, arg3);
8183 error(format, arg1, arg2, arg3);
8186 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
8188 static void do_error(error_type type,
8194 const char *filename;
8196 if (inhibit_errors && type < FATAL)
8199 input_stack::backtrace();
8200 if (!get_file_line(&filename, &lineno))
8203 errprint("%1:%2: ", filename, lineno);
8204 else if (program_name)
8205 fprintf(stderr, "%s: ", program_name);
8208 fputs("fatal error: ", stderr);
8213 fputs("warning: ", stderr);
8215 case OUTPUT_WARNING:
8216 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
8217 fprintf(stderr, "warning [p %d, %.1f%c",
8218 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
8219 if (topdiv != curdiv) {
8220 double fromtop1 = curdiv->get_vertical_position().to_units()
8222 fprintf(stderr, ", div `%s', %.1f%c",
8223 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
8225 fprintf(stderr, "]: ");
8228 errprint(format, arg1, arg2, arg3);
8229 fputc('\n', stderr);
8232 cleanup_and_exit(1);
8235 int warning(warning_type t,
8241 if ((t & warning_mask) != 0) {
8242 do_error(WARNING, format, arg1, arg2, arg3);
8249 int output_warning(warning_type t,
8255 if ((t & warning_mask) != 0) {
8256 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
8263 void error(const char *format,
8268 do_error(ERROR, format, arg1, arg2, arg3);
8271 void fatal(const char *format,
8276 do_error(FATAL, format, arg1, arg2, arg3);
8279 void fatal_with_file_and_line(const char *filename, int lineno,
8285 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
8286 errprint(format, arg1, arg2, arg3);
8287 fputc('\n', stderr);
8289 cleanup_and_exit(1);
8292 void error_with_file_and_line(const char *filename, int lineno,
8298 fprintf(stderr, "%s:%d: error: ", filename, lineno);
8299 errprint(format, arg1, arg2, arg3);
8300 fputc('\n', stderr);
8304 dictionary charinfo_dictionary(501);
8306 charinfo *get_charinfo(symbol nm)
8308 void *p = charinfo_dictionary.lookup(nm);
8310 return (charinfo *)p;
8311 charinfo *cp = new charinfo(nm);
8312 (void)charinfo_dictionary.lookup(nm, cp);
8316 int charinfo::next_index = 0;
8318 charinfo::charinfo(symbol s)
8319 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
8320 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
8321 not_found(0), transparent_translate(1), translate_input(0),
8322 mode(CHAR_NORMAL), nm(s)
8324 index = next_index++;
8328 void charinfo::set_hyphenation_code(unsigned char c)
8330 hyphenation_code = c;
8333 void charinfo::set_translation(charinfo *ci, int tt, int ti)
8337 if (hyphenation_code != 0)
8338 ci->set_hyphenation_code(hyphenation_code);
8339 if (asciify_code != 0)
8340 ci->set_asciify_code(asciify_code);
8341 else if (ascii_code != 0)
8342 ci->set_asciify_code(ascii_code);
8343 ci->set_translation_input();
8345 special_translation = TRANSLATE_NONE;
8346 transparent_translate = tt;
8349 void charinfo::set_special_translation(int c, int tt)
8351 special_translation = c;
8353 transparent_translate = tt;
8356 void charinfo::set_ascii_code(unsigned char c)
8361 void charinfo::set_asciify_code(unsigned char c)
8366 macro *charinfo::set_macro(macro *m)
8373 macro *charinfo::setx_macro(macro *m, char_mode cm)
8381 void charinfo::set_number(int n)
8387 int charinfo::get_number()
8389 assert(number >= 0);
8393 symbol UNNAMED_SYMBOL("---");
8395 // For numbered characters not between 0 and 255, we make a symbol out
8396 // of the number and store them in this dictionary.
8398 dictionary numbered_charinfo_dictionary(11);
8400 charinfo *get_charinfo_by_number(int n)
8402 static charinfo *number_table[256];
8404 if (n >= 0 && n < 256) {
8405 charinfo *ci = number_table[n];
8407 ci = new charinfo(UNNAMED_SYMBOL);
8409 number_table[n] = ci;
8414 symbol ns(i_to_a(n));
8415 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
8417 ci = new charinfo(UNNAMED_SYMBOL);
8419 (void)numbered_charinfo_dictionary.lookup(ns, ci);
8425 // This overrides the same function from libgroff; while reading font
8426 // definition files it puts single-letter glyph names into `charset_table'
8427 // and converts glyph names of the form `\x' (`x' a single letter) into `x'.
8428 // Consequently, symbol("x") refers to glyph name `\x', not `x'.
8430 glyph *name_to_glyph(const char *nm)
8434 ci = charset_table[nm[0] & 0xff];
8435 else if (nm[0] == '\\' && nm[2] == 0)
8436 ci = get_charinfo(symbol(nm + 1));
8438 ci = get_charinfo(symbol(nm));
8439 return ci->as_glyph();
8442 glyph *number_to_glyph(int n)
8444 return get_charinfo_by_number(n)->as_glyph();
8447 const char *glyph_to_name(glyph *g)
8449 charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
8450 return (ci->nm != UNNAMED_SYMBOL ? ci->nm.contents() : NULL);