CXX peace: src/troff/input.cpp
[s-roff.git] / src / troff / input.cpp
blobd18e6ddc02a43e4e1ad1b212dabec1dc26da3a35
1 /*@
2 * Copyright (c) 2014 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
4 * Copyright (C) 1989 - 1992, 2000 - 2008 Free Software Foundation, Inc.
5 * Written by James Clark (jjc@jclark.com)
7 * This is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2, or (at your option) any later
10 * version.
12 * This is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with groff; see the file COPYING. If not, write to the Free Software
19 * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
21 #define DEBUGGING // FIXME
23 #include "config.h"
24 #include "troff-config.h"
26 #include "defs.h"
27 #include "file_case.h"
28 #include "font.h"
29 #include "macropath.h"
30 #include "nonposix.h"
31 #include "posix.h"
32 #include "stringclass.h"
33 #include "unicode.h"
35 #include "charinfo.h"
36 #include "dictionary.h"
37 #include "div.h"
38 #include "env.h"
39 #include "hvunits.h"
40 #include "input.h"
41 #include "mtsm.h"
42 #include "node.h"
43 #include "reg.h"
44 #include "request.h"
45 #include "token.h"
46 #include "troff.h"
48 // warnings that are enabled by default
49 #ifndef DEFAULT_WARNING_MASK
50 # define DEFAULT_WARNING_MASK \
51 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT|WARN_FILE)
52 #endif
54 // initial size of buffer for reading names; expanded as necessary
55 #define ABUF_SIZE 16
57 extern "C" const char *program_name;
59 #ifdef COLUMN
60 void init_column_requests();
61 #endif /* COLUMN */
63 static node *read_draw_node();
64 static void read_color_draw_node(token &);
65 static void push_token(const token &);
66 void copy_file();
67 #ifdef COLUMN
68 void vjustify();
69 #endif /* COLUMN */
70 void transparent_file();
72 token tok;
73 int break_flag = 0;
74 int color_flag = 1; // colors are on by default
75 static int backtrace_flag = 0;
76 #ifndef POPEN_MISSING
77 char *pipe_command = 0;
78 #endif
79 charinfo *charset_table[UCHAR_MAX +1];
80 unsigned char hpf_code_table[UCHAR_MAX +1];
82 static int warning_mask = DEFAULT_WARNING_MASK;
83 static int inhibit_errors = 0;
84 static int ignoring = 0;
86 static void enable_warning(const char *);
87 static void disable_warning(const char *);
89 static int escape_char = '\\';
90 static symbol end_macro_name;
91 static symbol blank_line_macro_name;
92 static int compatible_flag = 0;
93 int ascii_output_flag = 0;
94 int suppress_output_flag = 0;
95 int is_html = 0;
96 int begin_level = 0; // number of nested \O escapes
98 int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
99 // \O[345], \R, \s, or \S has been processed
100 // in token::next()
101 int old_have_input = 0; // value of have_input right before \n
102 int tcommand_flag = 0;
103 int unsafe_flag = 0; // safer by default
105 int have_string_arg = 0; // whether we have \*[foo bar...]
107 double spread_limit = -3.0 - 1.0; // negative means deactivated
109 double warn_scale;
110 char warn_scaling_indicator;
111 int debug_state = 0; // turns on debugging of the html troff state
113 search_path *mac_path = &safer_macro_path;
115 // Defaults to the current directory.
116 search_path include_search_path(0, 0, 0, 1);
118 static int get_copy(node**, int = 0, int = 0);
119 static void copy_mode_error(const char *,
120 const errarg & = empty_errarg,
121 const errarg & = empty_errarg,
122 const errarg & = empty_errarg);
124 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
125 static symbol read_escape_name(read_mode = NO_ARGS);
126 static symbol read_long_escape_name(read_mode = NO_ARGS);
127 static void interpolate_string(symbol);
128 static void interpolate_string_with_args(symbol);
129 static void interpolate_macro(symbol, int = 0);
130 static void interpolate_number_format(symbol);
131 static void interpolate_environment_variable(symbol);
133 static symbol composite_glyph_name(symbol);
134 static void interpolate_arg(symbol);
135 static request_or_macro *lookup_request(symbol);
136 static int get_delim_number(units *, unsigned char);
137 static int get_delim_number(units *, unsigned char, units);
138 static symbol do_get_long_name(int, char);
139 static int get_line_arg(units *res, unsigned char si, charinfo **cp);
140 static int read_size(int *);
141 static symbol get_delim_name();
142 static void init_registers();
143 static void trapping_blank_line();
145 class input_iterator;
146 input_iterator *make_temp_iterator(const char *);
147 const char *input_char_description(int);
149 void process_input_stack();
150 void chop_macro(); // declare to avoid friend name injection
152 void set_escape_char()
154 if (has_arg()) {
155 if (tok.ch() == 0) {
156 error("bad escape character");
157 escape_char = '\\';
159 else
160 escape_char = tok.ch();
162 else
163 escape_char = '\\';
164 skip_line();
167 void escape_off()
169 escape_char = 0;
170 skip_line();
173 static int saved_escape_char = '\\';
175 void save_escape_char()
177 saved_escape_char = escape_char;
178 skip_line();
181 void restore_escape_char()
183 escape_char = saved_escape_char;
184 skip_line();
187 struct arg_list;
189 class input_iterator
191 friend class input_stack;
193 public:
194 int is_diversion;
195 statem *diversion_state;
197 input_iterator();
198 input_iterator(int is_div);
199 virtual ~input_iterator() {}
200 int get(node **);
202 protected:
203 const unsigned char *ptr;
204 const unsigned char *eptr;
205 input_iterator *next;
207 private: // FIXME (almost) abstract virtuals in private:?? ok, but.. ??
208 virtual int fill(node **);
209 virtual int peek();
210 virtual int has_args() { return 0; }
211 virtual int nargs() { return 0; }
212 virtual input_iterator *get_arg(int) { return 0; }
213 virtual arg_list *get_arg_list() { return 0; }
214 virtual symbol get_macro_name() { return NULL_SYMBOL; }
215 virtual int space_follows_arg(int) { return 0; }
216 virtual int get_break_flag() { return 0; }
217 virtual int get_location(int, const char **, int *) { return 0; }
218 virtual void backtrace() {}
219 virtual int set_location(const char *, int) { return 0; }
220 virtual int next_file(file_case *, const char *) { return 0; }
221 virtual void shift(int) {}
222 virtual int is_boundary() {return 0; }
223 virtual int is_file() { return 0; }
224 virtual int is_macro() { return 0; }
225 virtual void save_compatible_flag(int) {}
226 virtual int get_compatible_flag() { return 0; }
229 input_iterator::input_iterator()
230 : is_diversion(0), ptr(0), eptr(0)
234 input_iterator::input_iterator(int is_div)
235 : is_diversion(is_div), ptr(0), eptr(0)
239 int input_iterator::fill(node **)
241 return EOF;
244 int input_iterator::peek()
246 return EOF;
249 inline int input_iterator::get(node **p)
251 return ptr < eptr ? *ptr++ : fill(p);
254 class input_boundary
255 : public input_iterator
257 public:
258 int is_boundary() { return 1; }
261 class input_return_boundary
262 : public input_iterator
264 public:
265 int is_boundary() { return 2; }
268 class file_iterator
269 : public input_iterator
271 file_case *_fcp;
272 int lineno;
273 const char *filename;
274 int newline_flag;
275 int seen_escape;
276 enum { BUF_SIZE = 512 };
277 unsigned char buf[BUF_SIZE];
279 void close();
281 public:
282 file_iterator(file_case *, const char *);
283 ~file_iterator();
284 int fill(node **);
285 int peek();
286 int get_location(int, const char **, int *);
287 void backtrace();
288 int set_location(const char *, int);
289 int next_file(file_case *, const char *);
290 int is_file();
293 file_iterator::file_iterator(file_case *fcp, const char *fn)
294 : _fcp(fcp), lineno(1), filename(fn), newline_flag(0), seen_escape(0)
296 if ((font::use_charnames_in_special) && (fn != 0)) {
297 if (!the_output)
298 init_output();
299 the_output->put_filename(fn, _fcp->is_pipe());
303 file_iterator::~file_iterator()
305 if (_fcp != NULL)
306 delete _fcp;
309 void file_iterator::close()
311 if (_fcp != NULL) {
312 delete _fcp;
313 _fcp = NULL;
317 int file_iterator::is_file()
319 return 1;
322 int file_iterator::next_file(file_case *fcp, const char *s)
324 close();
325 _fcp = fcp;
326 filename = s;
327 lineno = 1;
328 newline_flag = 0;
329 seen_escape = 0;
330 ptr = 0;
331 eptr = 0;
332 return 1;
335 int file_iterator::fill(node **)
337 if (newline_flag)
338 lineno++;
339 newline_flag = 0;
340 unsigned char *p = buf;
341 ptr = p;
342 unsigned char *e = p + BUF_SIZE;
343 while (p < e) {
344 int c = _fcp->get_c();
345 if (c == EOF)
346 break;
347 if (invalid_input_char(c))
348 warning(WARN_INPUT, "invalid input character code %1", int(c));
349 else {
350 *p++ = c;
351 if (c == '\n') {
352 seen_escape = 0;
353 newline_flag = 1;
354 break;
356 seen_escape = (c == '\\');
359 if (p > buf) {
360 eptr = p;
361 return *ptr++;
363 else {
364 eptr = p;
365 return EOF;
369 int file_iterator::peek()
371 int c = _fcp->get_c();
372 while (invalid_input_char(c)) {
373 warning(WARN_INPUT, "invalid input character code %1", int(c));
374 c = _fcp->get_c();
376 if (c != EOF)
377 _fcp->unget_c(c);
378 return c;
381 int file_iterator::get_location(int /*allow_macro*/,
382 const char **filenamep, int *linenop)
384 *linenop = lineno;
385 if (filename != 0 && strcmp(filename, "-") == 0)
386 *filenamep = "<standard input>";
387 else
388 *filenamep = filename;
389 return 1;
392 void file_iterator::backtrace()
394 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
395 _fcp->is_pipe() ? "process" : "file");
398 int file_iterator::set_location(const char *f, int ln)
400 if (f) {
401 filename = f;
402 if (!the_output)
403 init_output();
404 the_output->put_filename(f, 0);
406 lineno = ln;
407 return 1;
410 input_iterator nil_iterator;
412 class input_stack
414 static input_iterator *top;
415 static int level;
416 static int finish_get(node **);
417 static int finish_peek();
419 public:
420 static int get(node **);
421 static int peek();
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();
428 static int nargs();
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_case *, 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();
444 static void clear();
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);
450 static int limit;
451 static int div_level;
452 static statem *diversion_state;
455 input_iterator *input_stack::top = &nil_iterator;
456 int input_stack::level = 0;
457 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
458 int input_stack::div_level = 0;
459 statem *input_stack::diversion_state = NULL;
460 int suppress_push=0;
462 inline int input_stack::get_level()
464 return level;
467 inline void input_stack::increase_level()
469 level++;
472 inline void input_stack::decrease_level()
474 level--;
477 inline int input_stack::get_div_level()
479 return div_level;
482 inline int input_stack::get(node **np)
484 int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
485 if (res == '\n') {
486 old_have_input = have_input;
487 have_input = 0;
489 return res;
492 int input_stack::finish_get(node **np)
494 for (;;) {
495 int c = top->fill(np);
496 if (c != EOF || top->is_boundary())
497 return c;
498 if (top == &nil_iterator)
499 break;
500 input_iterator *tem = top;
501 check_end_diversion(tem);
502 #if defined(DEBUGGING)
503 if (debug_state)
504 if (tem->is_diversion)
505 fprintf(stderr,
506 "in diversion level = %d\n", input_stack::get_div_level());
507 #endif
508 top = top->next;
509 level--;
510 delete tem;
511 if (top->ptr < top->eptr)
512 return *top->ptr++;
514 assert(level == 0);
515 return EOF;
518 inline int input_stack::peek()
520 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
523 void input_stack::check_end_diversion(input_iterator *t)
525 if (t->is_diversion) {
526 div_level--;
527 diversion_state = t->diversion_state;
531 int input_stack::finish_peek()
533 for (;;) {
534 int c = top->peek();
535 if (c != EOF || top->is_boundary())
536 return c;
537 if (top == &nil_iterator)
538 break;
539 input_iterator *tem = top;
540 check_end_diversion(tem);
541 top = top->next;
542 level--;
543 delete tem;
544 if (top->ptr < top->eptr)
545 return *top->ptr;
547 assert(level == 0);
548 return EOF;
551 void input_stack::add_boundary()
553 push(new input_boundary);
556 void input_stack::add_return_boundary()
558 push(new input_return_boundary);
561 int input_stack::is_return_boundary()
563 return top->is_boundary() == 2;
566 void input_stack::remove_boundary()
568 assert(top->is_boundary());
569 input_iterator *temp = top->next;
570 check_end_diversion(top);
572 delete top;
573 top = temp;
574 level--;
577 void input_stack::push(input_iterator *in)
579 if (in == 0)
580 return;
581 if (++level > limit && limit > 0)
582 fatal("input stack limit exceeded (probable infinite loop)");
583 in->next = top;
584 top = in;
585 if (top->is_diversion) {
586 div_level++;
587 in->diversion_state = diversion_state;
588 diversion_state = curenv->construct_state(0);
589 #if defined(DEBUGGING)
590 if (debug_state) {
591 curenv->dump_troff_state();
592 fflush(stderr);
594 #endif
596 #if defined(DEBUGGING)
597 if (debug_state)
598 if (top->is_diversion) {
599 fprintf(stderr,
600 "in diversion level = %d\n", input_stack::get_div_level());
601 fflush(stderr);
603 #endif
606 statem *get_diversion_state()
608 return input_stack::get_diversion_state();
611 statem *input_stack::get_diversion_state()
613 if (diversion_state == NULL)
614 return NULL;
615 else
616 return new statem(diversion_state);
619 input_iterator *input_stack::get_arg(int i)
621 input_iterator *p;
622 for (p = top; p != 0; p = p->next)
623 if (p->has_args())
624 return p->get_arg(i);
625 return 0;
628 arg_list *input_stack::get_arg_list()
630 input_iterator *p;
631 for (p = top; p != 0; p = p->next)
632 if (p->has_args())
633 return p->get_arg_list();
634 return 0;
637 symbol input_stack::get_macro_name()
639 input_iterator *p;
640 for (p = top; p != 0; p = p->next)
641 if (p->has_args())
642 return p->get_macro_name();
643 return NULL_SYMBOL;
646 int input_stack::space_follows_arg(int i)
648 input_iterator *p;
649 for (p = top; p != 0; p = p->next)
650 if (p->has_args())
651 return p->space_follows_arg(i);
652 return 0;
655 int input_stack::get_break_flag()
657 return top->get_break_flag();
660 void input_stack::shift(int n)
662 for (input_iterator *p = top; p; p = p->next)
663 if (p->has_args()) {
664 p->shift(n);
665 return;
669 int input_stack::nargs()
671 for (input_iterator *p =top; p != 0; p = p->next)
672 if (p->has_args())
673 return p->nargs();
674 return 0;
677 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
679 for (input_iterator *p = top; p; p = p->next)
680 if (p->get_location(allow_macro, filenamep, linenop))
681 return 1;
682 return 0;
685 void input_stack::backtrace()
687 const char *f;
688 int n;
689 // only backtrace down to (not including) the topmost file
690 for (input_iterator *p = top;
691 p && !p->get_location(0, &f, &n);
692 p = p->next)
693 p->backtrace();
696 void input_stack::backtrace_all()
698 for (input_iterator *p = top; p; p = p->next)
699 p->backtrace();
702 int input_stack::set_location(const char *filename, int lineno)
704 for (input_iterator *p = top; p; p = p->next)
705 if (p->set_location(filename, lineno))
706 return 1;
707 return 0;
710 void input_stack::next_file(file_case *fcp, const char *s)
712 input_iterator **pp;
713 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
714 if ((*pp)->next_file(fcp, s))
715 return;
716 if (++level > limit && limit > 0)
717 fatal("input stack limit exceeded");
718 *pp = new file_iterator(fcp, s);
719 (*pp)->next = &nil_iterator;
722 void input_stack::end_file()
724 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
725 if ((*pp)->is_file()) {
726 input_iterator *tem = *pp;
727 check_end_diversion(tem);
728 *pp = (*pp)->next;
729 delete tem;
730 level--;
731 return;
735 void input_stack::clear()
737 int nboundaries = 0;
738 while (top != &nil_iterator) {
739 if (top->is_boundary())
740 nboundaries++;
741 input_iterator *tem = top;
742 check_end_diversion(tem);
743 top = top->next;
744 level--;
745 delete tem;
747 // Keep while_request happy.
748 for (; nboundaries > 0; --nboundaries)
749 add_return_boundary();
752 void input_stack::pop_macro()
754 int nboundaries = 0;
755 int is_macro = 0;
756 do {
757 if (top->next == &nil_iterator)
758 break;
759 if (top->is_boundary())
760 nboundaries++;
761 is_macro = top->is_macro();
762 input_iterator *tem = top;
763 check_end_diversion(tem);
764 top = top->next;
765 level--;
766 delete tem;
767 } while (!is_macro);
768 // Keep while_request happy.
769 for (; nboundaries > 0; --nboundaries)
770 add_return_boundary();
773 inline void input_stack::save_compatible_flag(int f)
775 top->save_compatible_flag(f);
778 inline int input_stack::get_compatible_flag()
780 return top->get_compatible_flag();
783 void backtrace_request()
785 input_stack::backtrace_all();
786 fflush(stderr);
787 skip_line();
790 void next_file()
792 symbol nm = get_long_name();
793 while (!tok.newline() && !tok.eof())
794 tok.next();
795 if (nm.is_null())
796 input_stack::end_file();
797 else {
798 file_case *fcp = include_search_path.open_file_cautious(nm.contents(),
799 fcp->fc_const_path);
800 if (fcp != NULL)
801 input_stack::next_file(fcp, nm.contents());
802 else
803 error("can't open `%1': %2", nm.contents(), strerror(errno));
805 tok.next();
808 void shift()
810 int n;
811 if (!has_arg() || !get_integer(&n))
812 n = 1;
813 input_stack::shift(n);
814 skip_line();
817 static char get_char_for_escape_name(int allow_space = 0)
819 int c = get_copy(0, 0, 1);
820 switch (c) {
821 case EOF:
822 copy_mode_error("end of input in escape name");
823 return '\0';
824 default:
825 if (!invalid_input_char(c))
826 break;
827 // fall through
828 case '\n':
829 if (c == '\n')
830 input_stack::push(make_temp_iterator("\n"));
831 // fall through
832 case ' ':
833 if (c == ' ' && allow_space)
834 break;
835 // fall through
836 case '\t':
837 case '\001':
838 case '\b':
839 copy_mode_error("%1 is not allowed in an escape name",
840 input_char_description(c));
841 return '\0';
843 return c;
846 static symbol read_two_char_escape_name()
848 char buf[3];
849 buf[0] = get_char_for_escape_name();
850 if (buf[0] != '\0') {
851 buf[1] = get_char_for_escape_name();
852 if (buf[1] == '\0')
853 buf[0] = 0;
854 else
855 buf[2] = 0;
857 return symbol(buf);
860 static symbol read_long_escape_name(read_mode mode)
862 int start_level = input_stack::get_level();
863 char abuf[ABUF_SIZE];
864 char *buf = abuf;
865 int buf_size = ABUF_SIZE;
866 int i = 0;
867 char c;
868 int have_char = 0;
869 for (;;) {
870 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
871 if (c == 0) {
872 if (buf != abuf)
873 a_delete buf;
874 return NULL_SYMBOL;
876 have_char = 1;
877 if (mode == WITH_ARGS && c == ' ')
878 break;
879 if (i + 2 > buf_size) {
880 if (buf == abuf) {
881 buf = new char[ABUF_SIZE*2];
882 memcpy(buf, abuf, buf_size);
883 buf_size = ABUF_SIZE*2;
885 else {
886 char *old_buf = buf;
887 buf = new char[buf_size*2];
888 memcpy(buf, old_buf, buf_size);
889 buf_size *= 2;
890 a_delete old_buf;
893 if (c == ']' && input_stack::get_level() == start_level)
894 break;
895 buf[i++] = c;
897 buf[i] = 0;
898 if (c == ' ')
899 have_string_arg = 1;
900 if (buf == abuf) {
901 if (i == 0) {
902 if (mode != ALLOW_EMPTY)
903 copy_mode_error("empty escape name");
904 return EMPTY_SYMBOL;
906 return symbol(abuf);
908 else {
909 symbol s(buf);
910 a_delete buf;
911 return s;
915 static symbol read_escape_name(read_mode mode)
917 char c = get_char_for_escape_name();
918 if (c == 0)
919 return NULL_SYMBOL;
920 if (c == '(')
921 return read_two_char_escape_name();
922 if (c == '[' && !compatible_flag)
923 return read_long_escape_name(mode);
924 char buf[2];
925 buf[0] = c;
926 buf[1] = '\0';
927 return symbol(buf);
930 static symbol read_increment_and_escape_name(int *incp)
932 char c = get_char_for_escape_name();
933 switch (c) {
934 case 0:
935 *incp = 0;
936 return NULL_SYMBOL;
937 case '(':
938 *incp = 0;
939 return read_two_char_escape_name();
940 case '+':
941 *incp = 1;
942 return read_escape_name();
943 case '-':
944 *incp = -1;
945 return read_escape_name();
946 case '[':
947 if (!compatible_flag) {
948 *incp = 0;
949 return read_long_escape_name();
951 break;
953 *incp = 0;
954 char buf[2];
955 buf[0] = c;
956 buf[1] = '\0';
957 return symbol(buf);
960 static int get_copy(node **nd, int defining, int handle_escape_E)
962 for (;;) {
963 int c = input_stack::get(nd);
964 if (c == PUSH_GROFF_MODE) {
965 input_stack::save_compatible_flag(compatible_flag);
966 compatible_flag = 0;
967 continue;
969 if (c == PUSH_COMP_MODE) {
970 input_stack::save_compatible_flag(compatible_flag);
971 compatible_flag = 1;
972 continue;
974 if (c == POP_GROFFCOMP_MODE) {
975 compatible_flag = input_stack::get_compatible_flag();
976 continue;
978 if (c == BEGIN_QUOTE) {
979 input_stack::increase_level();
980 continue;
982 if (c == END_QUOTE) {
983 input_stack::decrease_level();
984 continue;
986 if (c == DOUBLE_QUOTE)
987 continue;
988 if (c == ESCAPE_E && handle_escape_E)
989 c = escape_char;
990 if (c == ESCAPE_NEWLINE) {
991 if (defining)
992 return c;
993 do {
994 c = input_stack::get(nd);
995 } while (c == ESCAPE_NEWLINE);
997 if (c != escape_char || escape_char <= 0)
998 return c;
999 again:
1000 c = input_stack::peek();
1001 switch(c) {
1002 case 0:
1003 return escape_char;
1004 case '"':
1005 (void)input_stack::get(0);
1006 while ((c = input_stack::get(0)) != '\n' && c != EOF)
1008 return c;
1009 case '#': // Like \" but newline is ignored.
1010 (void)input_stack::get(0);
1011 while ((c = input_stack::get(0)) != '\n')
1012 if (c == EOF)
1013 return EOF;
1014 break;
1015 case '$':
1017 (void)input_stack::get(0);
1018 symbol s = read_escape_name();
1019 if (!(s.is_null() || s.is_empty()))
1020 interpolate_arg(s);
1021 break;
1023 case '*':
1025 (void)input_stack::get(0);
1026 symbol s = read_escape_name(WITH_ARGS);
1027 if (!(s.is_null() || s.is_empty())) {
1028 if (have_string_arg) {
1029 have_string_arg = 0;
1030 interpolate_string_with_args(s);
1032 else
1033 interpolate_string(s);
1035 break;
1037 case 'a':
1038 (void)input_stack::get(0);
1039 return '\001';
1040 case 'e':
1041 (void)input_stack::get(0);
1042 return ESCAPE_e;
1043 case 'E':
1044 (void)input_stack::get(0);
1045 if (handle_escape_E)
1046 goto again;
1047 return ESCAPE_E;
1048 case 'n':
1050 (void)input_stack::get(0);
1051 int inc;
1052 symbol s = read_increment_and_escape_name(&inc);
1053 if (!(s.is_null() || s.is_empty()))
1054 interpolate_number_reg(s, inc);
1055 break;
1057 case 'g':
1059 (void)input_stack::get(0);
1060 symbol s = read_escape_name();
1061 if (!(s.is_null() || s.is_empty()))
1062 interpolate_number_format(s);
1063 break;
1065 case 't':
1066 (void)input_stack::get(0);
1067 return '\t';
1068 case 'V':
1070 (void)input_stack::get(0);
1071 symbol s = read_escape_name();
1072 if (!(s.is_null() || s.is_empty()))
1073 interpolate_environment_variable(s);
1074 break;
1076 case '\n':
1077 (void)input_stack::get(0);
1078 if (defining)
1079 return ESCAPE_NEWLINE;
1080 break;
1081 case ' ':
1082 (void)input_stack::get(0);
1083 return ESCAPE_SPACE;
1084 case '~':
1085 (void)input_stack::get(0);
1086 return ESCAPE_TILDE;
1087 case ':':
1088 (void)input_stack::get(0);
1089 return ESCAPE_COLON;
1090 case '|':
1091 (void)input_stack::get(0);
1092 return ESCAPE_BAR;
1093 case '^':
1094 (void)input_stack::get(0);
1095 return ESCAPE_CIRCUMFLEX;
1096 case '{':
1097 (void)input_stack::get(0);
1098 return ESCAPE_LEFT_BRACE;
1099 case '}':
1100 (void)input_stack::get(0);
1101 return ESCAPE_RIGHT_BRACE;
1102 case '`':
1103 (void)input_stack::get(0);
1104 return ESCAPE_LEFT_QUOTE;
1105 case '\'':
1106 (void)input_stack::get(0);
1107 return ESCAPE_RIGHT_QUOTE;
1108 case '-':
1109 (void)input_stack::get(0);
1110 return ESCAPE_HYPHEN;
1111 case '_':
1112 (void)input_stack::get(0);
1113 return ESCAPE_UNDERSCORE;
1114 case 'c':
1115 (void)input_stack::get(0);
1116 return ESCAPE_c;
1117 case '!':
1118 (void)input_stack::get(0);
1119 return ESCAPE_BANG;
1120 case '?':
1121 (void)input_stack::get(0);
1122 return ESCAPE_QUESTION;
1123 case '&':
1124 (void)input_stack::get(0);
1125 return ESCAPE_AMPERSAND;
1126 case ')':
1127 (void)input_stack::get(0);
1128 return ESCAPE_RIGHT_PARENTHESIS;
1129 case '.':
1130 (void)input_stack::get(0);
1131 return c;
1132 case '%':
1133 (void)input_stack::get(0);
1134 return ESCAPE_PERCENT;
1135 default:
1136 if (c == escape_char) {
1137 (void)input_stack::get(0);
1138 return c;
1140 else
1141 return escape_char;
1146 class non_interpreted_char_node
1147 : public node
1149 unsigned char c;
1151 public:
1152 non_interpreted_char_node(unsigned char);
1153 node *copy();
1154 int interpret(macro *);
1155 int same(node *);
1156 const char *type();
1157 int force_tprint();
1158 int is_tag();
1161 int non_interpreted_char_node::same(node *nd)
1163 return c == ((non_interpreted_char_node *)nd)->c;
1166 const char *non_interpreted_char_node::type()
1168 return "non_interpreted_char_node";
1171 int non_interpreted_char_node::force_tprint()
1173 return 0;
1176 int non_interpreted_char_node::is_tag()
1178 return 0;
1181 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1183 assert(n != 0);
1186 node *non_interpreted_char_node::copy()
1188 return new non_interpreted_char_node(c);
1191 int non_interpreted_char_node::interpret(macro *mac)
1193 mac->append(c);
1194 return 1;
1197 static void do_width();
1198 static node *do_non_interpreted();
1199 static node *do_special();
1200 static node *do_suppress(symbol nm);
1201 static void do_register();
1203 dictionary color_dictionary(501);
1205 static color *lookup_color(symbol nm)
1207 assert(!nm.is_null());
1208 if (nm == default_symbol)
1209 return &default_color;
1210 color *c = (color *)color_dictionary.lookup(nm);
1211 if (c == 0)
1212 warning(WARN_COLOR, "color `%1' not defined", nm.contents());
1213 return c;
1216 void do_glyph_color(symbol nm)
1218 if (nm.is_null())
1219 return;
1220 if (nm.is_empty())
1221 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1222 else {
1223 color *tem = lookup_color(nm);
1224 if (tem)
1225 curenv->set_glyph_color(tem);
1226 else
1227 (void)color_dictionary.lookup(nm, new color(nm));
1231 void do_fill_color(symbol nm)
1233 if (nm.is_null())
1234 return;
1235 if (nm.is_empty())
1236 curenv->set_fill_color(curenv->get_prev_fill_color());
1237 else {
1238 color *tem = lookup_color(nm);
1239 if (tem)
1240 curenv->set_fill_color(tem);
1241 else
1242 (void)color_dictionary.lookup(nm, new color(nm));
1246 static unsigned int get_color_element(const char *scheme, const char *col)
1248 units val;
1249 if (!get_number(&val, 'f')) {
1250 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1251 tok.next();
1252 return 0;
1254 if (val < 0) {
1255 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1256 return 0;
1258 if (val > color::MAX_COLOR_VAL+1) {
1259 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1260 // we change 0x10000 to 0xffff
1261 return color::MAX_COLOR_VAL;
1263 return (unsigned int)val;
1266 static color *read_rgb(char end = 0)
1268 symbol component = do_get_long_name(0, end);
1269 if (component.is_null()) {
1270 warning(WARN_COLOR, "missing rgb color values");
1271 return 0;
1273 const char *s = component.contents();
1274 color *col = new color;
1275 if (*s == '#') {
1276 if (!col->read_rgb(s)) {
1277 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1278 delete col;
1279 return 0;
1282 else {
1283 if (!end)
1284 input_stack::push(make_temp_iterator(" "));
1285 input_stack::push(make_temp_iterator(s));
1286 tok.next();
1287 unsigned int r = get_color_element("rgb color", "red component");
1288 unsigned int g = get_color_element("rgb color", "green component");
1289 unsigned int b = get_color_element("rgb color", "blue component");
1290 col->set_rgb(r, g, b);
1292 return col;
1295 static color *read_cmy(char end = 0)
1297 symbol component = do_get_long_name(0, end);
1298 if (component.is_null()) {
1299 warning(WARN_COLOR, "missing cmy color values");
1300 return 0;
1302 const char *s = component.contents();
1303 color *col = new color;
1304 if (*s == '#') {
1305 if (!col->read_cmy(s)) {
1306 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1307 delete col;
1308 return 0;
1311 else {
1312 if (!end)
1313 input_stack::push(make_temp_iterator(" "));
1314 input_stack::push(make_temp_iterator(s));
1315 tok.next();
1316 unsigned int c = get_color_element("cmy color", "cyan component");
1317 unsigned int m = get_color_element("cmy color", "magenta component");
1318 unsigned int y = get_color_element("cmy color", "yellow component");
1319 col->set_cmy(c, m, y);
1321 return col;
1324 static color *read_cmyk(char end = 0)
1326 symbol component = do_get_long_name(0, end);
1327 if (component.is_null()) {
1328 warning(WARN_COLOR, "missing cmyk color values");
1329 return 0;
1331 const char *s = component.contents();
1332 color *col = new color;
1333 if (*s == '#') {
1334 if (!col->read_cmyk(s)) {
1335 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1336 delete col;
1337 return 0;
1340 else {
1341 if (!end)
1342 input_stack::push(make_temp_iterator(" "));
1343 input_stack::push(make_temp_iterator(s));
1344 tok.next();
1345 unsigned int c = get_color_element("cmyk color", "cyan component");
1346 unsigned int m = get_color_element("cmyk color", "magenta component");
1347 unsigned int y = get_color_element("cmyk color", "yellow component");
1348 unsigned int k = get_color_element("cmyk color", "black component");
1349 col->set_cmyk(c, m, y, k);
1351 return col;
1354 static color *read_gray(char end = 0)
1356 symbol component = do_get_long_name(0, end);
1357 if (component.is_null()) {
1358 warning(WARN_COLOR, "missing gray values");
1359 return 0;
1361 const char *s = component.contents();
1362 color *col = new color;
1363 if (*s == '#') {
1364 if (!col->read_gray(s)) {
1365 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1366 delete col;
1367 return 0;
1370 else {
1371 if (!end)
1372 input_stack::push(make_temp_iterator("\n"));
1373 input_stack::push(make_temp_iterator(s));
1374 tok.next();
1375 unsigned int g = get_color_element("gray", "gray value");
1376 col->set_gray(g);
1378 return col;
1381 static void activate_color()
1383 int n;
1384 if (has_arg() && get_integer(&n))
1385 color_flag = n != 0;
1386 else
1387 color_flag = 1;
1388 skip_line();
1391 static void define_color()
1393 symbol color_name = get_long_name(1);
1394 if (color_name.is_null()) {
1395 skip_line();
1396 return;
1398 if (color_name == default_symbol) {
1399 warning(WARN_COLOR, "default color can't be redefined");
1400 skip_line();
1401 return;
1403 symbol style = get_long_name(1);
1404 if (style.is_null()) {
1405 skip_line();
1406 return;
1408 color *col;
1409 if (strcmp(style.contents(), "rgb") == 0)
1410 col = read_rgb();
1411 else if (strcmp(style.contents(), "cmyk") == 0)
1412 col = read_cmyk();
1413 else if (strcmp(style.contents(), "gray") == 0)
1414 col = read_gray();
1415 else if (strcmp(style.contents(), "grey") == 0)
1416 col = read_gray();
1417 else if (strcmp(style.contents(), "cmy") == 0)
1418 col = read_cmy();
1419 else {
1420 warning(WARN_COLOR,
1421 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1422 style.contents());
1423 skip_line();
1424 return;
1426 if (col) {
1427 col->nm = color_name;
1428 (void)color_dictionary.lookup(color_name, col);
1430 skip_line();
1433 static node *do_overstrike()
1435 token start;
1436 overstrike_node *on = new overstrike_node;
1437 int start_level = input_stack::get_level();
1438 start.next();
1439 for (;;) {
1440 tok.next();
1441 if (tok.newline() || tok.eof()) {
1442 warning(WARN_DELIM, "missing closing delimiter");
1443 input_stack::push(make_temp_iterator("\n"));
1444 break;
1446 if (tok == start
1447 && (compatible_flag || input_stack::get_level() == start_level))
1448 break;
1449 charinfo *ci = tok.get_char(1);
1450 if (ci) {
1451 node *n = curenv->make_char_node(ci);
1452 if (n)
1453 on->overstrike(n);
1456 return on;
1459 static node *do_bracket()
1461 token start;
1462 bracket_node *bn = new bracket_node;
1463 start.next();
1464 int start_level = input_stack::get_level();
1465 for (;;) {
1466 tok.next();
1467 if (tok.eof()) {
1468 warning(WARN_DELIM, "missing closing delimiter");
1469 break;
1471 if (tok.newline()) {
1472 warning(WARN_DELIM, "missing closing delimiter");
1473 input_stack::push(make_temp_iterator("\n"));
1474 break;
1476 if (tok == start
1477 && (compatible_flag || input_stack::get_level() == start_level))
1478 break;
1479 charinfo *ci = tok.get_char(1);
1480 if (ci) {
1481 node *n = curenv->make_char_node(ci);
1482 if (n)
1483 bn->bracket(n);
1486 return bn;
1489 static int do_name_test()
1491 token start;
1492 start.next();
1493 int start_level = input_stack::get_level();
1494 int bad_char = 0;
1495 int some_char = 0;
1496 for (;;) {
1497 tok.next();
1498 if (tok.newline() || tok.eof()) {
1499 warning(WARN_DELIM, "missing closing delimiter");
1500 input_stack::push(make_temp_iterator("\n"));
1501 break;
1503 if (tok == start
1504 && (compatible_flag || input_stack::get_level() == start_level))
1505 break;
1506 if (!tok.ch())
1507 bad_char = 1;
1508 some_char = 1;
1510 return some_char && !bad_char;
1513 static int do_expr_test()
1515 token start;
1516 start.next();
1517 int start_level = input_stack::get_level();
1518 if (!start.delimiter(1))
1519 return 0;
1520 tok.next();
1521 // disable all warning and error messages temporarily
1522 int saved_warning_mask = warning_mask;
1523 int saved_inhibit_errors = inhibit_errors;
1524 warning_mask = 0;
1525 inhibit_errors = 1;
1526 int dummy;
1527 int result = get_number_rigidly(&dummy, 'u');
1528 warning_mask = saved_warning_mask;
1529 inhibit_errors = saved_inhibit_errors;
1530 if (tok == start && input_stack::get_level() == start_level)
1531 return result;
1532 // ignore everything up to the delimiter in case we aren't right there
1533 for (;;) {
1534 tok.next();
1535 if (tok.newline() || tok.eof()) {
1536 warning(WARN_DELIM, "missing closing delimiter");
1537 input_stack::push(make_temp_iterator("\n"));
1538 break;
1540 if (tok == start && input_stack::get_level() == start_level)
1541 break;
1543 return 0;
1546 #if 0
1547 static node *do_zero_width()
1549 token start;
1550 start.next();
1551 int start_level = input_stack::get_level();
1552 environment env(curenv);
1553 environment *oldenv = curenv;
1554 curenv = &env;
1555 for (;;) {
1556 tok.next();
1557 if (tok.newline() || tok.eof()) {
1558 error("missing closing delimiter");
1559 break;
1561 if (tok == start
1562 && (compatible_flag || input_stack::get_level() == start_level))
1563 break;
1564 tok.process();
1566 curenv = oldenv;
1567 node *rev = env.extract_output_line();
1568 node *n = 0;
1569 while (rev) {
1570 node *tem = rev;
1571 rev = rev->next;
1572 tem->next = n;
1573 n = tem;
1575 return new zero_width_node(n);
1578 #else
1580 // It's undesirable for \Z to change environments, because then
1581 // \n(.w won't work as expected.
1583 static node *do_zero_width()
1585 node *rev = new dummy_node;
1586 token start;
1587 start.next();
1588 int start_level = input_stack::get_level();
1589 for (;;) {
1590 tok.next();
1591 if (tok.newline() || tok.eof()) {
1592 warning(WARN_DELIM, "missing closing delimiter");
1593 input_stack::push(make_temp_iterator("\n"));
1594 break;
1596 if (tok == start
1597 && (compatible_flag || input_stack::get_level() == start_level))
1598 break;
1599 if (!tok.add_to_node_list(&rev))
1600 error("invalid token in argument to \\Z");
1602 node *n = 0;
1603 while (rev) {
1604 node *tem = rev;
1605 rev = rev->next;
1606 tem->next = n;
1607 n = tem;
1609 return new zero_width_node(n);
1612 #endif
1614 token_node *node::get_token_node()
1616 return 0;
1619 class token_node
1620 : public node
1622 public:
1623 token tk;
1625 token_node(const token &t);
1626 node *copy();
1627 token_node *get_token_node();
1628 int same(node *);
1629 const char *type();
1630 int force_tprint();
1631 int is_tag();
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()
1645 return this;
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()
1660 return 0;
1663 int token_node::is_tag()
1665 return 0;
1668 token::token() : nd(0), type(TOKEN_EMPTY)
1672 token::~token()
1674 delete nd;
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++.
1681 node *tem = t.nd;
1682 nd = tem ? tem->copy() : 0;
1685 void token::operator=(const token &t)
1687 delete nd;
1688 nm = t.nm;
1689 // Use two statements to work around bug in SGI C++.
1690 node *tem = t.nd;
1691 nd = tem ? tem->copy() : 0;
1692 c = t.c;
1693 val = t.val;
1694 dim = t.dim;
1695 type = t.type;
1698 void token::skip()
1700 while (space())
1701 next();
1704 int has_arg()
1706 while (tok.space())
1707 tok.next();
1708 return !tok.newline();
1711 void token::make_space()
1713 type = TOKEN_SPACE;
1716 void token::make_newline()
1718 type = TOKEN_NEWLINE;
1721 void token::next()
1723 if (nd) {
1724 delete nd;
1725 nd = 0;
1727 units x;
1728 for (;;) {
1729 node *n = 0;
1730 int cc = input_stack::get(&n);
1731 if (cc != escape_char || escape_char == 0) {
1732 handle_normal_char:
1733 switch(cc) {
1734 case PUSH_GROFF_MODE:
1735 input_stack::save_compatible_flag(compatible_flag);
1736 compatible_flag = 0;
1737 continue;
1738 case PUSH_COMP_MODE:
1739 input_stack::save_compatible_flag(compatible_flag);
1740 compatible_flag = 1;
1741 continue;
1742 case POP_GROFFCOMP_MODE:
1743 compatible_flag = input_stack::get_compatible_flag();
1744 continue;
1745 case BEGIN_QUOTE:
1746 input_stack::increase_level();
1747 continue;
1748 case END_QUOTE:
1749 input_stack::decrease_level();
1750 continue;
1751 case DOUBLE_QUOTE:
1752 continue;
1753 case EOF:
1754 type = TOKEN_EOF;
1755 return;
1756 case TRANSPARENT_FILE_REQUEST:
1757 case TITLE_REQUEST:
1758 case COPY_FILE_REQUEST:
1759 #ifdef COLUMN
1760 case VJUSTIFY_REQUEST:
1761 #endif /* COLUMN */
1762 type = TOKEN_REQUEST;
1763 c = cc;
1764 return;
1765 case BEGIN_TRAP:
1766 type = TOKEN_BEGIN_TRAP;
1767 return;
1768 case END_TRAP:
1769 type = TOKEN_END_TRAP;
1770 return;
1771 case LAST_PAGE_EJECTOR:
1772 seen_last_page_ejector = 1;
1773 // fall through
1774 case PAGE_EJECTOR:
1775 type = TOKEN_PAGE_EJECTOR;
1776 return;
1777 case ESCAPE_PERCENT:
1778 ESCAPE_PERCENT:
1779 type = TOKEN_HYPHEN_INDICATOR;
1780 return;
1781 case ESCAPE_SPACE:
1782 ESCAPE_SPACE:
1783 type = TOKEN_UNSTRETCHABLE_SPACE;
1784 return;
1785 case ESCAPE_TILDE:
1786 ESCAPE_TILDE:
1787 type = TOKEN_STRETCHABLE_SPACE;
1788 return;
1789 case ESCAPE_COLON:
1790 ESCAPE_COLON:
1791 type = TOKEN_ZERO_WIDTH_BREAK;
1792 return;
1793 case ESCAPE_e:
1794 ESCAPE_e:
1795 type = TOKEN_ESCAPE;
1796 return;
1797 case ESCAPE_E:
1798 goto handle_escape_char;
1799 case ESCAPE_BAR:
1800 ESCAPE_BAR:
1801 type = TOKEN_NODE;
1802 nd = new hmotion_node(curenv->get_narrow_space_width(),
1803 curenv->get_fill_color());
1804 return;
1805 case ESCAPE_CIRCUMFLEX:
1806 ESCAPE_CIRCUMFLEX:
1807 type = TOKEN_NODE;
1808 nd = new hmotion_node(curenv->get_half_narrow_space_width(),
1809 curenv->get_fill_color());
1810 return;
1811 case ESCAPE_NEWLINE:
1812 have_input = 0;
1813 break;
1814 case ESCAPE_LEFT_BRACE:
1815 ESCAPE_LEFT_BRACE:
1816 type = TOKEN_LEFT_BRACE;
1817 return;
1818 case ESCAPE_RIGHT_BRACE:
1819 ESCAPE_RIGHT_BRACE:
1820 type = TOKEN_RIGHT_BRACE;
1821 return;
1822 case ESCAPE_LEFT_QUOTE:
1823 ESCAPE_LEFT_QUOTE:
1824 type = TOKEN_SPECIAL;
1825 nm = symbol("ga");
1826 return;
1827 case ESCAPE_RIGHT_QUOTE:
1828 ESCAPE_RIGHT_QUOTE:
1829 type = TOKEN_SPECIAL;
1830 nm = symbol("aa");
1831 return;
1832 case ESCAPE_HYPHEN:
1833 ESCAPE_HYPHEN:
1834 type = TOKEN_SPECIAL;
1835 nm = symbol("-");
1836 return;
1837 case ESCAPE_UNDERSCORE:
1838 ESCAPE_UNDERSCORE:
1839 type = TOKEN_SPECIAL;
1840 nm = symbol("ul");
1841 return;
1842 case ESCAPE_c:
1843 ESCAPE_c:
1844 type = TOKEN_INTERRUPT;
1845 return;
1846 case ESCAPE_BANG:
1847 ESCAPE_BANG:
1848 type = TOKEN_TRANSPARENT;
1849 return;
1850 case ESCAPE_QUESTION:
1851 ESCAPE_QUESTION:
1852 nd = do_non_interpreted();
1853 if (nd) {
1854 type = TOKEN_NODE;
1855 return;
1857 break;
1858 case ESCAPE_AMPERSAND:
1859 ESCAPE_AMPERSAND:
1860 type = TOKEN_DUMMY;
1861 return;
1862 case ESCAPE_RIGHT_PARENTHESIS:
1863 ESCAPE_RIGHT_PARENTHESIS:
1864 type = TOKEN_TRANSPARENT_DUMMY;
1865 return;
1866 case '\b':
1867 type = TOKEN_BACKSPACE;
1868 return;
1869 case ' ':
1870 type = TOKEN_SPACE;
1871 return;
1872 case '\t':
1873 type = TOKEN_TAB;
1874 return;
1875 case '\n':
1876 type = TOKEN_NEWLINE;
1877 return;
1878 case '\001':
1879 type = TOKEN_LEADER;
1880 return;
1881 case 0:
1883 assert(n != 0);
1884 token_node *tn = n->get_token_node();
1885 if (tn) {
1886 *this = tn->tk;
1887 delete tn;
1889 else {
1890 nd = n;
1891 type = TOKEN_NODE;
1894 return;
1895 default:
1896 type = TOKEN_CHAR;
1897 c = cc;
1898 return;
1901 else {
1902 handle_escape_char:
1903 cc = input_stack::get(&n);
1904 switch(cc) {
1905 case '(':
1906 nm = read_two_char_escape_name();
1907 type = TOKEN_SPECIAL;
1908 return;
1909 case EOF:
1910 type = TOKEN_EOF;
1911 error("end of input after escape character");
1912 return;
1913 case '`':
1914 goto ESCAPE_LEFT_QUOTE;
1915 case '\'':
1916 goto ESCAPE_RIGHT_QUOTE;
1917 case '-':
1918 goto ESCAPE_HYPHEN;
1919 case '_':
1920 goto ESCAPE_UNDERSCORE;
1921 case '%':
1922 goto ESCAPE_PERCENT;
1923 case ' ':
1924 goto ESCAPE_SPACE;
1925 case '0':
1926 nd = new hmotion_node(curenv->get_digit_width(),
1927 curenv->get_fill_color());
1928 type = TOKEN_NODE;
1929 return;
1930 case '|':
1931 goto ESCAPE_BAR;
1932 case '^':
1933 goto ESCAPE_CIRCUMFLEX;
1934 case '/':
1935 type = TOKEN_ITALIC_CORRECTION;
1936 return;
1937 case ',':
1938 type = TOKEN_NODE;
1939 nd = new left_italic_corrected_node;
1940 return;
1941 case '&':
1942 goto ESCAPE_AMPERSAND;
1943 case ')':
1944 goto ESCAPE_RIGHT_PARENTHESIS;
1945 case '!':
1946 goto ESCAPE_BANG;
1947 case '?':
1948 goto ESCAPE_QUESTION;
1949 case '~':
1950 goto ESCAPE_TILDE;
1951 case ':':
1952 goto ESCAPE_COLON;
1953 case '"':
1954 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1956 if (cc == '\n')
1957 type = TOKEN_NEWLINE;
1958 else
1959 type = TOKEN_EOF;
1960 return;
1961 case '#': // Like \" but newline is ignored.
1962 while ((cc = input_stack::get(0)) != '\n')
1963 if (cc == EOF) {
1964 type = TOKEN_EOF;
1965 return;
1967 break;
1968 case '$':
1970 symbol s = read_escape_name();
1971 if (!(s.is_null() || s.is_empty()))
1972 interpolate_arg(s);
1973 break;
1975 case '*':
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);
1983 else
1984 interpolate_string(s);
1986 break;
1988 case 'a':
1989 nd = new non_interpreted_char_node('\001');
1990 type = TOKEN_NODE;
1991 return;
1992 case 'A':
1993 c = '0' + do_name_test();
1994 type = TOKEN_CHAR;
1995 return;
1996 case 'b':
1997 nd = do_bracket();
1998 type = TOKEN_NODE;
1999 return;
2000 case 'B':
2001 c = '0' + do_expr_test();
2002 type = TOKEN_CHAR;
2003 return;
2004 case 'c':
2005 goto ESCAPE_c;
2006 case 'C':
2007 nm = get_delim_name();
2008 if (nm.is_null())
2009 break;
2010 type = TOKEN_SPECIAL;
2011 return;
2012 case 'd':
2013 type = TOKEN_NODE;
2014 nd = new vmotion_node(curenv->get_size() / 2,
2015 curenv->get_fill_color());
2016 return;
2017 case 'D':
2018 nd = read_draw_node();
2019 if (!nd)
2020 break;
2021 type = TOKEN_NODE;
2022 return;
2023 case 'e':
2024 goto ESCAPE_e;
2025 case 'E':
2026 goto handle_escape_char;
2027 case 'f':
2029 symbol s = read_escape_name(ALLOW_EMPTY);
2030 if (s.is_null())
2031 break;
2032 const char *p;
2033 for (p = s.contents(); *p != '\0'; p++)
2034 if (!csdigit(*p))
2035 break;
2036 if (*p || s.is_empty())
2037 curenv->set_font(s);
2038 else
2039 curenv->set_font(atoi(s.contents()));
2040 if (!compatible_flag)
2041 have_input = 1;
2042 break;
2044 case 'F':
2046 symbol s = read_escape_name(ALLOW_EMPTY);
2047 if (s.is_null())
2048 break;
2049 curenv->set_family(s);
2050 have_input = 1;
2051 break;
2053 case 'g':
2055 symbol s = read_escape_name();
2056 if (!(s.is_null() || s.is_empty()))
2057 interpolate_number_format(s);
2058 break;
2060 case 'h':
2061 if (!get_delim_number(&x, 'm'))
2062 break;
2063 type = TOKEN_NODE;
2064 nd = new hmotion_node(x, curenv->get_fill_color());
2065 return;
2066 case 'H':
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);
2073 else {
2074 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
2075 curenv->set_char_height(x);
2077 if (!compatible_flag)
2078 have_input = 1;
2079 break;
2080 case 'k':
2081 nm = read_escape_name();
2082 if (nm.is_null() || nm.is_empty())
2083 break;
2084 type = TOKEN_MARK_INPUT;
2085 return;
2086 case 'l':
2087 case 'L':
2089 charinfo *s = 0;
2090 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
2091 break;
2092 if (s == 0)
2093 s = get_charinfo(cc == 'l' ? "ru" : "br");
2094 type = TOKEN_NODE;
2095 node *char_node = curenv->make_char_node(s);
2096 if (cc == 'l')
2097 nd = new hline_node(x, char_node);
2098 else
2099 nd = new vline_node(x, char_node);
2100 return;
2102 case 'm':
2103 do_glyph_color(read_escape_name(ALLOW_EMPTY));
2104 if (!compatible_flag)
2105 have_input = 1;
2106 break;
2107 case 'M':
2108 do_fill_color(read_escape_name(ALLOW_EMPTY));
2109 if (!compatible_flag)
2110 have_input = 1;
2111 break;
2112 case 'n':
2114 int inc;
2115 symbol s = read_increment_and_escape_name(&inc);
2116 if (!(s.is_null() || s.is_empty()))
2117 interpolate_number_reg(s, inc);
2118 break;
2120 case 'N':
2121 if (!get_delim_number(&val, 0))
2122 break;
2123 if (val < 0) {
2124 warning(WARN_CHAR, "invalid numbered character %1", val);
2125 break;
2127 type = TOKEN_NUMBERED_CHAR;
2128 return;
2129 case 'o':
2130 nd = do_overstrike();
2131 type = TOKEN_NODE;
2132 return;
2133 case 'O':
2134 nd = do_suppress(read_escape_name());
2135 if (!nd)
2136 break;
2137 type = TOKEN_NODE;
2138 return;
2139 case 'p':
2140 type = TOKEN_SPREAD;
2141 return;
2142 case 'r':
2143 type = TOKEN_NODE;
2144 nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
2145 return;
2146 case 'R':
2147 do_register();
2148 if (!compatible_flag)
2149 have_input = 1;
2150 break;
2151 case 's':
2152 if (read_size(&x))
2153 curenv->set_size(x);
2154 if (!compatible_flag)
2155 have_input = 1;
2156 break;
2157 case 'S':
2158 if (get_delim_number(&x, 0))
2159 curenv->set_char_slant(x);
2160 if (!compatible_flag)
2161 have_input = 1;
2162 break;
2163 case 't':
2164 type = TOKEN_NODE;
2165 nd = new non_interpreted_char_node('\t');
2166 return;
2167 case 'u':
2168 type = TOKEN_NODE;
2169 nd = new vmotion_node(-curenv->get_size() / 2,
2170 curenv->get_fill_color());
2171 return;
2172 case 'v':
2173 if (!get_delim_number(&x, 'v'))
2174 break;
2175 type = TOKEN_NODE;
2176 nd = new vmotion_node(x, curenv->get_fill_color());
2177 return;
2178 case 'V':
2180 symbol s = read_escape_name();
2181 if (!(s.is_null() || s.is_empty()))
2182 interpolate_environment_variable(s);
2183 break;
2185 case 'w':
2186 do_width();
2187 break;
2188 case 'x':
2189 if (!get_delim_number(&x, 'v'))
2190 break;
2191 type = TOKEN_NODE;
2192 nd = new extra_size_node(x);
2193 return;
2194 case 'X':
2195 nd = do_special();
2196 if (!nd)
2197 break;
2198 type = TOKEN_NODE;
2199 return;
2200 case 'Y':
2202 symbol s = read_escape_name();
2203 if (s.is_null() || s.is_empty())
2204 break;
2205 request_or_macro *p = lookup_request(s);
2206 macro *m = p->to_macro();
2207 if (!m) {
2208 error("can't transparently throughput a request");
2209 break;
2211 nd = new special_node(*m);
2212 type = TOKEN_NODE;
2213 return;
2215 case 'z':
2217 next();
2218 if (type == TOKEN_NODE)
2219 nd = new zero_width_node(nd);
2220 else {
2221 charinfo *ci = get_char(1);
2222 if (ci == 0)
2223 break;
2224 node *gn = curenv->make_char_node(ci);
2225 if (gn == 0)
2226 break;
2227 nd = new zero_width_node(gn);
2228 type = TOKEN_NODE;
2230 return;
2232 case 'Z':
2233 nd = do_zero_width();
2234 if (nd == 0)
2235 break;
2236 type = TOKEN_NODE;
2237 return;
2238 case '{':
2239 goto ESCAPE_LEFT_BRACE;
2240 case '}':
2241 goto ESCAPE_RIGHT_BRACE;
2242 case '\n':
2243 break;
2244 case '[':
2245 if (!compatible_flag) {
2246 symbol s = read_long_escape_name(WITH_ARGS);
2247 if (s.is_null() || s.is_empty())
2248 break;
2249 if (have_string_arg) {
2250 have_string_arg = 0;
2251 nm = composite_glyph_name(s);
2253 else {
2254 const char *gn = check_unicode_name(s.contents());
2255 if (gn) {
2256 const char *gn_decomposed = decompose_unicode(gn);
2257 if (gn_decomposed)
2258 gn = &gn_decomposed[1];
2259 const char *groff_gn = unicode_to_glyph_name(gn);
2260 if (groff_gn)
2261 nm = symbol(groff_gn);
2262 else {
2263 char *buf = new char[strlen(gn) + 1 + 1];
2264 strcpy(buf, "u");
2265 strcat(buf, gn);
2266 nm = symbol(buf);
2267 a_delete buf;
2270 else
2271 nm = symbol(s.contents());
2273 type = TOKEN_SPECIAL;
2274 return;
2276 goto handle_normal_char;
2277 default:
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)
2289 if (type != t.type)
2290 return 0;
2291 switch(type) {
2292 case TOKEN_CHAR:
2293 return c == t.c;
2294 case TOKEN_SPECIAL:
2295 return nm == t.nm;
2296 case TOKEN_NUMBERED_CHAR:
2297 return val == t.val;
2298 default:
2299 return 1;
2303 int token::operator!=(const token &t)
2305 return !(*this == t);
2308 // is token a suitable delimiter (like ')?
2310 int token::delimiter(int err)
2312 switch(type) {
2313 case TOKEN_CHAR:
2314 switch(c) {
2315 case '0':
2316 case '1':
2317 case '2':
2318 case '3':
2319 case '4':
2320 case '5':
2321 case '6':
2322 case '7':
2323 case '8':
2324 case '9':
2325 case '+':
2326 case '-':
2327 case '/':
2328 case '*':
2329 case '%':
2330 case '<':
2331 case '>':
2332 case '=':
2333 case '&':
2334 case ':':
2335 case '(':
2336 case ')':
2337 case '.':
2338 if (err)
2339 error("cannot use character `%1' as a starting delimiter", char(c));
2340 return 0;
2341 default:
2342 return 1;
2344 case TOKEN_NODE:
2345 // the user doesn't know what a node is
2346 if (err)
2347 error("missing argument or invalid starting delimiter");
2348 return 0;
2349 case TOKEN_SPACE:
2350 case TOKEN_STRETCHABLE_SPACE:
2351 case TOKEN_UNSTRETCHABLE_SPACE:
2352 case TOKEN_TAB:
2353 case TOKEN_NEWLINE:
2354 if (err)
2355 error("cannot use %1 as a starting delimiter", description());
2356 return 0;
2357 default:
2358 return 1;
2362 const char *token::description()
2364 static char buf[4];
2365 switch (type) {
2366 case TOKEN_BACKSPACE:
2367 return "a backspace character";
2368 case TOKEN_CHAR:
2369 buf[0] = '`';
2370 buf[1] = c;
2371 buf[2] = '\'';
2372 buf[3] = '\0';
2373 return buf;
2374 case TOKEN_DUMMY:
2375 return "`\\&'";
2376 case TOKEN_ESCAPE:
2377 return "`\\e'";
2378 case TOKEN_HYPHEN_INDICATOR:
2379 return "`\\%'";
2380 case TOKEN_INTERRUPT:
2381 return "`\\c'";
2382 case TOKEN_ITALIC_CORRECTION:
2383 return "`\\/'";
2384 case TOKEN_LEADER:
2385 return "a leader character";
2386 case TOKEN_LEFT_BRACE:
2387 return "`\\{'";
2388 case TOKEN_MARK_INPUT:
2389 return "`\\k'";
2390 case TOKEN_NEWLINE:
2391 return "newline";
2392 case TOKEN_NODE:
2393 return "a node";
2394 case TOKEN_NUMBERED_CHAR:
2395 return "`\\N'";
2396 case TOKEN_RIGHT_BRACE:
2397 return "`\\}'";
2398 case TOKEN_SPACE:
2399 return "a space";
2400 case TOKEN_SPECIAL:
2401 return "a special character";
2402 case TOKEN_SPREAD:
2403 return "`\\p'";
2404 case TOKEN_STRETCHABLE_SPACE:
2405 return "`\\~'";
2406 case TOKEN_UNSTRETCHABLE_SPACE:
2407 return "`\\ '";
2408 case TOKEN_TAB:
2409 return "a tab character";
2410 case TOKEN_TRANSPARENT:
2411 return "`\\!'";
2412 case TOKEN_TRANSPARENT_DUMMY:
2413 return "`\\)'";
2414 case TOKEN_ZERO_WIDTH_BREAK:
2415 return "`\\:'";
2416 case TOKEN_EOF:
2417 return "end of input";
2418 default:
2419 break;
2421 return "a magic token";
2424 void skip_line()
2426 while (!tok.newline())
2427 if (tok.eof())
2428 return;
2429 else
2430 tok.next();
2431 tok.next();
2434 void compatible()
2436 int n;
2437 if (has_arg() && get_integer(&n))
2438 compatible_flag = n != 0;
2439 else
2440 compatible_flag = 1;
2441 skip_line();
2444 static void empty_name_warning(int required)
2446 if (tok.newline() || tok.eof()) {
2447 if (required)
2448 warning(WARN_MISSING, "missing name");
2450 else if (tok.right_brace() || tok.tab()) {
2451 const char *start = tok.description();
2452 do {
2453 tok.next();
2454 } while (tok.space() || tok.right_brace() || tok.tab());
2455 if (!tok.newline() && !tok.eof())
2456 error("%1 is not allowed before an argument", start);
2457 else if (required)
2458 warning(WARN_MISSING, "missing name");
2460 else if (required)
2461 error("name expected (got %1)", tok.description());
2462 else
2463 error("name expected (got %1): treated as missing", tok.description());
2466 static void non_empty_name_warning()
2468 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2469 && !tok.right_brace()
2470 // We don't want to give a warning for .el\{
2471 && !tok.left_brace())
2472 error("%1 is not allowed in a name", tok.description());
2475 symbol get_name(int required)
2477 if (compatible_flag) {
2478 char buf[3];
2479 tok.skip();
2480 if ((buf[0] = tok.ch()) != 0) {
2481 tok.next();
2482 if ((buf[1] = tok.ch()) != 0) {
2483 buf[2] = 0;
2484 tok.make_space();
2486 else
2487 non_empty_name_warning();
2488 return symbol(buf);
2490 else {
2491 empty_name_warning(required);
2492 return NULL_SYMBOL;
2495 else
2496 return get_long_name(required);
2499 symbol get_long_name(int required)
2501 return do_get_long_name(required, 0);
2504 static symbol do_get_long_name(int required, char end)
2506 while (tok.space())
2507 tok.next();
2508 char abuf[ABUF_SIZE];
2509 char *buf = abuf;
2510 int buf_size = ABUF_SIZE;
2511 int i = 0;
2512 for (;;) {
2513 // If end != 0 we normally have to append a null byte
2514 if (i + 2 > buf_size) {
2515 if (buf == abuf) {
2516 buf = new char[ABUF_SIZE*2];
2517 memcpy(buf, abuf, buf_size);
2518 buf_size = ABUF_SIZE*2;
2520 else {
2521 char *old_buf = buf;
2522 buf = new char[buf_size*2];
2523 memcpy(buf, old_buf, buf_size);
2524 buf_size *= 2;
2525 a_delete old_buf;
2528 if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
2529 break;
2530 i++;
2531 tok.next();
2533 if (i == 0) {
2534 empty_name_warning(required);
2535 return NULL_SYMBOL;
2537 if (end && buf[i] == end)
2538 buf[i+1] = '\0';
2539 else
2540 non_empty_name_warning();
2541 if (buf == abuf)
2542 return symbol(buf);
2543 else {
2544 symbol s(buf);
2545 a_delete buf;
2546 return s;
2550 void exit_troff()
2552 exit_started = 1;
2553 topdiv->set_last_page();
2554 if (!end_macro_name.is_null()) {
2555 spring_trap(end_macro_name);
2556 tok.next();
2557 process_input_stack();
2559 curenv->final_break();
2560 tok.next();
2561 process_input_stack();
2562 end_diversions();
2563 if (topdiv->get_page_length() > 0) {
2564 done_end_macro = 1;
2565 topdiv->set_ejecting();
2566 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2567 input_stack::push(make_temp_iterator((char *)buf));
2568 topdiv->space(topdiv->get_page_length(), 1);
2569 tok.next();
2570 process_input_stack();
2571 seen_last_page_ejector = 1; // should be set already
2572 topdiv->set_ejecting();
2573 push_page_ejector();
2574 topdiv->space(topdiv->get_page_length(), 1);
2575 tok.next();
2576 process_input_stack();
2578 // This will only happen if a trap-invoked macro starts a diversion,
2579 // or if vertical position traps have been disabled.
2580 cleanup_and_exit(0);
2583 // This implements .ex. The input stack must be cleared before calling
2584 // exit_troff().
2586 void exit_request()
2588 input_stack::clear();
2589 if (exit_started)
2590 tok.next();
2591 else
2592 exit_troff();
2595 void return_macro_request()
2597 if (has_arg() && tok.ch())
2598 input_stack::pop_macro();
2599 input_stack::pop_macro();
2600 tok.next();
2603 void end_macro()
2605 end_macro_name = get_name();
2606 skip_line();
2609 void blank_line_macro()
2611 blank_line_macro_name = get_name();
2612 skip_line();
2615 static void trapping_blank_line()
2617 if (!blank_line_macro_name.is_null())
2618 spring_trap(blank_line_macro_name);
2619 else
2620 blank_line();
2623 void do_request()
2625 int old_compatible_flag = compatible_flag;
2626 compatible_flag = 0;
2627 symbol nm = get_name();
2628 if (nm.is_null())
2629 skip_line();
2630 else
2631 interpolate_macro(nm, 1);
2632 compatible_flag = old_compatible_flag;
2633 request_or_macro *p = lookup_request(nm);
2634 macro *m = p->to_macro();
2635 if (m)
2636 tok.next();
2639 inline int possibly_handle_first_page_transition()
2641 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2642 handle_first_page_transition();
2643 return 1;
2645 else
2646 return 0;
2649 static int transparent_translate(int cc)
2651 if (!invalid_input_char(cc)) {
2652 charinfo *ci = charset_table[cc];
2653 switch (ci->get_special_translation(1)) {
2654 case charinfo::TRANSLATE_SPACE:
2655 return ' ';
2656 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2657 return ESCAPE_TILDE;
2658 case charinfo::TRANSLATE_DUMMY:
2659 return ESCAPE_AMPERSAND;
2660 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2661 return ESCAPE_PERCENT;
2663 // This is really ugly.
2664 ci = ci->get_translation(1);
2665 if (ci) {
2666 int c = ci->get_ascii_code();
2667 if (c != '\0')
2668 return c;
2669 error("can't translate %1 to special character `%2'"
2670 " in transparent throughput",
2671 input_char_description(cc),
2672 ci->nm.contents());
2675 return cc;
2678 class int_stack {
2679 struct int_stack_element {
2680 int n;
2681 int_stack_element *next;
2682 } *top;
2683 public:
2684 int_stack();
2685 ~int_stack();
2686 void push(int);
2687 int is_empty();
2688 int pop();
2691 int_stack::int_stack()
2693 top = 0;
2696 int_stack::~int_stack()
2698 while (top != 0) {
2699 int_stack_element *temp = top;
2700 top = top->next;
2701 delete temp;
2705 int int_stack::is_empty()
2707 return top == 0;
2710 void int_stack::push(int n)
2712 int_stack_element *p = new int_stack_element;
2713 p->next = top;
2714 p->n = n;
2715 top = p;
2718 int int_stack::pop()
2720 assert(top != 0);
2721 int_stack_element *p = top;
2722 top = top->next;
2723 int n = p->n;
2724 delete p;
2725 return n;
2728 int node::reread(int *)
2730 return 0;
2733 int global_diverted_space = 0;
2735 int diverted_space_node::reread(int *bolp)
2737 global_diverted_space = 1;
2738 if (curenv->get_fill())
2739 trapping_blank_line();
2740 else
2741 curdiv->space(n);
2742 global_diverted_space = 0;
2743 *bolp = 1;
2744 return 1;
2747 int diverted_copy_file_node::reread(int *bolp)
2749 curdiv->copy_file(filename.contents());
2750 *bolp = 1;
2751 return 1;
2754 int word_space_node::reread(int *)
2756 if (unformat) {
2757 for (width_list *w = orig_width; w; w = w->next)
2758 curenv->space(w->width, w->sentence_width);
2759 unformat = 0;
2760 return 1;
2762 return 0;
2765 int unbreakable_space_node::reread(int *)
2767 return 0;
2770 int hmotion_node::reread(int *)
2772 if (unformat && was_tab) {
2773 curenv->handle_tab(0);
2774 unformat = 0;
2775 return 1;
2777 return 0;
2780 void process_input_stack()
2782 int_stack trap_bol_stack;
2783 int bol = 1;
2784 for (;;) {
2785 int suppress_next = 0;
2786 switch (tok.type) {
2787 case token::TOKEN_CHAR:
2789 unsigned char ch = tok.c;
2790 if (bol && !have_input
2791 && (ch == curenv->control_char
2792 || ch == curenv->no_break_control_char)) {
2793 break_flag = ch == curenv->control_char;
2794 // skip tabs as well as spaces here
2795 do {
2796 tok.next();
2797 } while (tok.white_space());
2798 symbol nm = get_name();
2799 #if defined(DEBUGGING)
2800 if (debug_state) {
2801 if (! nm.is_null()) {
2802 if (strcmp(nm.contents(), "test") == 0) {
2803 fprintf(stderr, "found it!\n");
2804 fflush(stderr);
2806 fprintf(stderr, "interpreting [%s]", nm.contents());
2807 if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
2808 fprintf(stderr, " currently in diversion: %s",
2809 curdiv->get_diversion_name());
2810 fprintf(stderr, "\n");
2811 fflush(stderr);
2814 #endif
2815 if (nm.is_null())
2816 skip_line();
2817 else {
2818 interpolate_macro(nm);
2819 #if defined(DEBUGGING)
2820 if (debug_state) {
2821 fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
2822 curenv->dump_troff_state();
2824 #endif
2826 suppress_next = 1;
2828 else {
2829 if (possibly_handle_first_page_transition())
2831 else {
2832 for (;;) {
2833 #if defined(DEBUGGING)
2834 if (debug_state) {
2835 fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
2837 #endif
2838 curenv->add_char(charset_table[ch]);
2839 tok.next();
2840 if (tok.type != token::TOKEN_CHAR)
2841 break;
2842 ch = tok.c;
2844 suppress_next = 1;
2845 bol = 0;
2848 break;
2850 case token::TOKEN_TRANSPARENT:
2852 if (bol) {
2853 if (possibly_handle_first_page_transition())
2855 else {
2856 int cc;
2857 do {
2858 node *n;
2859 cc = get_copy(&n);
2860 if (cc != EOF) {
2861 if (cc != '\0')
2862 curdiv->transparent_output(transparent_translate(cc));
2863 else
2864 curdiv->transparent_output(n);
2866 } while (cc != '\n' && cc != EOF);
2867 if (cc == EOF)
2868 curdiv->transparent_output('\n');
2871 break;
2873 case token::TOKEN_NEWLINE:
2875 if (bol && !old_have_input
2876 && !curenv->get_prev_line_interrupted())
2877 trapping_blank_line();
2878 else {
2879 curenv->newline();
2880 bol = 1;
2882 break;
2884 case token::TOKEN_REQUEST:
2886 int request_code = tok.c;
2887 tok.next();
2888 switch (request_code) {
2889 case TITLE_REQUEST:
2890 title();
2891 break;
2892 case COPY_FILE_REQUEST:
2893 copy_file();
2894 break;
2895 case TRANSPARENT_FILE_REQUEST:
2896 transparent_file();
2897 break;
2898 #ifdef COLUMN
2899 case VJUSTIFY_REQUEST:
2900 vjustify();
2901 break;
2902 #endif /* COLUMN */
2903 default:
2904 assert(0);
2905 break;
2907 suppress_next = 1;
2908 break;
2910 case token::TOKEN_SPACE:
2912 if (possibly_handle_first_page_transition())
2914 else if (bol && !curenv->get_prev_line_interrupted()) {
2915 int nspaces = 0;
2916 // save space_width now so that it isn't changed by \f or \s
2917 // which we wouldn't notice here
2918 hunits space_width = curenv->get_space_width();
2919 do {
2920 nspaces += tok.nspaces();
2921 tok.next();
2922 } while (tok.space());
2923 if (tok.newline())
2924 trapping_blank_line();
2925 else {
2926 push_token(tok);
2927 curenv->do_break();
2928 curenv->add_node(new hmotion_node(space_width * nspaces,
2929 curenv->get_fill_color()));
2930 bol = 0;
2933 else {
2934 curenv->space();
2935 bol = 0;
2937 break;
2939 case token::TOKEN_EOF:
2940 return;
2941 case token::TOKEN_NODE:
2943 if (possibly_handle_first_page_transition())
2945 else if (tok.nd->reread(&bol)) {
2946 delete tok.nd;
2947 tok.nd = 0;
2949 else {
2950 curenv->add_node(tok.nd);
2951 tok.nd = 0;
2952 bol = 0;
2953 curenv->possibly_break_line(1);
2955 break;
2957 case token::TOKEN_PAGE_EJECTOR:
2959 continue_page_eject();
2960 // I think we just want to preserve bol.
2961 // bol = 1;
2962 break;
2964 case token::TOKEN_BEGIN_TRAP:
2966 trap_bol_stack.push(bol);
2967 bol = 1;
2968 have_input = 0;
2969 break;
2971 case token::TOKEN_END_TRAP:
2973 if (trap_bol_stack.is_empty())
2974 error("spurious end trap token detected!");
2975 else
2976 bol = trap_bol_stack.pop();
2977 have_input = 0;
2979 /* I'm not totally happy about this. But I can't think of any other
2980 way to do it. Doing an output_pending_lines() whenever a
2981 TOKEN_END_TRAP is detected doesn't work: for example,
2983 .wh -1i x
2984 .de x
2987 .wh -.5i y
2988 .de y
2989 .tl ''-%-''
2992 .ll .5i
2993 .sp |\n(.pu-1i-.5v
2994 a\%very\%very\%long\%word
2996 will print all but the first lines from the word immediately
2997 after the footer, rather than on the next page. */
2999 if (trap_bol_stack.is_empty())
3000 curenv->output_pending_lines();
3001 break;
3003 default:
3005 bol = 0;
3006 tok.process();
3007 break;
3010 if (!suppress_next)
3011 tok.next();
3012 trap_sprung_flag = 0;
3016 #ifdef WIDOW_CONTROL
3018 void flush_pending_lines()
3020 while (!tok.newline() && !tok.eof())
3021 tok.next();
3022 curenv->output_pending_lines();
3023 tok.next();
3026 #endif /* WIDOW_CONTROL */
3028 request_or_macro::request_or_macro()
3032 macro *request_or_macro::to_macro()
3034 return 0;
3037 request::request(REQUEST_FUNCP pp) : p(pp)
3041 void request::invoke(symbol, int)
3043 (*p)();
3046 class char_block
3048 public:
3049 enum { SIZE = 128 };
3050 unsigned char s[SIZE];
3051 char_block *next;
3053 char_block();
3056 char_block::char_block()
3057 : next(0)
3061 class char_list
3063 friend class macro_header;
3064 friend class string_iterator;
3066 unsigned char *ptr;
3067 int len;
3068 char_block *head;
3069 char_block *tail;
3071 public:
3072 char_list();
3073 ~char_list();
3074 void append(unsigned char);
3075 void set(unsigned char, int);
3076 unsigned char get(int);
3077 int length();
3080 char_list::char_list()
3081 : ptr(0), len(0), head(0), tail(0)
3085 char_list::~char_list()
3087 while (head != 0) {
3088 char_block *tem = head;
3089 head = head->next;
3090 delete tem;
3094 int char_list::length()
3096 return len;
3099 void char_list::append(unsigned char c)
3101 if (tail == 0) {
3102 head = tail = new char_block;
3103 ptr = tail->s;
3105 else {
3106 if (ptr >= tail->s + char_block::SIZE) {
3107 tail->next = new char_block;
3108 tail = tail->next;
3109 ptr = tail->s;
3112 *ptr++ = c;
3113 len++;
3116 void char_list::set(unsigned char c, int offset)
3118 assert(len > offset);
3119 // optimization for access at the end
3120 int boundary = len - len % char_block::SIZE;
3121 if (offset >= boundary) {
3122 *(tail->s + offset - boundary) = c;
3123 return;
3125 char_block *tem = head;
3126 int l = 0;
3127 for (;;) {
3128 l += char_block::SIZE;
3129 if (l > offset) {
3130 *(tem->s + offset % char_block::SIZE) = c;
3131 return;
3133 tem = tem->next;
3137 unsigned char char_list::get(int offset)
3139 assert(len > offset);
3140 // optimization for access at the end
3141 int boundary = len - len % char_block::SIZE;
3142 if (offset >= boundary)
3143 return *(tail->s + offset - boundary);
3144 char_block *tem = head;
3145 int l = 0;
3146 for (;;) {
3147 l += char_block::SIZE;
3148 if (l > offset)
3149 return *(tem->s + offset % char_block::SIZE);
3150 tem = tem->next;
3154 class node_list
3156 friend class macro_header;
3157 friend class string_iterator;
3159 node *head;
3160 node *tail;
3162 public:
3163 node_list();
3164 ~node_list();
3165 void append(node *);
3166 int length();
3167 node *extract();
3170 void node_list::append(node *n)
3172 if (head == 0) {
3173 n->next = 0;
3174 head = tail = n;
3176 else {
3177 n->next = 0;
3178 tail = tail->next = n;
3182 int node_list::length()
3184 int total = 0;
3185 for (node *n = head; n != 0; n = n->next)
3186 ++total;
3187 return total;
3190 node_list::node_list()
3192 head = tail = 0;
3195 node *node_list::extract()
3197 node *temp = head;
3198 head = tail = 0;
3199 return temp;
3202 node_list::~node_list()
3204 delete_node_list(head);
3207 class macro_header
3209 public:
3210 int count;
3211 char_list cl;
3212 node_list nl;
3214 macro_header() { count = 1; }
3215 macro_header *copy(int);
3218 macro::~macro()
3220 if (p != 0 && --(p->count) <= 0)
3221 delete p;
3224 macro::macro()
3225 : is_a_diversion(0), is_a_string(1)
3227 if (!input_stack::get_location(1, &filename, &lineno)) {
3228 filename = 0;
3229 lineno = 0;
3231 len = 0;
3232 empty_macro = 1;
3233 p = 0;
3236 macro::macro(const macro &m)
3237 : request_or_macro(), filename(m.filename), lineno(m.lineno), len(m.len),
3238 empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion),
3239 is_a_string(m.is_a_string), p(m.p)
3241 if (p != 0)
3242 p->count++;
3245 macro::macro(int is_div)
3246 : is_a_diversion(is_div)
3248 if (!input_stack::get_location(1, &filename, &lineno)) {
3249 filename = 0;
3250 lineno = 0;
3252 len = 0;
3253 empty_macro = 1;
3254 is_a_string = 1;
3255 p = 0;
3258 int macro::is_diversion()
3260 return is_a_diversion;
3263 int macro::is_string()
3265 return is_a_string;
3268 void macro::clear_string_flag()
3270 is_a_string = 0;
3273 macro &macro::operator=(const macro &m)
3275 // don't assign object
3276 if (m.p != 0)
3277 m.p->count++;
3278 if (p != 0 && --(p->count) <= 0)
3279 delete p;
3280 p = m.p;
3281 filename = m.filename;
3282 lineno = m.lineno;
3283 len = m.len;
3284 empty_macro = m.empty_macro;
3285 is_a_diversion = m.is_a_diversion;
3286 is_a_string = m.is_a_string;
3287 return *this;
3290 void macro::append(unsigned char c)
3292 assert(c != 0);
3293 if (p == 0)
3294 p = new macro_header;
3295 if (p->cl.length() != len) {
3296 macro_header *tem = p->copy(len);
3297 if (--(p->count) <= 0)
3298 delete p;
3299 p = tem;
3301 p->cl.append(c);
3302 ++len;
3303 if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
3304 empty_macro = 0;
3307 void macro::set(unsigned char c, int offset)
3309 assert(p != 0);
3310 assert(c != 0);
3311 p->cl.set(c, offset);
3314 unsigned char macro::get(int offset)
3316 assert(p != 0);
3317 return p->cl.get(offset);
3320 int macro::length()
3322 return len;
3325 void macro::append_str(const char *s)
3327 int i = 0;
3329 if (s) {
3330 while (s[i] != (char)0) {
3331 append(s[i]);
3332 i++;
3337 void macro::append(node *n)
3339 assert(n != 0);
3340 if (p == 0)
3341 p = new macro_header;
3342 if (p->cl.length() != len) {
3343 macro_header *tem = p->copy(len);
3344 if (--(p->count) <= 0)
3345 delete p;
3346 p = tem;
3348 p->cl.append(0);
3349 p->nl.append(n);
3350 ++len;
3351 empty_macro = 0;
3354 void macro::append_unsigned(unsigned int i)
3356 unsigned int j = i / 10;
3357 if (j != 0)
3358 append_unsigned(j);
3359 append(((unsigned char)(((int)'0') + i % 10)));
3362 void macro::append_int(int i)
3364 if (i < 0) {
3365 append('-');
3366 i = -i;
3368 append_unsigned((unsigned int)i);
3371 void macro::print_size()
3373 errprint("%1", len);
3376 // make a copy of the first n bytes
3378 macro_header *macro_header::copy(int n)
3380 macro_header *p = new macro_header;
3381 char_block *bp = cl.head;
3382 unsigned char *ptr = bp->s;
3383 node *nd = nl.head;
3384 while (--n >= 0) {
3385 if (ptr >= bp->s + char_block::SIZE) {
3386 bp = bp->next;
3387 ptr = bp->s;
3389 unsigned char c = *ptr++;
3390 p->cl.append(c);
3391 if (c == 0) {
3392 p->nl.append(nd->copy());
3393 nd = nd->next;
3396 return p;
3399 void print_macros()
3401 object_dictionary_iterator iter(request_dictionary);
3402 request_or_macro *rm;
3403 symbol s;
3404 while (iter.get(&s, (object **)&rm)) {
3405 assert(!s.is_null());
3406 macro *m = rm->to_macro();
3407 if (m) {
3408 errprint("%1\t", s.contents());
3409 m->print_size();
3410 errprint("\n");
3413 fflush(stderr);
3414 skip_line();
3417 class string_iterator
3418 : public input_iterator
3420 macro mac;
3421 const char *how_invoked;
3422 int newline_flag;
3423 int lineno;
3424 char_block *bp;
3425 int count; // of characters remaining
3426 node *nd;
3427 int saved_compatible_flag;
3428 int with_break; // inherited from the caller
3430 protected:
3431 symbol nm;
3433 string_iterator();
3435 public:
3436 string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
3437 int fill(node **);
3438 int peek();
3439 int get_location(int, const char **, int *);
3440 void backtrace();
3441 int get_break_flag() { return with_break; }
3442 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3443 int get_compatible_flag() { return saved_compatible_flag; }
3444 int is_diversion();
3447 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3448 : input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
3449 lineno(1), nm(s)
3451 count = mac.len;
3452 if (count != 0) {
3453 bp = mac.p->cl.head;
3454 nd = mac.p->nl.head;
3455 ptr = eptr = bp->s;
3457 else {
3458 bp = 0;
3459 nd = 0;
3460 ptr = eptr = 0;
3462 with_break = input_stack::get_break_flag();
3465 string_iterator::string_iterator()
3467 bp = 0;
3468 nd = 0;
3469 ptr = eptr = 0;
3470 newline_flag = 0;
3471 how_invoked = 0;
3472 lineno = 1;
3473 count = 0;
3474 with_break = input_stack::get_break_flag();
3477 int string_iterator::is_diversion()
3479 return mac.is_diversion();
3482 int string_iterator::fill(node **np)
3484 if (newline_flag)
3485 lineno++;
3486 newline_flag = 0;
3487 if (count <= 0)
3488 return EOF;
3489 const unsigned char *p = eptr;
3490 if (p >= bp->s + char_block::SIZE) {
3491 bp = bp->next;
3492 p = bp->s;
3494 if (*p == '\0') {
3495 if (np) {
3496 *np = nd->copy();
3497 if (is_diversion())
3498 (*np)->div_nest_level = input_stack::get_div_level();
3499 else
3500 (*np)->div_nest_level = 0;
3502 nd = nd->next;
3503 eptr = ptr = p + 1;
3504 count--;
3505 return 0;
3507 const unsigned char *e = bp->s + char_block::SIZE;
3508 if (e - p > count)
3509 e = p + count;
3510 ptr = p;
3511 while (p < e) {
3512 unsigned char c = *p;
3513 if (c == '\n' || c == ESCAPE_NEWLINE) {
3514 newline_flag = 1;
3515 p++;
3516 break;
3518 if (c == '\0')
3519 break;
3520 p++;
3522 eptr = p;
3523 count -= p - ptr;
3524 return *ptr++;
3527 int string_iterator::peek()
3529 if (count <= 0)
3530 return EOF;
3531 const unsigned char *p = eptr;
3532 if (p >= bp->s + char_block::SIZE) {
3533 p = bp->next->s;
3535 return *p;
3538 int string_iterator::get_location(int allow_macro,
3539 const char **filep, int *linep)
3541 if (!allow_macro)
3542 return 0;
3543 if (mac.filename == 0)
3544 return 0;
3545 *filep = mac.filename;
3546 *linep = mac.lineno + lineno - 1;
3547 return 1;
3550 void string_iterator::backtrace()
3552 if (mac.filename) {
3553 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3554 if (how_invoked) {
3555 if (!nm.is_null())
3556 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3557 else
3558 errprint(": %1\n", how_invoked);
3560 else
3561 errprint("\n");
3565 class temp_iterator
3566 : public input_iterator
3568 friend input_iterator *make_temp_iterator(const char *);
3570 unsigned char *base;
3572 temp_iterator(const char *, int len);
3573 public:
3574 ~temp_iterator();
3577 inline
3578 temp_iterator::temp_iterator(const char *s, int len)
3580 base = new unsigned char[len];
3581 memcpy(base, s, len);
3582 ptr = base;
3583 eptr = base + len;
3586 temp_iterator::~temp_iterator()
3588 a_delete base;
3591 class small_temp_iterator
3592 : public input_iterator
3594 friend input_iterator *make_temp_iterator(const char *);
3596 static small_temp_iterator *free_list;
3598 enum { BLOCK = 16 };
3599 enum { SIZE = 12 };
3600 unsigned char buf[SIZE];
3602 private: // TODO class all private; ok (has friend: public static? !!), but
3603 small_temp_iterator(const char *, int);
3604 ~small_temp_iterator();
3606 void *operator new(size_t);
3607 void operator delete(void *);
3610 small_temp_iterator *small_temp_iterator::free_list = 0;
3612 void *small_temp_iterator::operator new(size_t n) // TODO -> object cache
3614 assert(n == sizeof(small_temp_iterator));
3615 if (!free_list) {
3616 free_list =
3617 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3618 for (int i = 0; i < BLOCK - 1; i++)
3619 free_list[i].next = free_list + i + 1;
3620 free_list[BLOCK-1].next = 0;
3622 small_temp_iterator *p = free_list;
3623 free_list = (small_temp_iterator *)(free_list->next);
3624 p->next = 0;
3625 return p;
3628 inline
3629 void small_temp_iterator::operator delete(void *p) // TODO -> nope
3631 if (p) {
3632 ((small_temp_iterator *)p)->next = free_list;
3633 free_list = (small_temp_iterator *)p;
3637 small_temp_iterator::~small_temp_iterator()
3641 inline
3642 small_temp_iterator::small_temp_iterator(const char *s, int len)
3644 for (int i = 0; i < len; i++)
3645 buf[i] = s[i];
3646 ptr = buf;
3647 eptr = buf + len;
3650 input_iterator *make_temp_iterator(const char *s)
3652 if (s == 0)
3653 return new small_temp_iterator(s, 0);
3654 else {
3655 int n = strlen(s);
3656 if (n <= small_temp_iterator::SIZE)
3657 return new small_temp_iterator(s, n);
3658 else
3659 return new temp_iterator(s, n);
3663 // this is used when macros with arguments are interpolated
3665 class arg_list
3667 public:
3668 macro mac;
3669 int space_follows;
3670 arg_list *next;
3672 arg_list(const macro &, int);
3673 arg_list(const arg_list *);
3674 ~arg_list();
3677 arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
3681 arg_list::arg_list(const arg_list *al)
3682 : next(0)
3684 mac = al->mac;
3685 space_follows = al->space_follows;
3686 arg_list **a = &next;
3687 arg_list *p = al->next;
3688 while (p) {
3689 *a = new arg_list(p->mac, p->space_follows);
3690 p = p->next;
3691 a = &(*a)->next;
3695 arg_list::~arg_list()
3699 class macro_iterator
3700 : public string_iterator
3702 arg_list *args;
3703 int argc;
3704 int with_break; // whether called as .foo or 'foo
3706 public:
3707 macro_iterator(symbol, macro &, const char * = "macro", int = 0);
3708 macro_iterator();
3709 ~macro_iterator();
3710 int has_args() { return 1; }
3711 input_iterator *get_arg(int);
3712 arg_list *get_arg_list();
3713 symbol get_macro_name();
3714 int space_follows_arg(int);
3715 int get_break_flag() { return with_break; }
3716 int nargs() { return argc; }
3717 void add_arg(const macro &, int);
3718 void shift(int);
3719 int is_macro() { return 1; }
3720 int is_diversion();
3723 input_iterator *macro_iterator::get_arg(int i)
3725 if (i == 0)
3726 return make_temp_iterator(nm.contents());
3727 if (i > 0 && i <= argc) {
3728 arg_list *p = args;
3729 for (int j = 1; j < i; j++) {
3730 assert(p != 0);
3731 p = p->next;
3733 return new string_iterator(p->mac);
3735 else
3736 return 0;
3739 arg_list *macro_iterator::get_arg_list()
3741 return args;
3744 symbol macro_iterator::get_macro_name()
3746 return nm;
3749 int macro_iterator::space_follows_arg(int i)
3751 if (i > 0 && i <= argc) {
3752 arg_list *p = args;
3753 for (int j = 1; j < i; j++) {
3754 assert(p != 0);
3755 p = p->next;
3757 return p->space_follows;
3759 else
3760 return 0;
3763 void macro_iterator::add_arg(const macro &m, int s)
3765 arg_list **p;
3766 for (p = &args; *p; p = &((*p)->next))
3768 *p = new arg_list(m, s);
3769 ++argc;
3772 void macro_iterator::shift(int n)
3774 while (n > 0 && argc > 0) {
3775 arg_list *tem = args;
3776 args = args->next;
3777 delete tem;
3778 --argc;
3779 --n;
3783 // This gets used by eg .if '\?xxx\?''.
3784 int operator==(const macro &m1, const macro &m2)
3786 if (m1.len != m2.len)
3787 return 0;
3788 string_iterator iter1(m1);
3789 string_iterator iter2(m2);
3790 int n = m1.len;
3791 while (--n >= 0) {
3792 node *nd1 = 0;
3793 int c1 = iter1.get(&nd1);
3794 assert(c1 != EOF);
3795 node *nd2 = 0;
3796 int c2 = iter2.get(&nd2);
3797 assert(c2 != EOF);
3798 if (c1 != c2) {
3799 if (c1 == 0)
3800 delete nd1;
3801 else if (c2 == 0)
3802 delete nd2;
3803 return 0;
3805 if (c1 == 0) {
3806 assert(nd1 != 0);
3807 assert(nd2 != 0);
3808 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3809 delete nd1;
3810 delete nd2;
3811 if (!are_same)
3812 return 0;
3815 return 1;
3818 static void interpolate_macro(symbol nm, int no_next)
3820 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3821 if (p == 0) {
3822 int warned = 0;
3823 const char *s = nm.contents();
3824 if (strlen(s) > 2) {
3825 request_or_macro *r;
3826 char buf[3];
3827 buf[0] = s[0];
3828 buf[1] = s[1];
3829 buf[2] = '\0';
3830 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3831 if (r) {
3832 macro *m = r->to_macro();
3833 if (!m || !m->empty())
3834 warned = warning(WARN_SPACE,
3835 "macro `%1' not defined "
3836 "(possibly missing space after `%2')",
3837 nm.contents(), buf);
3840 if (!warned) {
3841 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
3842 p = new macro;
3843 request_dictionary.define(nm, p);
3846 if (p)
3847 p->invoke(nm, no_next);
3848 else {
3849 skip_line();
3850 return;
3854 static void decode_args(macro_iterator *mi)
3856 if (!tok.newline() && !tok.eof()) {
3857 node *n;
3858 int c = get_copy(&n);
3859 for (;;) {
3860 while (c == ' ')
3861 c = get_copy(&n);
3862 if (c == '\n' || c == EOF)
3863 break;
3864 macro arg;
3865 int quote_input_level = 0;
3866 int done_tab_warning = 0;
3867 arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
3868 // we store discarded double quotes for \$^
3869 if (c == '"') {
3870 arg.append(DOUBLE_QUOTE);
3871 quote_input_level = input_stack::get_level();
3872 c = get_copy(&n);
3874 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3875 if (quote_input_level > 0 && c == '"'
3876 && (compatible_flag
3877 || input_stack::get_level() == quote_input_level)) {
3878 arg.append(DOUBLE_QUOTE);
3879 c = get_copy(&n);
3880 if (c == '"') {
3881 arg.append(c);
3882 c = get_copy(&n);
3884 else
3885 break;
3887 else {
3888 if (c == 0)
3889 arg.append(n);
3890 else {
3891 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3892 warning(WARN_TAB, "tab character in unquoted macro argument");
3893 done_tab_warning = 1;
3895 arg.append(c);
3897 c = get_copy(&n);
3900 arg.append(POP_GROFFCOMP_MODE);
3901 mi->add_arg(arg, (c == ' '));
3906 static void decode_string_args(macro_iterator *mi)
3908 node *n;
3909 int c = get_copy(&n);
3910 for (;;) {
3911 while (c == ' ')
3912 c = get_copy(&n);
3913 if (c == '\n' || c == EOF) {
3914 error("missing `]'");
3915 break;
3917 if (c == ']')
3918 break;
3919 macro arg;
3920 int quote_input_level = 0;
3921 int done_tab_warning = 0;
3922 if (c == '"') {
3923 quote_input_level = input_stack::get_level();
3924 c = get_copy(&n);
3926 while (c != EOF && c != '\n'
3927 && !(c == ']' && quote_input_level == 0)
3928 && !(c == ' ' && quote_input_level == 0)) {
3929 if (quote_input_level > 0 && c == '"'
3930 && input_stack::get_level() == quote_input_level) {
3931 c = get_copy(&n);
3932 if (c == '"') {
3933 arg.append(c);
3934 c = get_copy(&n);
3936 else
3937 break;
3939 else {
3940 if (c == 0)
3941 arg.append(n);
3942 else {
3943 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3944 warning(WARN_TAB, "tab character in unquoted string argument");
3945 done_tab_warning = 1;
3947 arg.append(c);
3949 c = get_copy(&n);
3952 mi->add_arg(arg, (c == ' '));
3956 void macro::invoke(symbol nm, int no_next)
3958 macro_iterator *mi = new macro_iterator(nm, *this);
3959 decode_args(mi);
3960 input_stack::push(mi);
3961 // we must delay tok.next() in case the function has been called by
3962 // do_request to assure proper handling of compatible_flag
3963 if (!no_next)
3964 tok.next();
3967 macro *macro::to_macro()
3969 return this;
3972 int macro::empty()
3974 return empty_macro == 1;
3977 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called,
3978 int init_args)
3979 : string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
3981 if (init_args) {
3982 arg_list *al = input_stack::get_arg_list();
3983 if (al) {
3984 args = new arg_list(al);
3985 argc = input_stack::nargs();
3990 macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
3994 macro_iterator::~macro_iterator()
3996 while (args != 0) {
3997 arg_list *tem = args;
3998 args = args->next;
3999 delete tem;
4003 dictionary composite_dictionary(17);
4005 void composite_request()
4007 symbol from = get_name(1);
4008 if (!from.is_null()) {
4009 const char *from_gn = glyph_name_to_unicode(from.contents());
4010 if (!from_gn) {
4011 from_gn = check_unicode_name(from.contents());
4012 if (!from_gn) {
4013 error("invalid composite glyph name `%1'", from.contents());
4014 skip_line();
4015 return;
4018 const char *from_decomposed = decompose_unicode(from_gn);
4019 if (from_decomposed)
4020 from_gn = &from_decomposed[1];
4021 symbol to = get_name(1);
4022 if (to.is_null())
4023 composite_dictionary.remove(symbol(from_gn));
4024 else {
4025 const char *to_gn = glyph_name_to_unicode(to.contents());
4026 if (!to_gn) {
4027 to_gn = check_unicode_name(to.contents());
4028 if (!to_gn) {
4029 error("invalid composite glyph name `%1'", to.contents());
4030 skip_line();
4031 return;
4034 const char *to_decomposed = decompose_unicode(to_gn);
4035 if (to_decomposed)
4036 to_gn = &to_decomposed[1];
4037 if (strcmp(from_gn, to_gn) == 0)
4038 composite_dictionary.remove(symbol(from_gn));
4039 else
4040 (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
4043 skip_line();
4046 static symbol composite_glyph_name(symbol nm)
4048 macro_iterator *mi = new macro_iterator();
4049 decode_string_args(mi);
4050 input_stack::push(mi);
4051 const char *gn = glyph_name_to_unicode(nm.contents());
4052 if (!gn) {
4053 gn = check_unicode_name(nm.contents());
4054 if (!gn) {
4055 error("invalid base glyph `%1' in composite glyph name", nm.contents());
4056 return EMPTY_SYMBOL;
4059 const char *gn_decomposed = decompose_unicode(gn);
4060 string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
4061 string gl;
4062 int n = input_stack::nargs();
4063 for (int i = 1; i <= n; i++) {
4064 glyph_name += '_';
4065 input_iterator *p = input_stack::get_arg(i);
4066 gl.clear();
4067 int c;
4068 while ((c = p->get(0)) != EOF)
4069 if (c != DOUBLE_QUOTE)
4070 gl += c;
4071 gl += '\0';
4072 const char *u = glyph_name_to_unicode(gl.contents());
4073 if (!u) {
4074 u = check_unicode_name(gl.contents());
4075 if (!u) {
4076 error("invalid component `%1' in composite glyph name",
4077 gl.contents());
4078 return EMPTY_SYMBOL;
4081 const char *decomposed = decompose_unicode(u);
4082 if (decomposed)
4083 u = &decomposed[1];
4084 void *mapped_composite = composite_dictionary.lookup(symbol(u));
4085 if (mapped_composite)
4086 u = (const char *)mapped_composite;
4087 glyph_name += u;
4089 glyph_name += '\0';
4090 const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
4091 if (groff_gn)
4092 return symbol(groff_gn);
4093 gl.clear();
4094 gl += 'u';
4095 gl += glyph_name;
4096 return symbol(gl.contents());
4099 int trap_sprung_flag = 0;
4100 int postpone_traps_flag = 0;
4101 symbol postponed_trap;
4103 void spring_trap(symbol nm)
4105 assert(!nm.is_null());
4106 trap_sprung_flag = 1;
4107 if (postpone_traps_flag) {
4108 postponed_trap = nm;
4109 return;
4111 static char buf[2] = { BEGIN_TRAP, 0 };
4112 static char buf2[2] = { END_TRAP, '\0' };
4113 input_stack::push(make_temp_iterator(buf2));
4114 request_or_macro *p = lookup_request(nm);
4115 macro *m = p->to_macro();
4116 if (m)
4117 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
4118 else
4119 error("you can't invoke a request with a trap");
4120 input_stack::push(make_temp_iterator(buf));
4123 void postpone_traps()
4125 postpone_traps_flag = 1;
4128 int unpostpone_traps()
4130 postpone_traps_flag = 0;
4131 if (!postponed_trap.is_null()) {
4132 spring_trap(postponed_trap);
4133 postponed_trap = NULL_SYMBOL;
4134 return 1;
4136 else
4137 return 0;
4140 void read_request()
4142 macro_iterator *mi = new macro_iterator;
4143 int reading_from_terminal = isatty(fileno(stdin));
4144 int had_prompt = 0;
4145 if (!tok.newline() && !tok.eof()) {
4146 int c = get_copy(0);
4147 while (c == ' ')
4148 c = get_copy(0);
4149 while (c != EOF && c != '\n' && c != ' ') {
4150 if (!invalid_input_char(c)) {
4151 if (reading_from_terminal)
4152 fputc(c, stderr);
4153 had_prompt = 1;
4155 c = get_copy(0);
4157 if (c == ' ') {
4158 tok.make_space();
4159 decode_args(mi);
4162 if (reading_from_terminal) {
4163 fputc(had_prompt ? ':' : '\a', stderr);
4164 fflush(stderr);
4166 input_stack::push(mi);
4167 macro mac;
4168 int nl = 0;
4169 int c;
4170 while ((c = getchar()) != EOF) {
4171 if (invalid_input_char(c))
4172 warning(WARN_INPUT, "invalid input character code %1", int(c));
4173 else {
4174 if (c == '\n') {
4175 if (nl)
4176 break;
4177 else
4178 nl = 1;
4180 else
4181 nl = 0;
4182 mac.append(c);
4185 if (reading_from_terminal)
4186 clearerr(stdin);
4187 input_stack::push(new string_iterator(mac));
4188 tok.next();
4191 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
4192 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
4193 enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
4195 void do_define_string(define_mode mode, comp_mode comp)
4197 symbol nm;
4198 node *n = 0; // pacify compiler
4199 int c;
4200 nm = get_name(1);
4201 if (nm.is_null()) {
4202 skip_line();
4203 return;
4205 if (tok.newline())
4206 c = '\n';
4207 else if (tok.tab())
4208 c = '\t';
4209 else if (!tok.space()) {
4210 error("bad string definition");
4211 skip_line();
4212 return;
4214 else
4215 c = get_copy(&n);
4216 while (c == ' ')
4217 c = get_copy(&n);
4218 if (c == '"')
4219 c = get_copy(&n);
4220 macro mac;
4221 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
4222 macro *mm = rm ? rm->to_macro() : 0;
4223 if (mode == DEFINE_APPEND && mm)
4224 mac = *mm;
4225 if (comp == COMP_DISABLE)
4226 mac.append(PUSH_GROFF_MODE);
4227 else if (comp == COMP_ENABLE)
4228 mac.append(PUSH_COMP_MODE);
4229 while (c != '\n' && c != EOF) {
4230 if (c == 0)
4231 mac.append(n);
4232 else
4233 mac.append((unsigned char)c);
4234 c = get_copy(&n);
4236 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4237 mac.append(POP_GROFFCOMP_MODE);
4238 if (!mm) {
4239 mm = new macro;
4240 request_dictionary.define(nm, mm);
4242 *mm = mac;
4243 tok.next();
4246 void define_string()
4248 do_define_string(DEFINE_NORMAL,
4249 compatible_flag ? COMP_ENABLE: COMP_IGNORE);
4252 void define_nocomp_string()
4254 do_define_string(DEFINE_NORMAL, COMP_DISABLE);
4257 void append_string()
4259 do_define_string(DEFINE_APPEND,
4260 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4263 void append_nocomp_string()
4265 do_define_string(DEFINE_APPEND, COMP_DISABLE);
4268 void do_define_character(char_mode mode, const char *font_name)
4270 node *n = 0; // pacify compiler
4271 int c;
4272 tok.skip();
4273 charinfo *ci = tok.get_char(1);
4274 if (ci == 0) {
4275 skip_line();
4276 return;
4278 if (font_name) {
4279 string s(font_name);
4280 s += ' ';
4281 s += ci->nm.contents();
4282 s += '\0';
4283 ci = get_charinfo(symbol(s.contents()));
4285 tok.next();
4286 if (tok.newline())
4287 c = '\n';
4288 else if (tok.tab())
4289 c = '\t';
4290 else if (!tok.space()) {
4291 error("bad character definition");
4292 skip_line();
4293 return;
4295 else
4296 c = get_copy(&n);
4297 while (c == ' ' || c == '\t')
4298 c = get_copy(&n);
4299 if (c == '"')
4300 c = get_copy(&n);
4301 macro *m = new macro;
4302 while (c != '\n' && c != EOF) {
4303 if (c == 0)
4304 m->append(n);
4305 else
4306 m->append((unsigned char)c);
4307 c = get_copy(&n);
4309 m = ci->setx_macro(m, mode);
4310 if (m)
4311 delete m;
4312 tok.next();
4315 void define_character()
4317 do_define_character(CHAR_NORMAL);
4320 void define_fallback_character()
4322 do_define_character(CHAR_FALLBACK);
4325 void define_special_character()
4327 do_define_character(CHAR_SPECIAL);
4330 static void remove_character()
4332 tok.skip();
4333 while (!tok.newline() && !tok.eof()) {
4334 if (!tok.space() && !tok.tab()) {
4335 charinfo *ci = tok.get_char(1);
4336 if (!ci)
4337 break;
4338 macro *m = ci->set_macro(0);
4339 if (m)
4340 delete m;
4342 tok.next();
4344 skip_line();
4347 static void interpolate_string(symbol nm)
4349 request_or_macro *p = lookup_request(nm);
4350 macro *m = p->to_macro();
4351 if (!m)
4352 error("you can only invoke a string or macro using \\*");
4353 else {
4354 if (m->is_string()) {
4355 string_iterator *si = new string_iterator(*m, "string", nm);
4356 input_stack::push(si);
4358 else {
4359 // if a macro is called as a string, \$0 doesn't get changed
4360 macro_iterator *mi = new macro_iterator(input_stack::get_macro_name(),
4361 *m, "string", 1);
4362 input_stack::push(mi);
4367 static void interpolate_string_with_args(symbol s)
4369 request_or_macro *p = lookup_request(s);
4370 macro *m = p->to_macro();
4371 if (!m)
4372 error("you can only invoke a string or macro using \\*");
4373 else {
4374 macro_iterator *mi = new macro_iterator(s, *m);
4375 decode_string_args(mi);
4376 input_stack::push(mi);
4380 static void interpolate_arg(symbol nm)
4382 const char *s = nm.contents();
4383 if (!s || *s == '\0')
4384 copy_mode_error("missing argument name");
4385 else if (s[1] == 0 && csdigit(s[0]))
4386 input_stack::push(input_stack::get_arg(s[0] - '0'));
4387 else if (s[0] == '*' && s[1] == '\0') {
4388 int limit = input_stack::nargs();
4389 string args;
4390 for (int i = 1; i <= limit; i++) {
4391 input_iterator *p = input_stack::get_arg(i);
4392 int c;
4393 while ((c = p->get(0)) != EOF)
4394 if (c != DOUBLE_QUOTE)
4395 args += c;
4396 if (i != limit)
4397 args += ' ';
4399 if (limit > 0) {
4400 args += '\0';
4401 input_stack::push(make_temp_iterator(args.contents()));
4404 else if (s[0] == '@' && s[1] == '\0') {
4405 int limit = input_stack::nargs();
4406 string args;
4407 for (int i = 1; i <= limit; i++) {
4408 args += '"';
4409 args += char(BEGIN_QUOTE);
4410 input_iterator *p = input_stack::get_arg(i);
4411 int c;
4412 while ((c = p->get(0)) != EOF)
4413 if (c != DOUBLE_QUOTE)
4414 args += c;
4415 args += char(END_QUOTE);
4416 args += '"';
4417 if (i != limit)
4418 args += ' ';
4420 if (limit > 0) {
4421 args += '\0';
4422 input_stack::push(make_temp_iterator(args.contents()));
4425 else if (s[0] == '^' && s[1] == '\0') {
4426 int limit = input_stack::nargs();
4427 string args;
4428 int c = input_stack::peek();
4429 for (int i = 1; i <= limit; i++) {
4430 input_iterator *p = input_stack::get_arg(i);
4431 while ((c = p->get(0)) != EOF) {
4432 if (c == DOUBLE_QUOTE)
4433 c = '"';
4434 args += c;
4436 if (input_stack::space_follows_arg(i))
4437 args += ' ';
4439 if (limit > 0) {
4440 args += '\0';
4441 input_stack::push(make_temp_iterator(args.contents()));
4444 else {
4445 const char *p;
4446 for (p = s; *p && csdigit(*p); p++)
4448 if (*p)
4449 copy_mode_error("bad argument name `%1'", s);
4450 else
4451 input_stack::push(input_stack::get_arg(atoi(s)));
4455 void handle_first_page_transition()
4457 push_token(tok);
4458 topdiv->begin_page();
4461 // We push back a token by wrapping it up in a token_node, and
4462 // wrapping that up in a string_iterator.
4464 static void push_token(const token &t)
4466 macro m;
4467 m.append(new token_node(t));
4468 input_stack::push(new string_iterator(m));
4471 void push_page_ejector()
4473 static char buf[2] = { PAGE_EJECTOR, '\0' };
4474 input_stack::push(make_temp_iterator(buf));
4477 void handle_initial_request(unsigned char code)
4479 char buf[2];
4480 buf[0] = code;
4481 buf[1] = '\0';
4482 macro mac;
4483 mac.append(new token_node(tok));
4484 input_stack::push(new string_iterator(mac));
4485 input_stack::push(make_temp_iterator(buf));
4486 topdiv->begin_page();
4487 tok.next();
4490 void handle_initial_title()
4492 handle_initial_request(TITLE_REQUEST);
4495 // this should be local to define_macro, but cfront 1.2 doesn't support that
4496 static symbol dot_symbol(".");
4498 void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
4500 symbol nm, term;
4501 if (calling == CALLING_INDIRECT) {
4502 symbol temp1 = get_name(1);
4503 if (temp1.is_null()) {
4504 skip_line();
4505 return;
4507 symbol temp2 = get_name();
4508 input_stack::push(make_temp_iterator("\n"));
4509 if (!temp2.is_null()) {
4510 interpolate_string(temp2);
4511 input_stack::push(make_temp_iterator(" "));
4513 interpolate_string(temp1);
4514 input_stack::push(make_temp_iterator(" "));
4515 tok.next();
4517 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4518 nm = get_name(1);
4519 if (nm.is_null()) {
4520 skip_line();
4521 return;
4524 term = get_name(); // the request that terminates the definition
4525 if (term.is_null())
4526 term = dot_symbol;
4527 while (!tok.newline() && !tok.eof())
4528 tok.next();
4529 const char *start_filename;
4530 int start_lineno;
4531 int have_start_location = input_stack::get_location(0, &start_filename,
4532 &start_lineno);
4533 node *n;
4534 // doing this here makes the line numbers come out right
4535 int c = get_copy(&n, 1);
4536 macro mac;
4537 macro *mm = 0;
4538 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4539 request_or_macro *rm =
4540 (request_or_macro *)request_dictionary.lookup(nm);
4541 if (rm)
4542 mm = rm->to_macro();
4543 if (mm && mode == DEFINE_APPEND)
4544 mac = *mm;
4546 int bol = 1;
4547 if (comp == COMP_DISABLE)
4548 mac.append(PUSH_GROFF_MODE);
4549 else if (comp == COMP_ENABLE)
4550 mac.append(PUSH_COMP_MODE);
4551 for (;;) {
4552 if (c == '\n')
4553 mac.clear_string_flag();
4554 while (c == ESCAPE_NEWLINE) {
4555 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
4556 mac.append(c);
4557 c = get_copy(&n, 1);
4559 if (bol && c == '.') {
4560 const char *s = term.contents();
4561 int d = 0;
4562 // see if it matches term
4563 int i = 0;
4564 if (s[0] != 0) {
4565 while ((d = get_copy(&n)) == ' ' || d == '\t')
4567 if ((unsigned char)s[0] == d) {
4568 for (i = 1; s[i] != 0; i++) {
4569 d = get_copy(&n);
4570 if ((unsigned char)s[i] != d)
4571 break;
4575 if (s[i] == 0
4576 && ((i == 2 && compatible_flag)
4577 || (d = get_copy(&n)) == ' '
4578 || d == '\n')) { // we found it
4579 if (d == '\n')
4580 tok.make_newline();
4581 else
4582 tok.make_space();
4583 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4584 if (!mm) {
4585 mm = new macro;
4586 request_dictionary.define(nm, mm);
4588 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4589 mac.append(POP_GROFFCOMP_MODE);
4590 *mm = mac;
4592 if (term != dot_symbol) {
4593 ignoring = 0;
4594 interpolate_macro(term);
4596 else
4597 skip_line();
4598 return;
4600 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4601 mac.append(c);
4602 for (int j = 0; j < i; j++)
4603 mac.append(s[j]);
4605 c = d;
4607 if (c == EOF) {
4608 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4609 if (have_start_location)
4610 error_with_file_and_line(start_filename, start_lineno,
4611 "end of file while defining macro `%1'",
4612 nm.contents());
4613 else
4614 error("end of file while defining macro `%1'", nm.contents());
4616 else {
4617 if (have_start_location)
4618 error_with_file_and_line(start_filename, start_lineno,
4619 "end of file while ignoring input lines");
4620 else
4621 error("end of file while ignoring input lines");
4623 tok.next();
4624 return;
4626 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4627 if (c == 0)
4628 mac.append(n);
4629 else
4630 mac.append(c);
4632 bol = (c == '\n');
4633 c = get_copy(&n, 1);
4637 void define_macro()
4639 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
4640 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4643 void define_nocomp_macro()
4645 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
4648 void define_indirect_macro()
4650 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
4651 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4654 void define_indirect_nocomp_macro()
4656 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
4659 void append_macro()
4661 do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
4662 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4665 void append_nocomp_macro()
4667 do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
4670 void append_indirect_macro()
4672 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
4673 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4676 void append_indirect_nocomp_macro()
4678 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
4681 void ignore()
4683 ignoring = 1;
4684 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
4685 ignoring = 0;
4688 void remove_macro()
4690 for (;;) {
4691 symbol s = get_name();
4692 if (s.is_null())
4693 break;
4694 request_dictionary.remove(s);
4696 skip_line();
4699 void rename_macro()
4701 symbol s1 = get_name(1);
4702 if (!s1.is_null()) {
4703 symbol s2 = get_name(1);
4704 if (!s2.is_null())
4705 request_dictionary.rename(s1, s2);
4707 skip_line();
4710 void alias_macro()
4712 symbol s1 = get_name(1);
4713 if (!s1.is_null()) {
4714 symbol s2 = get_name(1);
4715 if (!s2.is_null()) {
4716 if (!request_dictionary.alias(s1, s2))
4717 warning(WARN_MAC, "macro `%1' not defined", s2.contents());
4720 skip_line();
4723 void chop_macro()
4725 symbol s = get_name(1);
4726 if (!s.is_null()) {
4727 request_or_macro *p = lookup_request(s);
4728 macro *m = p->to_macro();
4729 if (!m)
4730 error("cannot chop request");
4731 else if (m->empty())
4732 error("cannot chop empty macro");
4733 else {
4734 int have_restore = 0;
4735 // we have to check for additional save/restore pairs which could be
4736 // there due to empty am1 requests.
4737 for (;;) {
4738 if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
4739 break;
4740 have_restore = 1;
4741 m->len -= 1;
4742 if (m->get(m->len - 1) != PUSH_GROFF_MODE
4743 && m->get(m->len - 1) != PUSH_COMP_MODE)
4744 break;
4745 have_restore = 0;
4746 m->len -= 1;
4747 if (m->len == 0)
4748 break;
4750 if (m->len == 0)
4751 error("cannot chop empty macro");
4752 else {
4753 if (have_restore)
4754 m->set(POP_GROFFCOMP_MODE, m->len - 1);
4755 else
4756 m->len -= 1;
4760 skip_line();
4763 void substring_request()
4765 int start; // 0, 1, ..., n-1 or -1, -2, ...
4766 symbol s = get_name(1);
4767 if (!s.is_null() && get_integer(&start)) {
4768 request_or_macro *p = lookup_request(s);
4769 macro *m = p->to_macro();
4770 if (!m)
4771 error("cannot apply `substring' on a request");
4772 else {
4773 int end = -1;
4774 if (!has_arg() || get_integer(&end)) {
4775 int real_length = 0; // 1, 2, ..., n
4776 string_iterator iter1(*m);
4777 for (int l = 0; l < m->len; l++) {
4778 int c = iter1.get(0);
4779 if (c == PUSH_GROFF_MODE
4780 || c == PUSH_COMP_MODE
4781 || c == POP_GROFFCOMP_MODE)
4782 continue;
4783 if (c == EOF)
4784 break;
4785 real_length++;
4787 if (start < 0)
4788 start += real_length;
4789 if (end < 0)
4790 end += real_length;
4791 if (start > end) {
4792 int tem = start;
4793 start = end;
4794 end = tem;
4796 if (start >= real_length || end < 0) {
4797 warning(WARN_RANGE,
4798 "start and end index of substring out of range");
4799 m->len = 0;
4800 if (m->p) {
4801 if (--(m->p->count) <= 0)
4802 delete m->p;
4803 m->p = 0;
4805 skip_line();
4806 return;
4808 if (start < 0) {
4809 warning(WARN_RANGE,
4810 "start index of substring out of range, set to 0");
4811 start = 0;
4813 if (end >= real_length) {
4814 warning(WARN_RANGE,
4815 "end index of substring out of range, set to string length");
4816 end = real_length - 1;
4818 // now extract the substring
4819 string_iterator iter(*m);
4820 int i;
4821 for (i = 0; i < start; i++) {
4822 int c = iter.get(0);
4823 while (c == PUSH_GROFF_MODE
4824 || c == PUSH_COMP_MODE
4825 || c == POP_GROFFCOMP_MODE)
4826 c = iter.get(0);
4827 if (c == EOF)
4828 break;
4830 macro mac;
4831 for (; i <= end; i++) {
4832 node *nd = 0; // pacify compiler
4833 int c = iter.get(&nd);
4834 while (c == PUSH_GROFF_MODE
4835 || c == PUSH_COMP_MODE
4836 || c == POP_GROFFCOMP_MODE)
4837 c = iter.get(0);
4838 if (c == EOF)
4839 break;
4840 if (c == 0)
4841 mac.append(nd);
4842 else
4843 mac.append((unsigned char)c);
4845 *m = mac;
4849 skip_line();
4852 void length_request()
4854 symbol ret;
4855 ret = get_name(1);
4856 if (ret.is_null()) {
4857 skip_line();
4858 return;
4860 int c;
4861 node *n;
4862 if (tok.newline())
4863 c = '\n';
4864 else if (tok.tab())
4865 c = '\t';
4866 else if (!tok.space()) {
4867 error("bad string definition");
4868 skip_line();
4869 return;
4871 else
4872 c = get_copy(&n);
4873 while (c == ' ')
4874 c = get_copy(&n);
4875 if (c == '"')
4876 c = get_copy(&n);
4877 int len = 0;
4878 while (c != '\n' && c != EOF) {
4879 ++len;
4880 c = get_copy(&n);
4882 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4883 if (r)
4884 r->set_value(len);
4885 else
4886 set_number_reg(ret, len);
4887 tok.next();
4890 void asciify_macro()
4892 symbol s = get_name(1);
4893 if (!s.is_null()) {
4894 request_or_macro *p = lookup_request(s);
4895 macro *m = p->to_macro();
4896 if (!m)
4897 error("cannot asciify request");
4898 else {
4899 macro am;
4900 string_iterator iter(*m);
4901 for (;;) {
4902 node *nd = 0; // pacify compiler
4903 int c = iter.get(&nd);
4904 if (c == EOF)
4905 break;
4906 if (c != 0)
4907 am.append(c);
4908 else
4909 nd->asciify(&am);
4911 *m = am;
4914 skip_line();
4917 void unformat_macro()
4919 symbol s = get_name(1);
4920 if (!s.is_null()) {
4921 request_or_macro *p = lookup_request(s);
4922 macro *m = p->to_macro();
4923 if (!m)
4924 error("cannot unformat request");
4925 else {
4926 macro am;
4927 string_iterator iter(*m);
4928 for (;;) {
4929 node *nd = 0; // pacify compiler
4930 int c = iter.get(&nd);
4931 if (c == EOF)
4932 break;
4933 if (c != 0)
4934 am.append(c);
4935 else {
4936 if (nd->set_unformat_flag())
4937 am.append(nd);
4940 *m = am;
4943 skip_line();
4946 static void interpolate_environment_variable(symbol nm)
4948 const char *s = getenv(nm.contents());
4949 if (s && *s)
4950 input_stack::push(make_temp_iterator(s));
4953 void interpolate_number_reg(symbol nm, int inc)
4955 reg *r = lookup_number_reg(nm);
4956 if (inc < 0)
4957 r->decrement();
4958 else if (inc > 0)
4959 r->increment();
4960 input_stack::push(make_temp_iterator(r->get_string()));
4963 static void interpolate_number_format(symbol nm)
4965 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4966 if (r)
4967 input_stack::push(make_temp_iterator(r->get_format()));
4970 static int get_delim_number(units *n, unsigned char si, int prev_value)
4972 token start;
4973 start.next();
4974 if (start.delimiter(1)) {
4975 tok.next();
4976 if (get_number(n, si, prev_value)) {
4977 if (start != tok)
4978 warning(WARN_DELIM, "closing delimiter does not match");
4979 return 1;
4982 return 0;
4985 static int get_delim_number(units *n, unsigned char si)
4987 token start;
4988 start.next();
4989 if (start.delimiter(1)) {
4990 tok.next();
4991 if (get_number(n, si)) {
4992 if (start != tok)
4993 warning(WARN_DELIM, "closing delimiter does not match");
4994 return 1;
4997 return 0;
5000 static int get_line_arg(units *n, unsigned char si, charinfo **cp)
5002 token start;
5003 start.next();
5004 int start_level = input_stack::get_level();
5005 if (!start.delimiter(1))
5006 return 0;
5007 tok.next();
5008 if (get_number(n, si)) {
5009 if (tok.dummy() || tok.transparent_dummy())
5010 tok.next();
5011 if (!(start == tok && input_stack::get_level() == start_level)) {
5012 *cp = tok.get_char(1);
5013 tok.next();
5015 if (!(start == tok && input_stack::get_level() == start_level))
5016 warning(WARN_DELIM, "closing delimiter does not match");
5017 return 1;
5019 return 0;
5022 static int read_size(int *x)
5024 tok.next();
5025 int c = tok.ch();
5026 int inc = 0;
5027 if (c == '-') {
5028 inc = -1;
5029 tok.next();
5030 c = tok.ch();
5032 else if (c == '+') {
5033 inc = 1;
5034 tok.next();
5035 c = tok.ch();
5037 int val = 0; // pacify compiler
5038 int bad = 0;
5039 if (c == '(') {
5040 tok.next();
5041 c = tok.ch();
5042 if (!inc) {
5043 // allow an increment either before or after the left parenthesis
5044 if (c == '-') {
5045 inc = -1;
5046 tok.next();
5047 c = tok.ch();
5049 else if (c == '+') {
5050 inc = 1;
5051 tok.next();
5052 c = tok.ch();
5055 if (!csdigit(c))
5056 bad = 1;
5057 else {
5058 val = c - '0';
5059 tok.next();
5060 c = tok.ch();
5061 if (!csdigit(c))
5062 bad = 1;
5063 else {
5064 val = val*10 + (c - '0');
5065 val *= sizescale;
5069 else if (csdigit(c)) {
5070 val = c - '0';
5071 if (!inc && c != '0' && c < '4') {
5072 tok.next();
5073 c = tok.ch();
5074 if (!csdigit(c))
5075 bad = 1;
5076 else
5077 val = val*10 + (c - '0');
5079 val *= sizescale;
5081 else if (!tok.delimiter(1))
5082 return 0;
5083 else {
5084 token start(tok);
5085 tok.next();
5086 c = tok.ch();
5087 if (!inc && (c == '-' || c == '+')) {
5088 inc = c == '+' ? 1 : -1;
5089 tok.next();
5091 if (!get_number(&val, 'z'))
5092 return 0;
5093 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
5094 if (start.ch() == '[')
5095 error("missing `]'");
5096 else
5097 error("missing closing delimiter");
5098 return 0;
5101 if (!bad) {
5102 switch (inc) {
5103 case 0:
5104 if (val == 0) {
5105 // special case -- \s[0] and \s0 means to revert to previous size
5106 *x = 0;
5107 return 1;
5109 *x = val;
5110 break;
5111 case 1:
5112 *x = curenv->get_requested_point_size() + val;
5113 break;
5114 case -1:
5115 *x = curenv->get_requested_point_size() - val;
5116 break;
5117 default:
5118 assert(0);
5120 if (*x <= 0) {
5121 warning(WARN_RANGE,
5122 "\\s escape results in non-positive point size; set to 1");
5123 *x = 1;
5125 return 1;
5127 else {
5128 error("bad digit in point size");
5129 return 0;
5133 static symbol get_delim_name()
5135 token start;
5136 start.next();
5137 if (start.eof()) {
5138 error("end of input at start of delimited name");
5139 return NULL_SYMBOL;
5141 if (start.newline()) {
5142 error("can't delimit name with a newline");
5143 return NULL_SYMBOL;
5145 int start_level = input_stack::get_level();
5146 char abuf[ABUF_SIZE];
5147 char *buf = abuf;
5148 int buf_size = ABUF_SIZE;
5149 int i = 0;
5150 for (;;) {
5151 if (i + 1 > buf_size) {
5152 if (buf == abuf) {
5153 buf = new char[ABUF_SIZE*2];
5154 memcpy(buf, abuf, buf_size);
5155 buf_size = ABUF_SIZE*2;
5157 else {
5158 char *old_buf = buf;
5159 buf = new char[buf_size*2];
5160 memcpy(buf, old_buf, buf_size);
5161 buf_size *= 2;
5162 a_delete old_buf;
5165 tok.next();
5166 if (tok == start
5167 && (compatible_flag || input_stack::get_level() == start_level))
5168 break;
5169 if ((buf[i] = tok.ch()) == 0) {
5170 error("missing delimiter (got %1)", tok.description());
5171 if (buf != abuf)
5172 a_delete buf;
5173 return NULL_SYMBOL;
5175 i++;
5177 buf[i] = '\0';
5178 if (buf == abuf) {
5179 if (i == 0) {
5180 error("empty delimited name");
5181 return NULL_SYMBOL;
5183 else
5184 return symbol(buf);
5186 else {
5187 symbol s(buf);
5188 a_delete buf;
5189 return s;
5193 // Implement \R
5195 static void do_register()
5197 token start;
5198 start.next();
5199 if (!start.delimiter(1))
5200 return;
5201 tok.next();
5202 symbol nm = get_long_name(1);
5203 if (nm.is_null())
5204 return;
5205 while (tok.space())
5206 tok.next();
5207 reg *r = (reg *)number_reg_dictionary.lookup(nm);
5208 int prev_value;
5209 if (!r || !r->get_value(&prev_value))
5210 prev_value = 0;
5211 int val;
5212 if (!get_number(&val, 'u', prev_value))
5213 return;
5214 if (start != tok)
5215 warning(WARN_DELIM, "closing delimiter does not match");
5216 if (r)
5217 r->set_value(val);
5218 else
5219 set_number_reg(nm, val);
5222 // this implements the \w escape sequence
5224 static void do_width()
5226 token start;
5227 start.next();
5228 int start_level = input_stack::get_level();
5229 environment env(curenv);
5230 environment *oldenv = curenv;
5231 curenv = &env;
5232 for (;;) {
5233 tok.next();
5234 if (tok.eof()) {
5235 warning(WARN_DELIM, "missing closing delimiter");
5236 break;
5238 if (tok.newline()) {
5239 warning(WARN_DELIM, "missing closing delimiter");
5240 input_stack::push(make_temp_iterator("\n"));
5241 break;
5243 if (tok == start
5244 && (compatible_flag || input_stack::get_level() == start_level))
5245 break;
5246 tok.process();
5248 env.wrap_up_tab();
5249 units x = env.get_input_line_position().to_units();
5250 input_stack::push(make_temp_iterator(i_to_a(x)));
5251 env.width_registers();
5252 curenv = oldenv;
5253 have_input = 0;
5256 charinfo *page_character;
5258 void set_page_character()
5260 page_character = get_optional_char();
5261 skip_line();
5264 static const symbol percent_symbol("%");
5266 void read_title_parts(node **part, hunits *part_width)
5268 tok.skip();
5269 if (tok.newline() || tok.eof())
5270 return;
5271 token start(tok);
5272 int start_level = input_stack::get_level();
5273 tok.next();
5274 for (int i = 0; i < 3; i++) {
5275 while (!tok.newline() && !tok.eof()) {
5276 if (tok == start
5277 && (compatible_flag || input_stack::get_level() == start_level)) {
5278 tok.next();
5279 break;
5281 if (page_character != 0 && tok.get_char() == page_character)
5282 interpolate_number_reg(percent_symbol, 0);
5283 else
5284 tok.process();
5285 tok.next();
5287 curenv->wrap_up_tab();
5288 part_width[i] = curenv->get_input_line_position();
5289 part[i] = curenv->extract_output_line();
5291 while (!tok.newline() && !tok.eof())
5292 tok.next();
5295 class non_interpreted_node : public node {
5296 macro mac;
5297 public:
5298 non_interpreted_node(const macro &);
5299 int interpret(macro *);
5300 node *copy();
5301 int ends_sentence();
5302 int same(node *);
5303 const char *type();
5304 int force_tprint();
5305 int is_tag();
5308 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
5312 int non_interpreted_node::ends_sentence()
5314 return 2;
5317 int non_interpreted_node::same(node *nd)
5319 return mac == ((non_interpreted_node *)nd)->mac;
5322 const char *non_interpreted_node::type()
5324 return "non_interpreted_node";
5327 int non_interpreted_node::force_tprint()
5329 return 0;
5332 int non_interpreted_node::is_tag()
5334 return 0;
5337 node *non_interpreted_node::copy()
5339 return new non_interpreted_node(mac);
5342 int non_interpreted_node::interpret(macro *m)
5344 string_iterator si(mac);
5345 node *n = 0; // pacify compiler
5346 for (;;) {
5347 int c = si.get(&n);
5348 if (c == EOF)
5349 break;
5350 if (c == 0)
5351 m->append(n);
5352 else
5353 m->append(c);
5355 return 1;
5358 static node *do_non_interpreted()
5360 node *n;
5361 int c;
5362 macro mac;
5363 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
5364 if (c == 0)
5365 mac.append(n);
5366 else
5367 mac.append(c);
5368 if (c == EOF || c == '\n') {
5369 error("missing \\?");
5370 return 0;
5372 return new non_interpreted_node(mac);
5375 static void encode_char(macro *mac, char c)
5377 if (c == '\0') {
5378 if ((font::use_charnames_in_special) && tok.special()) {
5379 charinfo *ci = tok.get_char(1);
5380 const char *s = ci->get_symbol()->contents();
5381 if (s[0] != (char)0) {
5382 mac->append('\\');
5383 mac->append('[');
5384 int i = 0;
5385 while (s[i] != (char)0) {
5386 mac->append(s[i]);
5387 i++;
5389 mac->append(']');
5392 else if (tok.stretchable_space()
5393 || tok.unstretchable_space())
5394 mac->append(' ');
5395 else if (!(tok.hyphen_indicator()
5396 || tok.dummy()
5397 || tok.transparent_dummy()
5398 || tok.zero_width_break()))
5399 error("%1 is invalid within \\X", tok.description());
5401 else {
5402 if ((font::use_charnames_in_special) && (c == '\\')) {
5404 * add escape escape sequence
5406 mac->append(c);
5408 mac->append(c);
5412 node *do_special()
5414 token start;
5415 start.next();
5416 int start_level = input_stack::get_level();
5417 macro mac;
5418 for (tok.next();
5419 tok != start || input_stack::get_level() != start_level;
5420 tok.next()) {
5421 if (tok.eof()) {
5422 warning(WARN_DELIM, "missing closing delimiter");
5423 return 0;
5425 if (tok.newline()) {
5426 input_stack::push(make_temp_iterator("\n"));
5427 warning(WARN_DELIM, "missing closing delimiter");
5428 break;
5430 unsigned char c;
5431 if (tok.space())
5432 c = ' ';
5433 else if (tok.tab())
5434 c = '\t';
5435 else if (tok.leader())
5436 c = '\001';
5437 else if (tok.backspace())
5438 c = '\b';
5439 else
5440 c = tok.ch();
5441 encode_char(&mac, c);
5443 return new special_node(mac);
5446 void device_request()
5448 if (!tok.newline() && !tok.eof()) {
5449 int c;
5450 macro mac;
5451 for (;;) {
5452 c = get_copy(0);
5453 if (c == '"') {
5454 c = get_copy(0);
5455 break;
5457 if (c != ' ' && c != '\t')
5458 break;
5460 for (; c != '\n' && c != EOF; c = get_copy(0))
5461 mac.append(c);
5462 curenv->add_node(new special_node(mac));
5464 tok.next();
5467 void device_macro_request()
5469 symbol s = get_name(1);
5470 if (!(s.is_null() || s.is_empty())) {
5471 request_or_macro *p = lookup_request(s);
5472 macro *m = p->to_macro();
5473 if (m)
5474 curenv->add_node(new special_node(*m));
5475 else
5476 error("can't transparently throughput a request");
5478 skip_line();
5481 void output_request()
5483 if (!tok.newline() && !tok.eof()) {
5484 int c;
5485 for (;;) {
5486 c = get_copy(0);
5487 if (c == '"') {
5488 c = get_copy(0);
5489 break;
5491 if (c != ' ' && c != '\t')
5492 break;
5494 for (; c != '\n' && c != EOF; c = get_copy(0))
5495 topdiv->transparent_output(c);
5496 topdiv->transparent_output('\n');
5498 tok.next();
5501 extern int image_no; // from node.cpp
5503 static node *do_suppress(symbol nm)
5505 if (nm.is_null() || nm.is_empty()) {
5506 error("expecting an argument to escape \\O");
5507 return 0;
5509 const char *s = nm.contents();
5510 switch (*s) {
5511 case '0':
5512 if (begin_level == 0)
5513 // suppress generation of glyphs
5514 return new suppress_node(0, 0);
5515 break;
5516 case '1':
5517 if (begin_level == 0)
5518 // enable generation of glyphs
5519 return new suppress_node(1, 0);
5520 break;
5521 case '2':
5522 if (begin_level == 0)
5523 return new suppress_node(1, 1);
5524 break;
5525 case '3':
5526 have_input = 1;
5527 begin_level++;
5528 break;
5529 case '4':
5530 have_input = 1;
5531 begin_level--;
5532 break;
5533 case '5':
5535 s++; // move over '5'
5536 char position = *s;
5537 if (*s == (char)0) {
5538 error("missing position and filename in \\O");
5539 return 0;
5541 if (!(position == 'l'
5542 || position == 'r'
5543 || position == 'c'
5544 || position == 'i')) {
5545 error("l, r, c, or i position expected (got %1 in \\O)", position);
5546 return 0;
5548 s++; // onto image name
5549 if (s == (char *)0) {
5550 error("missing image name for \\O");
5551 return 0;
5553 image_no++;
5554 if (begin_level == 0)
5555 return new suppress_node(symbol(s), position, image_no);
5556 else
5557 have_input = 1;
5559 break;
5560 default:
5561 error("`%1' is an invalid argument to \\O", *s);
5563 return 0;
5566 void special_node::tprint(troff_output_file *out)
5568 tprint_start(out);
5569 string_iterator iter(mac);
5570 for (;;) {
5571 int c = iter.get(0);
5572 if (c == EOF)
5573 break;
5574 for (const char *s = ::asciify(c); *s; s++)
5575 tprint_char(out, *s);
5577 tprint_end(out);
5580 int get_file_line(const char **filename, int *lineno)
5582 return input_stack::get_location(0, filename, lineno);
5585 void line_file()
5587 int n;
5588 if (get_integer(&n)) {
5589 const char *filename = 0;
5590 if (has_arg()) {
5591 symbol s = get_long_name();
5592 filename = s.contents();
5594 (void)input_stack::set_location(filename, n-1);
5596 skip_line();
5599 static int nroff_mode = 0;
5601 static void nroff_request()
5603 nroff_mode = 1;
5604 skip_line();
5607 static void troff_request()
5609 nroff_mode = 0;
5610 skip_line();
5613 static void skip_alternative()
5615 int level = 0;
5616 // ensure that ``.if 0\{'' works as expected
5617 if (tok.left_brace())
5618 level++;
5619 int c;
5620 for (;;) {
5621 c = input_stack::get(0);
5622 if (c == EOF)
5623 break;
5624 if (c == ESCAPE_LEFT_BRACE)
5625 ++level;
5626 else if (c == ESCAPE_RIGHT_BRACE)
5627 --level;
5628 else if (c == escape_char && escape_char > 0)
5629 switch(input_stack::get(0)) {
5630 case '{':
5631 ++level;
5632 break;
5633 case '}':
5634 --level;
5635 break;
5636 case '"':
5637 while ((c = input_stack::get(0)) != '\n' && c != EOF)
5641 Note that the level can properly be < 0, eg
5643 .if 1 \{\
5644 .if 0 \{\
5645 .\}\}
5647 So don't give an error message in this case.
5649 if (level <= 0 && c == '\n')
5650 break;
5652 tok.next();
5655 static void begin_alternative()
5657 while (tok.space() || tok.left_brace())
5658 tok.next();
5661 void nop_request()
5663 while (tok.space())
5664 tok.next();
5667 static int_stack if_else_stack;
5669 int do_if_request()
5671 int invert = 0;
5672 while (tok.space())
5673 tok.next();
5674 while (tok.ch() == '!') {
5675 tok.next();
5676 invert = !invert;
5678 int result;
5679 unsigned char c = tok.ch();
5680 if (c == 't') {
5681 tok.next();
5682 result = !nroff_mode;
5684 else if (c == 'n') {
5685 tok.next();
5686 result = nroff_mode;
5688 else if (c == 'v') {
5689 tok.next();
5690 result = 0;
5692 else if (c == 'o') {
5693 result = (topdiv->get_page_number() & 1);
5694 tok.next();
5696 else if (c == 'e') {
5697 result = !(topdiv->get_page_number() & 1);
5698 tok.next();
5700 else if (c == 'd' || c == 'r') {
5701 tok.next();
5702 symbol nm = get_name(1);
5703 if (nm.is_null()) {
5704 skip_alternative();
5705 return 0;
5707 result = (c == 'd'
5708 ? request_dictionary.lookup(nm) != 0
5709 : number_reg_dictionary.lookup(nm) != 0);
5711 else if (c == 'm') {
5712 tok.next();
5713 symbol nm = get_long_name(1);
5714 if (nm.is_null()) {
5715 skip_alternative();
5716 return 0;
5718 result = (nm == default_symbol
5719 || color_dictionary.lookup(nm) != 0);
5721 else if (c == 'c') {
5722 tok.next();
5723 tok.skip();
5724 charinfo *ci = tok.get_char(1);
5725 if (ci == 0) {
5726 skip_alternative();
5727 return 0;
5729 result = character_exists(ci, curenv);
5730 tok.next();
5732 else if (c == 'F') {
5733 tok.next();
5734 symbol nm = get_long_name(1);
5735 if (nm.is_null()) {
5736 skip_alternative();
5737 return 0;
5739 result = check_font(curenv->get_family()->nm, nm);
5741 else if (c == 'S') {
5742 tok.next();
5743 symbol nm = get_long_name(1);
5744 if (nm.is_null()) {
5745 skip_alternative();
5746 return 0;
5748 result = check_style(nm);
5750 else if (tok.space())
5751 result = 0;
5752 else if (tok.delimiter()) {
5753 token delim = tok;
5754 int delim_level = input_stack::get_level();
5755 environment env1(curenv);
5756 environment env2(curenv);
5757 environment *oldenv = curenv;
5758 curenv = &env1;
5759 suppress_push = 1;
5760 for (int i = 0; i < 2; i++) {
5761 for (;;) {
5762 tok.next();
5763 if (tok.newline() || tok.eof()) {
5764 warning(WARN_DELIM, "missing closing delimiter");
5765 tok.next();
5766 curenv = oldenv;
5767 return 0;
5769 if (tok == delim
5770 && (compatible_flag || input_stack::get_level() == delim_level))
5771 break;
5772 tok.process();
5774 curenv = &env2;
5776 node *n1 = env1.extract_output_line();
5777 node *n2 = env2.extract_output_line();
5778 result = same_node_list(n1, n2);
5779 delete_node_list(n1);
5780 delete_node_list(n2);
5781 curenv = oldenv;
5782 have_input = 0;
5783 suppress_push = 0;
5784 tok.next();
5786 else {
5787 units n;
5788 if (!get_number(&n, 'u')) {
5789 skip_alternative();
5790 return 0;
5792 else
5793 result = n > 0;
5795 if (invert)
5796 result = !result;
5797 if (result)
5798 begin_alternative();
5799 else
5800 skip_alternative();
5801 return result;
5804 void if_else_request()
5806 if_else_stack.push(do_if_request());
5809 void if_request()
5811 do_if_request();
5814 void else_request()
5816 if (if_else_stack.is_empty()) {
5817 warning(WARN_EL, "unbalanced .el request");
5818 skip_alternative();
5820 else {
5821 if (if_else_stack.pop())
5822 skip_alternative();
5823 else
5824 begin_alternative();
5828 static int while_depth = 0;
5829 static int while_break_flag = 0;
5831 void while_request()
5833 macro mac;
5834 int escaped = 0;
5835 int level = 0;
5836 mac.append(new token_node(tok));
5837 for (;;) {
5838 node *n = 0; // pacify compiler
5839 int c = input_stack::get(&n);
5840 if (c == EOF)
5841 break;
5842 if (c == 0) {
5843 escaped = 0;
5844 mac.append(n);
5846 else if (escaped) {
5847 if (c == '{')
5848 level += 1;
5849 else if (c == '}')
5850 level -= 1;
5851 escaped = 0;
5852 mac.append(c);
5854 else {
5855 if (c == ESCAPE_LEFT_BRACE)
5856 level += 1;
5857 else if (c == ESCAPE_RIGHT_BRACE)
5858 level -= 1;
5859 else if (c == escape_char)
5860 escaped = 1;
5861 mac.append(c);
5862 if (c == '\n' && level <= 0)
5863 break;
5866 if (level != 0)
5867 error("unbalanced \\{ \\}");
5868 else {
5869 while_depth++;
5870 input_stack::add_boundary();
5871 for (;;) {
5872 input_stack::push(new string_iterator(mac, "while loop"));
5873 tok.next();
5874 if (!do_if_request()) {
5875 while (input_stack::get(0) != EOF)
5877 break;
5879 process_input_stack();
5880 if (while_break_flag || input_stack::is_return_boundary()) {
5881 while_break_flag = 0;
5882 break;
5885 input_stack::remove_boundary();
5886 while_depth--;
5888 tok.next();
5891 void while_break_request()
5893 if (!while_depth) {
5894 error("no while loop");
5895 skip_line();
5897 else {
5898 while_break_flag = 1;
5899 while (input_stack::get(0) != EOF)
5901 tok.next();
5905 void while_continue_request()
5907 if (!while_depth) {
5908 error("no while loop");
5909 skip_line();
5911 else {
5912 while (input_stack::get(0) != EOF)
5914 tok.next();
5918 // .so
5919 void source()
5921 symbol nm = get_long_name(1);
5922 if (nm.is_null())
5923 skip_line();
5924 else {
5925 while (!tok.newline() && !tok.eof())
5926 tok.next();
5927 file_case *fcp = include_search_path.open_file_cautious(nm.contents(),
5928 fcp->mux_unpack);
5929 if (fcp != NULL)
5930 input_stack::push(new file_iterator(fcp, nm.contents()));
5931 else
5932 error("can't open `%1': %2", nm.contents(), strerror(errno));
5933 tok.next();
5937 // like .so but use popen()
5938 void pipe_source()
5940 if (!unsafe_flag) {
5941 error(".pso request not allowed in safer mode");
5942 skip_line();
5944 else {
5945 #ifdef POPEN_MISSING
5946 error("pipes not available on this system");
5947 skip_line();
5948 #else /* not POPEN_MISSING */
5949 if (tok.newline() || tok.eof())
5950 error("missing command");
5951 else {
5952 int c;
5953 while ((c = get_copy(0)) == ' ' || c == '\t')
5955 int buf_size = 24;
5956 char *buf = new char[buf_size];
5957 int buf_used = 0;
5958 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5959 const char *s = asciify(c);
5960 int slen = strlen(s);
5961 if (buf_used + slen + 1> buf_size) {
5962 char *old_buf = buf;
5963 int old_buf_size = buf_size;
5964 buf_size *= 2;
5965 buf = new char[buf_size];
5966 memcpy(buf, old_buf, old_buf_size);
5967 a_delete old_buf;
5969 strcpy(buf + buf_used, s);
5970 buf_used += slen;
5972 buf[buf_used] = '\0';
5973 errno = 0;
5974 FILE *fp = popen(buf, POPEN_RT);
5975 if (fp != NULL)
5976 input_stack::push(new file_iterator(
5977 new file_case(fp, buf, file_case::fc_pipe | file_case::fc_take_path),
5978 symbol(buf).contents()));
5979 else {
5980 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5981 a_delete buf;
5984 tok.next();
5985 #endif /* not POPEN_MISSING */
5989 // .psbb
5991 static int llx_reg_contents = 0;
5992 static int lly_reg_contents = 0;
5993 static int urx_reg_contents = 0;
5994 static int ury_reg_contents = 0;
5996 struct bounding_box {
5997 int llx, lly, urx, ury;
6000 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
6001 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
6003 int parse_bounding_box(char *p, bounding_box *bb)
6005 if (sscanf(p, "%d %d %d %d",
6006 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
6007 return 1;
6008 else {
6009 /* The Document Structuring Conventions say that the numbers
6010 should be integers. Unfortunately some broken applications
6011 get this wrong. */
6012 double x1, x2, x3, x4;
6013 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
6014 bb->llx = (int)x1;
6015 bb->lly = (int)x2;
6016 bb->urx = (int)x3;
6017 bb->ury = (int)x4;
6018 return 1;
6020 else {
6021 for (; *p == ' ' || *p == '\t'; p++)
6023 if (strncmp(p, "(atend)", 7) == 0) {
6024 return 2;
6028 bb->llx = bb->lly = bb->urx = bb->ury = 0;
6029 return 0;
6032 // This version is taken from psrm.cpp FIXME DO IT LIKE
6034 #define PS_LINE_MAX 255
6035 cset white_space("\n\r \t");
6037 int ps_get_line(char *buf, file_case *fcp, const char* filename)
6039 int c = fcp->get_c();
6040 if (c == EOF) {
6041 buf[0] = '\0';
6042 return 0;
6044 int i = 0;
6045 int err = 0;
6046 while (c != '\r' && c != '\n' && c != EOF) {
6047 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
6048 error("invalid input character code %1 in `%2'", int(c), filename);
6049 else if (i < PS_LINE_MAX)
6050 buf[i++] = c;
6051 else if (!err) {
6052 err = 1;
6053 error("PostScript file `%1' is non-conforming "
6054 "because length of line exceeds 255", filename);
6056 c = fcp->get_c();
6058 buf[i++] = '\n';
6059 buf[i] = '\0';
6060 if (c == '\r') {
6061 c = fcp->get_c();
6062 if (c != EOF && c != '\n')
6063 fcp->unget_c(c);
6065 return 1;
6068 inline void assign_registers(int llx, int lly, int urx, int ury)
6070 llx_reg_contents = llx;
6071 lly_reg_contents = lly;
6072 urx_reg_contents = urx;
6073 ury_reg_contents = ury;
6076 void do_ps_file(file_case *fcp, const char* filename)
6078 bounding_box bb;
6079 int bb_at_end = 0;
6080 char buf[PS_LINE_MAX + 1 /* NL */ +1];
6081 llx_reg_contents = lly_reg_contents =
6082 urx_reg_contents = ury_reg_contents = 0;
6083 if (!ps_get_line(buf, fcp, filename)) {
6084 error("`%1' is empty", filename);
6085 return;
6087 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
6088 error("`%1' is not conforming to the Document Structuring Conventions",
6089 filename);
6090 return;
6092 while (ps_get_line(buf, fcp, filename) != 0) {
6093 // in header comments, `%X' (`X' any printable character except
6094 // whitespace) is possible too
6095 if (buf[0] == '%') {
6096 if (strncmp(buf + 1, "%EndComments", 12) == 0)
6097 break;
6098 if (white_space(buf[1]))
6099 break;
6101 else
6102 break;
6103 if (strncmp(buf + 1, "%BoundingBox:", 13) == 0) {
6104 int res = parse_bounding_box(buf + 14, &bb);
6105 if (res == 1) {
6106 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6107 return;
6109 else if (res == 2) {
6110 bb_at_end = 1;
6111 break;
6113 else {
6114 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6115 filename);
6116 return;
6120 if (bb_at_end) {
6121 long offset;
6122 int last_try = 0;
6123 // in the trailer, the last BoundingBox comment is significant
6124 for (offset = 512; !last_try; offset *= 2) {
6125 int had_trailer = 0;
6126 int got_bb = 0;
6127 if (offset > 32768 || fcp->seek(-offset, fcp->seek_end) == -1) {
6128 last_try = 1;
6129 if (fcp->seek(0L, fcp->seek_set) == -1)
6130 break;
6132 while (ps_get_line(buf, fcp, filename) != 0) {
6133 if (buf[0] == '%' && buf[1] == '%') {
6134 if (!had_trailer) {
6135 if (strncmp(buf + 2, "Trailer", 7) == 0)
6136 had_trailer = 1;
6138 else {
6139 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
6140 int res = parse_bounding_box(buf + 14, &bb);
6141 if (res == 1)
6142 got_bb = 1;
6143 else if (res == 2) {
6144 error("`(atend)' not allowed in trailer of `%1'", filename);
6145 return;
6147 else {
6148 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6149 filename);
6150 return;
6156 if (got_bb) {
6157 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6158 return;
6162 error("%%%%BoundingBox comment not found in `%1'", filename);
6165 void ps_bbox_request()
6167 symbol nm = get_long_name(1);
6168 if (nm.is_null())
6169 skip_line();
6170 else {
6171 while (!tok.newline() && !tok.eof())
6172 tok.next();
6173 // PS files might contain non-printable characters, such as ^Z
6174 // and CRs not followed by an LF, so open them in binary mode.
6175 file_case *fcp = include_search_path.open_file_cautious(nm.contents(),
6176 fcp->mux_need_seek | fcp->mux_need_binary);
6177 if (fcp != NULL) {
6178 do_ps_file(fcp, nm.contents());
6179 delete fcp;
6180 } else
6181 error("can't open `%1': %2", nm.contents(), strerror(errno));
6182 tok.next();
6186 const char *asciify(int c)
6188 static char buf[3];
6189 buf[0] = escape_char == '\0' ? '\\' : escape_char;
6190 buf[1] = buf[2] = '\0';
6191 switch (c) {
6192 case ESCAPE_QUESTION:
6193 buf[1] = '?';
6194 break;
6195 case ESCAPE_AMPERSAND:
6196 buf[1] = '&';
6197 break;
6198 case ESCAPE_RIGHT_PARENTHESIS:
6199 buf[1] = ')';
6200 break;
6201 case ESCAPE_UNDERSCORE:
6202 buf[1] = '_';
6203 break;
6204 case ESCAPE_BAR:
6205 buf[1] = '|';
6206 break;
6207 case ESCAPE_CIRCUMFLEX:
6208 buf[1] = '^';
6209 break;
6210 case ESCAPE_LEFT_BRACE:
6211 buf[1] = '{';
6212 break;
6213 case ESCAPE_RIGHT_BRACE:
6214 buf[1] = '}';
6215 break;
6216 case ESCAPE_LEFT_QUOTE:
6217 buf[1] = '`';
6218 break;
6219 case ESCAPE_RIGHT_QUOTE:
6220 buf[1] = '\'';
6221 break;
6222 case ESCAPE_HYPHEN:
6223 buf[1] = '-';
6224 break;
6225 case ESCAPE_BANG:
6226 buf[1] = '!';
6227 break;
6228 case ESCAPE_c:
6229 buf[1] = 'c';
6230 break;
6231 case ESCAPE_e:
6232 buf[1] = 'e';
6233 break;
6234 case ESCAPE_E:
6235 buf[1] = 'E';
6236 break;
6237 case ESCAPE_PERCENT:
6238 buf[1] = '%';
6239 break;
6240 case ESCAPE_SPACE:
6241 buf[1] = ' ';
6242 break;
6243 case ESCAPE_TILDE:
6244 buf[1] = '~';
6245 break;
6246 case ESCAPE_COLON:
6247 buf[1] = ':';
6248 break;
6249 case PUSH_GROFF_MODE:
6250 case PUSH_COMP_MODE:
6251 case POP_GROFFCOMP_MODE:
6252 buf[0] = '\0';
6253 break;
6254 default:
6255 if (invalid_input_char(c))
6256 buf[0] = '\0';
6257 else
6258 buf[0] = c;
6259 break;
6261 return buf;
6264 const char *input_char_description(int c)
6266 switch (c) {
6267 case '\n':
6268 return "a newline character";
6269 case '\b':
6270 return "a backspace character";
6271 case '\001':
6272 return "a leader character";
6273 case '\t':
6274 return "a tab character";
6275 case ' ':
6276 return "a space character";
6277 case '\0':
6278 return "a node";
6280 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
6281 if (invalid_input_char(c)) {
6282 const char *s = asciify(c);
6283 if (*s) {
6284 buf[0] = '`';
6285 strcpy(buf + 1, s);
6286 strcat(buf, "'");
6287 return buf;
6289 sprintf(buf, "magic character code %d", c);
6290 return buf;
6292 if (csprint(c)) {
6293 buf[0] = '`';
6294 buf[1] = c;
6295 buf[2] = '\'';
6296 return buf;
6298 sprintf(buf, "character code %d", c);
6299 return buf;
6302 void tag()
6304 if (!tok.newline() && !tok.eof()) {
6305 string s;
6306 int c;
6307 for (;;) {
6308 c = get_copy(0);
6309 if (c == '"') {
6310 c = get_copy(0);
6311 break;
6313 if (c != ' ' && c != '\t')
6314 break;
6316 s = "x X ";
6317 for (; c != '\n' && c != EOF; c = get_copy(0))
6318 s += (char)c;
6319 s += '\n';
6320 curenv->add_node(new tag_node(s, 0));
6322 tok.next();
6325 void taga()
6327 if (!tok.newline() && !tok.eof()) {
6328 string s;
6329 int c;
6330 for (;;) {
6331 c = get_copy(0);
6332 if (c == '"') {
6333 c = get_copy(0);
6334 break;
6336 if (c != ' ' && c != '\t')
6337 break;
6339 s = "x X ";
6340 for (; c != '\n' && c != EOF; c = get_copy(0))
6341 s += (char)c;
6342 s += '\n';
6343 curenv->add_node(new tag_node(s, 1));
6345 tok.next();
6348 // .tm, .tm1, and .tmc
6350 void do_terminal(int newline, int string_like)
6352 if (!tok.newline() && !tok.eof()) {
6353 int c;
6354 for (;;) {
6355 c = get_copy(0);
6356 if (string_like && c == '"') {
6357 c = get_copy(0);
6358 break;
6360 if (c != ' ' && c != '\t')
6361 break;
6363 for (; c != '\n' && c != EOF; c = get_copy(0))
6364 fputs(asciify(c), stderr);
6366 if (newline)
6367 fputc('\n', stderr);
6368 fflush(stderr);
6369 tok.next();
6372 void terminal()
6374 do_terminal(1, 0);
6377 void terminal1()
6379 do_terminal(1, 1);
6382 void terminal_continue()
6384 do_terminal(0, 1);
6387 dictionary stream_dictionary(20);
6389 void do_open(int append)
6391 symbol stream = get_name(1);
6392 if (!stream.is_null()) {
6393 symbol filename = get_long_name(1);
6394 if (!filename.is_null()) {
6395 errno = 0;
6396 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
6397 if (!fp) {
6398 error("can't open `%1' for %2: %3",
6399 filename.contents(),
6400 append ? "appending" : "writing",
6401 strerror(errno));
6402 fp = (FILE *)stream_dictionary.remove(stream);
6404 else
6405 fp = (FILE *)stream_dictionary.lookup(stream, fp);
6406 if (fp)
6407 fclose(fp);
6410 skip_line();
6413 void open_request()
6415 if (!unsafe_flag) {
6416 error(".open request not allowed in safer mode");
6417 skip_line();
6419 else
6420 do_open(0);
6423 void opena_request()
6425 if (!unsafe_flag) {
6426 error(".opena request not allowed in safer mode");
6427 skip_line();
6429 else
6430 do_open(1);
6433 void close_request()
6435 symbol stream = get_name(1);
6436 if (!stream.is_null()) {
6437 FILE *fp = (FILE *)stream_dictionary.remove(stream);
6438 if (!fp)
6439 error("no stream named `%1'", stream.contents());
6440 else
6441 fclose(fp);
6443 skip_line();
6446 // .write and .writec
6448 void do_write_request(int newline)
6450 symbol stream = get_name(1);
6451 if (stream.is_null()) {
6452 skip_line();
6453 return;
6455 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6456 if (!fp) {
6457 error("no stream named `%1'", stream.contents());
6458 skip_line();
6459 return;
6461 int c;
6462 while ((c = get_copy(0)) == ' ')
6464 if (c == '"')
6465 c = get_copy(0);
6466 for (; c != '\n' && c != EOF; c = get_copy(0))
6467 fputs(asciify(c), fp);
6468 if (newline)
6469 fputc('\n', fp);
6470 fflush(fp);
6471 tok.next();
6474 void write_request()
6476 do_write_request(1);
6479 void write_request_continue()
6481 do_write_request(0);
6484 void write_macro_request()
6486 symbol stream = get_name(1);
6487 if (stream.is_null()) {
6488 skip_line();
6489 return;
6491 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6492 if (!fp) {
6493 error("no stream named `%1'", stream.contents());
6494 skip_line();
6495 return;
6497 symbol s = get_name(1);
6498 if (s.is_null()) {
6499 skip_line();
6500 return;
6502 request_or_macro *p = lookup_request(s);
6503 macro *m = p->to_macro();
6504 if (!m)
6505 error("cannot write request");
6506 else {
6507 string_iterator iter(*m);
6508 for (;;) {
6509 int c = iter.get(0);
6510 if (c == EOF)
6511 break;
6512 fputs(asciify(c), fp);
6514 fflush(fp);
6516 skip_line();
6519 void warnscale_request()
6521 if (has_arg()) {
6522 char c = tok.ch();
6523 if (c == 'u')
6524 warn_scale = 1.0;
6525 else if (c == 'i')
6526 warn_scale = (double)units_per_inch;
6527 else if (c == 'c')
6528 warn_scale = (double)units_per_inch / 2.54;
6529 else if (c == 'p')
6530 warn_scale = (double)units_per_inch / 72.0;
6531 else if (c == 'P')
6532 warn_scale = (double)units_per_inch / 6.0;
6533 else {
6534 warning(WARN_SCALE,
6535 "invalid scaling indicator `%1', using `i' instead", c);
6536 c = 'i';
6538 warn_scaling_indicator = c;
6540 skip_line();
6543 void spreadwarn_request()
6545 hunits n;
6546 if (has_arg() && get_hunits(&n, 'm')) {
6547 if (n < 0)
6548 n = 0;
6549 hunits em = curenv->get_size();
6550 spread_limit = (double)n.to_units()
6551 / (em.is_zero() ? hresolution : em.to_units());
6553 else
6554 spread_limit = -spread_limit - 1; // no arg toggles on/off without
6555 // changing value; we mirror at
6556 // -0.5 to make zero a valid value
6557 skip_line();
6560 static void init_charset_table()
6562 char buf[16];
6563 strcpy(buf, "char");
6564 for (int i = 0; i <= UCHAR_MAX; i++) {
6565 strcpy(buf + 4, i_to_a(i));
6566 charset_table[i] = get_charinfo(symbol(buf));
6567 charset_table[i]->set_ascii_code(i);
6568 if (csalpha(i))
6569 charset_table[i]->set_hyphenation_code(cmlower(i));
6571 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
6572 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
6573 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
6574 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
6575 charset_table['"']->set_flags(charinfo::TRANSPARENT);
6576 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
6577 charset_table[')']->set_flags(charinfo::TRANSPARENT);
6578 charset_table[']']->set_flags(charinfo::TRANSPARENT);
6579 charset_table['*']->set_flags(charinfo::TRANSPARENT);
6580 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
6581 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
6582 get_charinfo(symbol("cq"))->set_flags(charinfo::TRANSPARENT);
6583 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
6584 get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
6585 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6586 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6587 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6588 get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6589 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6590 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
6591 page_character = charset_table['%'];
6594 static void init_hpf_code_table()
6596 for (unsigned char i = 0; i <= UCHAR_MAX; ++i)
6597 hpf_code_table[i] = cmlower(i);
6600 static void do_translate(int translate_transparent, int translate_input)
6602 tok.skip();
6603 while (!tok.newline() && !tok.eof()) {
6604 if (tok.space()) {
6605 // This is a really bizarre troff feature.
6606 tok.next();
6607 translate_space_to_dummy = tok.dummy();
6608 if (tok.newline() || tok.eof())
6609 break;
6610 tok.next();
6611 continue;
6613 charinfo *ci1 = tok.get_char(1);
6614 if (ci1 == 0)
6615 break;
6616 tok.next();
6617 if (tok.newline() || tok.eof()) {
6618 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6619 translate_transparent);
6620 break;
6622 if (tok.space())
6623 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6624 translate_transparent);
6625 else if (tok.stretchable_space())
6626 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
6627 translate_transparent);
6628 else if (tok.dummy())
6629 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
6630 translate_transparent);
6631 else if (tok.hyphen_indicator())
6632 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
6633 translate_transparent);
6634 else {
6635 charinfo *ci2 = tok.get_char(1);
6636 if (ci2 == 0)
6637 break;
6638 if (ci1 == ci2)
6639 ci1->set_translation(0, translate_transparent, translate_input);
6640 else
6641 ci1->set_translation(ci2, translate_transparent, translate_input);
6643 tok.next();
6645 skip_line();
6648 void translate()
6650 do_translate(1, 0);
6653 void translate_no_transparent()
6655 do_translate(0, 0);
6658 void translate_input()
6660 do_translate(1, 1);
6663 void char_flags()
6665 int flags;
6666 if (get_integer(&flags))
6667 while (has_arg()) {
6668 charinfo *ci = tok.get_char(1);
6669 if (ci) {
6670 charinfo *tem = ci->get_translation();
6671 if (tem)
6672 ci = tem;
6673 ci->set_flags(flags);
6675 tok.next();
6677 skip_line();
6680 void hyphenation_code()
6682 tok.skip();
6683 while (!tok.newline() && !tok.eof()) {
6684 charinfo *ci = tok.get_char(1);
6685 if (ci == 0)
6686 break;
6687 tok.next();
6688 tok.skip();
6689 unsigned char c = tok.ch();
6690 if (c == 0) {
6691 error("hyphenation code must be ordinary character");
6692 break;
6694 if (csdigit(c)) {
6695 error("hyphenation code cannot be digit");
6696 break;
6698 ci->set_hyphenation_code(c);
6699 if (ci->get_translation()
6700 && ci->get_translation()->get_translation_input())
6701 ci->get_translation()->set_hyphenation_code(c);
6702 tok.next();
6703 tok.skip();
6705 skip_line();
6708 void hyphenation_patterns_file_code()
6710 tok.skip();
6711 while (!tok.newline() && !tok.eof()) {
6712 int n1, n2;
6713 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
6714 if (!has_arg()) {
6715 error("missing output hyphenation code");
6716 break;
6718 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
6719 hpf_code_table[n1] = n2;
6720 tok.skip();
6722 else {
6723 error("output hyphenation code must be integer in the range 0..255");
6724 break;
6727 else {
6728 error("input hyphenation code must be integer in the range 0..255");
6729 break;
6732 skip_line();
6735 charinfo *token::get_char(int required)
6737 if (type == TOKEN_CHAR)
6738 return charset_table[c];
6739 if (type == TOKEN_SPECIAL)
6740 return get_charinfo(nm);
6741 if (type == TOKEN_NUMBERED_CHAR)
6742 return get_charinfo_by_number(val);
6743 if (type == TOKEN_ESCAPE) {
6744 if (escape_char != 0)
6745 return charset_table[escape_char];
6746 else {
6747 error("`\\e' used while no current escape character");
6748 return 0;
6751 if (required) {
6752 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6753 warning(WARN_MISSING, "missing normal or special character");
6754 else
6755 error("normal or special character expected (got %1)", description());
6757 return 0;
6760 charinfo *get_optional_char()
6762 while (tok.space())
6763 tok.next();
6764 charinfo *ci = tok.get_char();
6765 if (!ci)
6766 check_missing_character();
6767 else
6768 tok.next();
6769 return ci;
6772 void check_missing_character()
6774 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6775 error("normal or special character expected (got %1): "
6776 "treated as missing",
6777 tok.description());
6780 // this is for \Z
6782 int token::add_to_node_list(node **pp)
6784 hunits w;
6785 int s;
6786 node *n = 0;
6787 switch (type) {
6788 case TOKEN_CHAR:
6789 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6790 break;
6791 case TOKEN_DUMMY:
6792 n = new dummy_node;
6793 break;
6794 case TOKEN_ESCAPE:
6795 if (escape_char != 0)
6796 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6797 break;
6798 case TOKEN_HYPHEN_INDICATOR:
6799 *pp = (*pp)->add_discretionary_hyphen();
6800 break;
6801 case TOKEN_ITALIC_CORRECTION:
6802 *pp = (*pp)->add_italic_correction(&w);
6803 break;
6804 case TOKEN_LEFT_BRACE:
6805 break;
6806 case TOKEN_MARK_INPUT:
6807 set_number_reg(nm, curenv->get_input_line_position().to_units());
6808 break;
6809 case TOKEN_NODE:
6810 n = nd;
6811 nd = 0;
6812 break;
6813 case TOKEN_NUMBERED_CHAR:
6814 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6815 break;
6816 case TOKEN_RIGHT_BRACE:
6817 break;
6818 case TOKEN_SPACE:
6819 n = new hmotion_node(curenv->get_space_width(),
6820 curenv->get_fill_color());
6821 break;
6822 case TOKEN_SPECIAL:
6823 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6824 break;
6825 case TOKEN_STRETCHABLE_SPACE:
6826 n = new unbreakable_space_node(curenv->get_space_width(),
6827 curenv->get_fill_color());
6828 break;
6829 case TOKEN_UNSTRETCHABLE_SPACE:
6830 n = new space_char_hmotion_node(curenv->get_space_width(),
6831 curenv->get_fill_color());
6832 break;
6833 case TOKEN_TRANSPARENT_DUMMY:
6834 n = new transparent_dummy_node;
6835 break;
6836 case TOKEN_ZERO_WIDTH_BREAK:
6837 n = new space_node(H0, curenv->get_fill_color());
6838 n->freeze_space();
6839 n->is_escape_colon();
6840 break;
6841 default:
6842 return 0;
6844 if (n) {
6845 n->next = *pp;
6846 *pp = n;
6848 return 1;
6851 void token::process()
6853 if (possibly_handle_first_page_transition())
6854 return;
6855 switch (type) {
6856 case TOKEN_BACKSPACE:
6857 curenv->add_node(new hmotion_node(-curenv->get_space_width(),
6858 curenv->get_fill_color()));
6859 break;
6860 case TOKEN_CHAR:
6861 curenv->add_char(charset_table[c]);
6862 break;
6863 case TOKEN_DUMMY:
6864 curenv->add_node(new dummy_node);
6865 break;
6866 case TOKEN_EMPTY:
6867 assert(0);
6868 break;
6869 case TOKEN_EOF:
6870 assert(0);
6871 break;
6872 case TOKEN_ESCAPE:
6873 if (escape_char != 0)
6874 curenv->add_char(charset_table[escape_char]);
6875 break;
6876 case TOKEN_BEGIN_TRAP:
6877 case TOKEN_END_TRAP:
6878 case TOKEN_PAGE_EJECTOR:
6879 // these are all handled in process_input_stack()
6880 break;
6881 case TOKEN_HYPHEN_INDICATOR:
6882 curenv->add_hyphen_indicator();
6883 break;
6884 case TOKEN_INTERRUPT:
6885 curenv->interrupt();
6886 break;
6887 case TOKEN_ITALIC_CORRECTION:
6888 curenv->add_italic_correction();
6889 break;
6890 case TOKEN_LEADER:
6891 curenv->handle_tab(1);
6892 break;
6893 case TOKEN_LEFT_BRACE:
6894 break;
6895 case TOKEN_MARK_INPUT:
6896 set_number_reg(nm, curenv->get_input_line_position().to_units());
6897 break;
6898 case TOKEN_NEWLINE:
6899 curenv->newline();
6900 break;
6901 case TOKEN_NODE:
6902 curenv->add_node(nd);
6903 nd = 0;
6904 break;
6905 case TOKEN_NUMBERED_CHAR:
6906 curenv->add_char(get_charinfo_by_number(val));
6907 break;
6908 case TOKEN_REQUEST:
6909 // handled in process_input_stack()
6910 break;
6911 case TOKEN_RIGHT_BRACE:
6912 break;
6913 case TOKEN_SPACE:
6914 curenv->space();
6915 break;
6916 case TOKEN_SPECIAL:
6917 curenv->add_char(get_charinfo(nm));
6918 break;
6919 case TOKEN_SPREAD:
6920 curenv->spread();
6921 break;
6922 case TOKEN_STRETCHABLE_SPACE:
6923 curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
6924 curenv->get_fill_color()));
6925 break;
6926 case TOKEN_UNSTRETCHABLE_SPACE:
6927 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
6928 curenv->get_fill_color()));
6929 break;
6930 case TOKEN_TAB:
6931 curenv->handle_tab(0);
6932 break;
6933 case TOKEN_TRANSPARENT:
6934 break;
6935 case TOKEN_TRANSPARENT_DUMMY:
6936 curenv->add_node(new transparent_dummy_node);
6937 break;
6938 case TOKEN_ZERO_WIDTH_BREAK:
6940 node *tmp = new space_node(H0, curenv->get_fill_color());
6941 tmp->freeze_space();
6942 tmp->is_escape_colon();
6943 curenv->add_node(tmp);
6944 break;
6946 default:
6947 assert(0);
6951 class nargs_reg
6952 : public reg
6954 public:
6955 const char *get_string();
6958 const char *nargs_reg::get_string()
6960 return i_to_a(input_stack::nargs());
6963 class lineno_reg
6964 : public reg
6966 public:
6967 const char *get_string();
6970 const char *lineno_reg::get_string()
6972 int line;
6973 const char *file;
6974 if (!input_stack::get_location(0, &file, &line))
6975 line = 0;
6976 return i_to_a(line);
6979 class writable_lineno_reg
6980 : public general_reg
6982 public:
6983 writable_lineno_reg();
6984 void set_value(units);
6985 int get_value(units *);
6988 writable_lineno_reg::writable_lineno_reg()
6992 int writable_lineno_reg::get_value(units *res)
6994 int line;
6995 const char *file;
6996 if (!input_stack::get_location(0, &file, &line))
6997 return 0;
6998 *res = line;
6999 return 1;
7002 void writable_lineno_reg::set_value(units n)
7004 input_stack::set_location(0, n);
7007 class filename_reg
7008 : public reg
7010 public:
7011 const char *get_string();
7014 const char *filename_reg::get_string()
7016 int line;
7017 const char *file;
7018 if (input_stack::get_location(0, &file, &line))
7019 return file;
7020 else
7021 return 0;
7024 class break_flag_reg
7025 : public reg
7027 public:
7028 const char *get_string();
7031 const char *break_flag_reg::get_string()
7033 return i_to_a(input_stack::get_break_flag());
7036 class constant_reg
7037 : public reg
7039 const char *s;
7041 public:
7042 constant_reg(const char *);
7043 const char *get_string();
7046 constant_reg::constant_reg(const char *p) : s(p)
7050 const char *constant_reg::get_string()
7052 return s;
7055 constant_int_reg::constant_int_reg(int *q) : p(q)
7059 const char *constant_int_reg::get_string()
7061 return i_to_a(*p);
7064 void abort_request()
7066 int c;
7067 if (tok.eof())
7068 c = EOF;
7069 else if (tok.newline())
7070 c = '\n';
7071 else {
7072 while ((c = get_copy(0)) == ' ')
7075 if (c == EOF || c == '\n')
7076 fputs("User Abort.", stderr);
7077 else {
7078 for (; c != '\n' && c != EOF; c = get_copy(0))
7079 fputs(asciify(c), stderr);
7081 fputc('\n', stderr);
7082 cleanup_and_exit(1);
7085 char *read_string()
7087 int len = 256;
7088 char *s = new char[len];
7089 int c;
7090 while ((c = get_copy(0)) == ' ')
7092 int i = 0;
7093 while (c != '\n' && c != EOF) {
7094 if (!invalid_input_char(c)) {
7095 if (i + 2 > len) {
7096 char *tem = s;
7097 s = new char[len*2];
7098 memcpy(s, tem, len);
7099 len *= 2;
7100 a_delete tem;
7102 s[i++] = c;
7104 c = get_copy(0);
7106 s[i] = '\0';
7107 tok.next();
7108 if (i == 0) {
7109 a_delete s;
7110 return 0;
7112 return s;
7115 void pipe_output()
7117 if (!unsafe_flag) {
7118 error(".pi request not allowed in safer mode");
7119 skip_line();
7121 else {
7122 #ifdef POPEN_MISSING
7123 error("pipes not available on this system");
7124 skip_line();
7125 #else /* not POPEN_MISSING */
7126 if (the_output) {
7127 error("can't pipe: output already started");
7128 skip_line();
7130 else {
7131 char *pc;
7132 if ((pc = read_string()) == 0)
7133 error("can't pipe to empty command");
7134 if (pipe_command) {
7135 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
7136 strcpy(s, pipe_command);
7137 strcat(s, "|");
7138 strcat(s, pc);
7139 a_delete pipe_command;
7140 a_delete pc;
7141 pipe_command = s;
7143 else
7144 pipe_command = pc;
7146 #endif /* not POPEN_MISSING */
7150 static int system_status;
7152 void system_request()
7154 if (!unsafe_flag) {
7155 error(".sy request not allowed in safer mode");
7156 skip_line();
7158 else {
7159 char *command = read_string();
7160 if (!command)
7161 error("empty command");
7162 else {
7163 system_status = system(command);
7164 a_delete command;
7169 void copy_file()
7171 if (curdiv == topdiv && topdiv->before_first_page) {
7172 handle_initial_request(COPY_FILE_REQUEST);
7173 return;
7175 symbol filename = get_long_name(1);
7176 while (!tok.newline() && !tok.eof())
7177 tok.next();
7178 if (break_flag)
7179 curenv->do_break();
7180 if (!filename.is_null())
7181 curdiv->copy_file(filename.contents());
7182 tok.next();
7185 #ifdef COLUMN
7186 void vjustify()
7188 if (curdiv == topdiv && topdiv->before_first_page) {
7189 handle_initial_request(VJUSTIFY_REQUEST);
7190 return;
7192 symbol type = get_long_name(1);
7193 if (!type.is_null())
7194 curdiv->vjustify(type);
7195 skip_line();
7197 #endif
7199 void transparent_file()
7201 if (curdiv == topdiv && topdiv->before_first_page) {
7202 handle_initial_request(TRANSPARENT_FILE_REQUEST);
7203 return;
7205 symbol filename = get_long_name(1);
7206 while (!tok.newline() && !tok.eof())
7207 tok.next();
7208 if (break_flag)
7209 curenv->do_break();
7210 if (!filename.is_null()) {
7211 file_case *fcp = include_search_path
7212 .open_file_cautious(filename.contents());
7213 if (fcp == NULL)
7214 error("can't open `%1': %2", filename.contents(), strerror(errno));
7215 else {
7216 int bol = 1;
7217 for (;;) {
7218 int c = fcp->get_c();
7219 if (c == EOF)
7220 break;
7221 if (invalid_input_char(c))
7222 warning(WARN_INPUT, "invalid input character code %1", int(c));
7223 else {
7224 curdiv->transparent_output(c);
7225 bol = c == '\n';
7228 if (!bol)
7229 curdiv->transparent_output('\n');
7230 delete fcp;
7233 tok.next();
7236 class page_range
7238 int first;
7239 int last;
7241 public:
7242 page_range *next;
7244 page_range(int, int, page_range *);
7245 int contains(int n);
7248 page_range::page_range(int i, int j, page_range *p)
7249 : first(i), last(j), next(p)
7253 int page_range::contains(int n)
7255 return n >= first && (last <= 0 || n <= last);
7258 page_range *output_page_list = 0;
7260 int in_output_page_list(int n)
7262 if (!output_page_list)
7263 return 1;
7264 for (page_range *p = output_page_list; p; p = p->next)
7265 if (p->contains(n))
7266 return 1;
7267 return 0;
7270 static void parse_output_page_list(char *p)
7272 for (;;) {
7273 int i;
7274 if (*p == '-')
7275 i = 1;
7276 else if (csdigit(*p)) {
7277 i = 0;
7279 i = i*10 + *p++ - '0';
7280 while (csdigit(*p));
7282 else
7283 break;
7284 int j;
7285 if (*p == '-') {
7286 p++;
7287 j = 0;
7288 if (csdigit(*p)) {
7290 j = j*10 + *p++ - '0';
7291 while (csdigit(*p));
7294 else
7295 j = i;
7296 if (j == 0)
7297 last_page_number = -1;
7298 else if (last_page_number >= 0 && j > last_page_number)
7299 last_page_number = j;
7300 output_page_list = new page_range(i, j, output_page_list);
7301 if (*p != ',')
7302 break;
7303 ++p;
7305 if (*p != '\0') {
7306 error("bad output page list");
7307 output_page_list = 0;
7311 static file_case *open_mac_file(const char *mac)
7313 // Try first FOOBAR.tmac, then tmac.FOOBAR
7314 char *s = new char[strlen(mac) + strlen(MACRO_POSTFIX) +1];
7315 strcpy(s, mac);
7316 strcat(s, MACRO_POSTFIX);
7318 file_case *fcp;
7319 if ((fcp = mac_path->open_file(s, fcp->fc_take_path)) == NULL) {
7320 s = new char[strlen(mac) + strlen(MACRO_PREFIX) +1];
7321 strcpy(s, MACRO_PREFIX);
7322 strcat(s, mac);
7323 fcp = mac_path->open_file(s, fcp->fc_take_path);
7325 return fcp;
7328 static void process_macro_file(const char *mac)
7330 file_case *fcp = open_mac_file(mac);
7331 if (fcp == NULL)
7332 fatal("can't find macro file %1", mac);
7333 const char *s = symbol(fcp->path()).contents();
7334 input_stack::push(new file_iterator(fcp, s));
7335 tok.next();
7336 process_input_stack();
7339 static void process_startup_file(const char *filename)
7341 search_path *orig_mac_path = mac_path;
7342 mac_path = &config_macro_path;
7343 file_case *fcp;
7344 if ((fcp = mac_path->open_file(filename)) != NULL) {
7345 input_stack::push(new file_iterator(fcp, symbol(fcp->path()).contents()));
7346 tok.next();
7347 process_input_stack();
7349 mac_path = orig_mac_path;
7352 void macro_source()
7354 symbol nm = get_long_name(1);
7355 if (nm.is_null())
7356 skip_line();
7357 else {
7358 while (!tok.newline() && !tok.eof())
7359 tok.next();
7360 // .mso doesn't (and cannot) go through open_mac_file, so we
7361 // need to do it here manually: If we have tmac.FOOBAR, try
7362 // FOOBAR.tmac and vice versa
7363 file_case *fcp;
7364 if ((fcp = mac_path->open_file(nm.contents())) == NULL) {
7365 const char *fn = nm.contents();
7367 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
7368 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
7369 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
7370 strcat(s, MACRO_POSTFIX);
7371 fcp = mac_path->open_file(s, fcp->fc_take_path);
7374 if (fcp == NULL) {
7375 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
7376 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
7377 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
7378 strcpy(s, MACRO_PREFIX);
7379 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
7380 fcp = mac_path->open_file(s, fcp->fc_take_path);
7385 if (fcp != NULL)
7386 input_stack::push(new file_iterator(fcp, symbol(fcp->path()).contents()));
7387 else
7388 warning(WARN_FILE, "can't find macro file `%1'", nm.contents());
7389 tok.next();
7393 static void process_input_file(const char *name)
7395 file_case *fcp;
7396 if ((fcp = include_search_path.open_file_cautious(name)) == NULL) {
7397 assert(strcmp(name, "-"));
7398 fatal("can't open `%1': %2", name, strerror(errno));
7400 input_stack::push(new file_iterator(fcp, name));
7401 tok.next();
7402 process_input_stack();
7405 // make sure the_input is empty before calling this
7407 static int evaluate_expression(const char *expr, units *res)
7409 input_stack::push(make_temp_iterator(expr));
7410 tok.next();
7411 int success = get_number(res, 'u');
7412 while (input_stack::get(0) != EOF)
7414 return success;
7417 static void do_register_assignment(const char *s)
7419 const char *p = strchr(s, '=');
7420 if (!p) {
7421 char buf[2];
7422 buf[0] = s[0];
7423 buf[1] = 0;
7424 units n;
7425 if (evaluate_expression(s + 1, &n))
7426 set_number_reg(buf, n);
7428 else {
7429 char *buf = new char[p - s + 1];
7430 memcpy(buf, s, p - s);
7431 buf[p - s] = 0;
7432 units n;
7433 if (evaluate_expression(p + 1, &n))
7434 set_number_reg(buf, n);
7435 a_delete buf;
7439 static void set_string(const char *name, const char *value)
7441 macro *m = new macro;
7442 for (const char *p = value; *p; p++)
7443 if (!invalid_input_char((unsigned char)*p))
7444 m->append(*p);
7445 request_dictionary.define(name, m);
7448 static void do_string_assignment(const char *s)
7450 const char *p = strchr(s, '=');
7451 if (!p) {
7452 char buf[2];
7453 buf[0] = s[0];
7454 buf[1] = 0;
7455 set_string(buf, s + 1);
7457 else {
7458 char *buf = new char[p - s + 1];
7459 memcpy(buf, s, p - s);
7460 buf[p - s] = 0;
7461 set_string(buf, p + 1);
7462 a_delete buf;
7466 class string_list
7468 public:
7469 const char *s;
7470 string_list *next;
7472 string_list(const char *ss) : s(ss), next(0) {}
7475 #if 0
7476 static void prepend_string(const char *s, string_list **p)
7478 string_list *l = new string_list(s);
7479 l->next = *p;
7480 *p = l;
7482 #endif
7484 static void add_string(const char *s, string_list **p)
7486 while (*p)
7487 p = &((*p)->next);
7488 *p = new string_list(s);
7491 void usage(FILE *stream, const char *prog)
7493 fprintf(stream,
7494 "Synopsis: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
7495 " -rcn -Tname -Fdir -Idir -Mdir [files...]\n",
7496 prog);
7499 int main(int argc, char **argv)
7501 program_name = argv[0];
7502 static char stderr_buf[BUFSIZ];
7503 setbuf(stderr, stderr_buf);
7504 int c;
7505 string_list *macros = 0;
7506 string_list *register_assignments = 0;
7507 string_list *string_assignments = 0;
7508 int iflag = 0;
7509 int tflag = 0;
7510 int fflag = 0;
7511 int nflag = 0;
7512 int no_rc = 0; // don't process troffrc and troffrc-end
7513 int next_page_number = 0; // pacify compiler
7514 opterr = 0;
7515 hresolution = vresolution = 1;
7516 // restore $PATH if called from groff
7517 char* groff_path = getenv(U_ROFF_PATH__);
7518 if (groff_path) {
7519 string e = "PATH";
7520 e += '=';
7521 if (*groff_path)
7522 e += groff_path;
7523 e += '\0';
7524 if (putenv(strsave(e.contents())))
7525 fatal("putenv failed");
7527 static const struct option long_options[] = {
7528 { "help", no_argument, 0, CHAR_MAX + 1 },
7529 { "version", no_argument, 0, 'v' },
7530 { 0, 0, 0, 0 }
7532 #ifdef DEBUGGING
7533 # define DEBUG_OPTION "D"
7534 #endif
7535 while ((c = getopt_long(argc, argv,
7536 "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
7537 DEBUG_OPTION, long_options, 0))
7538 != EOF)
7539 switch(c) {
7540 case 'v':
7542 printf(L_TROFF " (" T_ROFF ") v" VERSION);
7543 exit(0);
7544 break;
7546 case 'I':
7547 // Search path for .psbb files
7548 // and most other non-system input files.
7549 include_search_path.command_line_dir(optarg);
7550 break;
7551 case 'T':
7552 device = optarg;
7553 tflag = 1;
7554 is_html = (strcmp(device, "html") == 0);
7555 break;
7556 case 'C':
7557 compatible_flag = 1;
7558 // fall through
7559 case 'c':
7560 color_flag = 0;
7561 break;
7562 case 'M':
7563 macro_path.command_line_dir(optarg);
7564 safer_macro_path.command_line_dir(optarg);
7565 config_macro_path.command_line_dir(optarg);
7566 break;
7567 case 'F':
7568 font::command_line_font_dir(optarg);
7569 break;
7570 case 'm':
7571 add_string(optarg, &macros);
7572 break;
7573 case 'E':
7574 inhibit_errors = 1;
7575 break;
7576 case 'R':
7577 no_rc = 1;
7578 break;
7579 case 'w':
7580 enable_warning(optarg);
7581 break;
7582 case 'W':
7583 disable_warning(optarg);
7584 break;
7585 case 'i':
7586 iflag = 1;
7587 break;
7588 case 'b':
7589 backtrace_flag = 1;
7590 break;
7591 case 'a':
7592 ascii_output_flag = 1;
7593 break;
7594 case 'z':
7595 suppress_output_flag = 1;
7596 break;
7597 case 'n':
7598 if (sscanf(optarg, "%d", &next_page_number) == 1)
7599 nflag++;
7600 else
7601 error("bad page number");
7602 break;
7603 case 'o':
7604 parse_output_page_list(optarg);
7605 break;
7606 case 'd':
7607 if (*optarg == '\0')
7608 error("`-d' requires non-empty argument");
7609 else
7610 add_string(optarg, &string_assignments);
7611 break;
7612 case 'r':
7613 if (*optarg == '\0')
7614 error("`-r' requires non-empty argument");
7615 else
7616 add_string(optarg, &register_assignments);
7617 break;
7618 case 'f':
7619 default_family = symbol(optarg);
7620 fflag = 1;
7621 break;
7622 case 'q':
7623 case 's':
7624 case 't':
7625 // silently ignore these
7626 break;
7627 case 'U':
7628 unsafe_flag = 1; // unsafe behaviour
7629 break;
7630 #ifdef DEBUGGING
7631 case 'D':
7632 debug_state = 1;
7633 break;
7634 #endif
7635 case CHAR_MAX + 1: // --help
7636 usage(stdout, argv[0]);
7637 exit(0);
7638 break;
7639 case '?':
7640 usage(stderr, argv[0]);
7641 exit(1);
7642 break; // never reached
7643 default:
7644 assert(0);
7646 if (unsafe_flag)
7647 mac_path = &macro_path;
7648 set_string(".T", device);
7649 init_charset_table();
7650 init_hpf_code_table();
7651 if (!font::load_desc())
7652 fatal("sorry, I can't continue");
7653 units_per_inch = font::res;
7654 hresolution = font::hor;
7655 vresolution = font::vert;
7656 sizescale = font::sizescale;
7657 tcommand_flag = font::tcommand;
7658 warn_scale = (double)units_per_inch;
7659 warn_scaling_indicator = 'i';
7660 if (!fflag && font::family != 0 && *font::family != '\0')
7661 default_family = symbol(font::family);
7662 font_size::init_size_table(font::sizes);
7663 int i;
7664 int j = 1;
7665 if (font::style_table) {
7666 for (i = 0; font::style_table[i]; i++)
7667 mount_style(j++, symbol(font::style_table[i]));
7669 for (i = 0; font::font_name_table[i]; i++, j++)
7670 // In the DESC file a font name of 0 (zero) means leave this
7671 // position empty.
7672 if (strcmp(font::font_name_table[i], "0") != 0)
7673 mount_font(j, symbol(font::font_name_table[i]));
7674 curdiv = topdiv = new top_level_diversion;
7675 if (nflag)
7676 topdiv->set_next_page_number(next_page_number);
7677 init_input_requests();
7678 init_env_requests();
7679 init_div_requests();
7680 #ifdef COLUMN
7681 init_column_requests();
7682 #endif /* COLUMN */
7683 init_node_requests();
7684 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
7685 init_registers();
7686 init_reg_requests();
7687 init_hyphen_requests();
7688 init_environments();
7689 while (string_assignments) {
7690 do_string_assignment(string_assignments->s);
7691 string_list *tem = string_assignments;
7692 string_assignments = string_assignments->next;
7693 delete tem;
7695 while (register_assignments) {
7696 do_register_assignment(register_assignments->s);
7697 string_list *tem = register_assignments;
7698 register_assignments = register_assignments->next;
7699 delete tem;
7701 if (!no_rc)
7702 process_startup_file(INITIAL_STARTUP_FILE);
7703 while (macros) {
7704 process_macro_file(macros->s);
7705 string_list *tem = macros;
7706 macros = macros->next;
7707 delete tem;
7709 if (!no_rc)
7710 process_startup_file(FINAL_STARTUP_FILE);
7711 for (i = optind; i < argc; i++)
7712 process_input_file(argv[i]);
7713 if (optind >= argc || iflag)
7714 process_input_file("-");
7715 exit_troff();
7716 return 0; // not reached
7719 void warn_request()
7721 int n;
7722 if (has_arg() && get_integer(&n)) {
7723 if (n & ~WARN_TOTAL) {
7724 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
7725 n &= WARN_TOTAL;
7727 warning_mask = n;
7729 else
7730 warning_mask = WARN_TOTAL;
7731 skip_line();
7734 static void init_registers()
7736 #ifdef LONG_FOR_TIME_T
7737 long
7738 #else /* not LONG_FOR_TIME_T */
7739 time_t
7740 #endif /* not LONG_FOR_TIME_T */
7741 t = time(0);
7742 // Use struct here to work around misfeature in old versions of g++.
7743 struct tm *tt = localtime(&t);
7744 set_number_reg("seconds", int(tt->tm_sec));
7745 set_number_reg("minutes", int(tt->tm_min));
7746 set_number_reg("hours", int(tt->tm_hour));
7747 set_number_reg("dw", int(tt->tm_wday + 1));
7748 set_number_reg("dy", int(tt->tm_mday));
7749 set_number_reg("mo", int(tt->tm_mon + 1));
7750 set_number_reg("year", int(1900 + tt->tm_year));
7751 set_number_reg("yr", int(tt->tm_year));
7752 set_number_reg("$$", getpid());
7753 number_reg_dictionary.define(".A",
7754 new constant_reg(ascii_output_flag
7755 ? "1"
7756 : "0"));
7760 * registers associated with \O
7763 static int output_reg_minx_contents = -1;
7764 static int output_reg_miny_contents = -1;
7765 static int output_reg_maxx_contents = -1;
7766 static int output_reg_maxy_contents = -1;
7768 void check_output_limits(int x, int y)
7770 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
7771 output_reg_minx_contents = x;
7772 if (x > output_reg_maxx_contents)
7773 output_reg_maxx_contents = x;
7774 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
7775 output_reg_miny_contents = y;
7776 if (y > output_reg_maxy_contents)
7777 output_reg_maxy_contents = y;
7780 void reset_output_registers()
7782 output_reg_minx_contents = -1;
7783 output_reg_miny_contents = -1;
7784 output_reg_maxx_contents = -1;
7785 output_reg_maxy_contents = -1;
7788 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
7790 *minx = output_reg_minx_contents;
7791 *miny = output_reg_miny_contents;
7792 *maxx = output_reg_maxx_contents;
7793 *maxy = output_reg_maxy_contents;
7796 void init_input_requests()
7798 init_request("ab", abort_request);
7799 init_request("als", alias_macro);
7800 init_request("am", append_macro);
7801 init_request("am1", append_nocomp_macro);
7802 init_request("ami", append_indirect_macro);
7803 init_request("ami1", append_indirect_nocomp_macro);
7804 init_request("as", append_string);
7805 init_request("as1", append_nocomp_string);
7806 init_request("asciify", asciify_macro);
7807 init_request("backtrace", backtrace_request);
7808 init_request("blm", blank_line_macro);
7809 init_request("break", while_break_request);
7810 init_request("cf", copy_file);
7811 init_request("cflags", char_flags);
7812 init_request("char", define_character);
7813 init_request("chop", chop_macro);
7814 init_request("close", close_request);
7815 init_request("color", activate_color);
7816 init_request("composite", composite_request);
7817 init_request("continue", while_continue_request);
7818 init_request("cp", compatible);
7819 init_request("de", define_macro);
7820 init_request("de1", define_nocomp_macro);
7821 init_request("defcolor", define_color);
7822 init_request("dei", define_indirect_macro);
7823 init_request("dei1", define_indirect_nocomp_macro);
7824 init_request("device", device_request);
7825 init_request("devicem", device_macro_request);
7826 init_request("do", do_request);
7827 init_request("ds", define_string);
7828 init_request("ds1", define_nocomp_string);
7829 init_request("ec", set_escape_char);
7830 init_request("ecr", restore_escape_char);
7831 init_request("ecs", save_escape_char);
7832 init_request("el", else_request);
7833 init_request("em", end_macro);
7834 init_request("eo", escape_off);
7835 init_request("ex", exit_request);
7836 init_request("fchar", define_fallback_character);
7837 #ifdef WIDOW_CONTROL
7838 init_request("fpl", flush_pending_lines);
7839 #endif
7840 init_request("hcode", hyphenation_code);
7841 init_request("hpfcode", hyphenation_patterns_file_code);
7842 init_request("ie", if_else_request);
7843 init_request("if", if_request);
7844 init_request("ig", ignore);
7845 init_request("length", length_request);
7846 init_request("lf", line_file);
7847 init_request("mso", macro_source);
7848 init_request("nop", nop_request);
7849 init_request("nroff", nroff_request);
7850 init_request("nx", next_file);
7851 init_request("open", open_request);
7852 init_request("opena", opena_request);
7853 init_request("output", output_request);
7854 init_request("pc", set_page_character);
7855 init_request("pi", pipe_output);
7856 init_request("pm", print_macros);
7857 init_request("psbb", ps_bbox_request);
7858 #ifndef POPEN_MISSING
7859 init_request("pso", pipe_source);
7860 #endif
7861 init_request("rchar", remove_character);
7862 init_request("rd", read_request);
7863 init_request("return", return_macro_request);
7864 init_request("rm", remove_macro);
7865 init_request("rn", rename_macro);
7866 init_request("schar", define_special_character);
7867 init_request("shift", shift);
7868 init_request("so", source);
7869 init_request("spreadwarn", spreadwarn_request);
7870 init_request("substring", substring_request);
7871 init_request("sy", system_request);
7872 init_request("tag", tag);
7873 init_request("taga", taga);
7874 init_request("tm", terminal);
7875 init_request("tm1", terminal1);
7876 init_request("tmc", terminal_continue);
7877 init_request("tr", translate);
7878 init_request("trf", transparent_file);
7879 init_request("trin", translate_input);
7880 init_request("trnt", translate_no_transparent);
7881 init_request("troff", troff_request);
7882 init_request("unformat", unformat_macro);
7883 #ifdef COLUMN
7884 init_request("vj", vjustify);
7885 #endif
7886 init_request("warn", warn_request);
7887 init_request("warnscale", warnscale_request);
7888 init_request("while", while_request);
7889 init_request("write", write_request);
7890 init_request("writec", write_request_continue);
7891 init_request("writem", write_macro_request);
7892 number_reg_dictionary.define(".$", new nargs_reg);
7893 number_reg_dictionary.define(".br", new break_flag_reg);
7894 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
7895 number_reg_dictionary.define(".O", new variable_reg(&begin_level));
7896 number_reg_dictionary.define(".c", new lineno_reg);
7897 number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
7898 number_reg_dictionary.define(".F", new filename_reg);
7899 number_reg_dictionary.define(".g", new constant_reg("1"));
7900 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
7901 number_reg_dictionary.define(".R", new constant_reg("10000"));
7902 number_reg_dictionary.define(".U", new constant_int_reg(&unsafe_flag));
7903 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
7904 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
7905 extern const char *major_version;
7906 number_reg_dictionary.define(".x", new constant_reg(major_version));
7907 extern const char *revision;
7908 number_reg_dictionary.define(".Y", new constant_reg(revision));
7909 extern const char *minor_version;
7910 number_reg_dictionary.define(".y", new constant_reg(minor_version));
7911 number_reg_dictionary.define("c.", new writable_lineno_reg);
7912 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
7913 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
7914 number_reg_dictionary.define("opmaxx",
7915 new variable_reg(&output_reg_maxx_contents));
7916 number_reg_dictionary.define("opmaxy",
7917 new variable_reg(&output_reg_maxy_contents));
7918 number_reg_dictionary.define("opminx",
7919 new variable_reg(&output_reg_minx_contents));
7920 number_reg_dictionary.define("opminy",
7921 new variable_reg(&output_reg_miny_contents));
7922 number_reg_dictionary.define("slimit",
7923 new variable_reg(&input_stack::limit));
7924 number_reg_dictionary.define("systat", new variable_reg(&system_status));
7925 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
7926 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
7929 object_dictionary request_dictionary(501);
7931 void init_request(const char *s, REQUEST_FUNCP f)
7933 request_dictionary.define(s, new request(f));
7936 static request_or_macro *lookup_request(symbol nm)
7938 assert(!nm.is_null());
7939 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
7940 if (p == 0) {
7941 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
7942 p = new macro;
7943 request_dictionary.define(nm, p);
7945 return p;
7948 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7950 // Don't interpret character definitions in compatible mode.
7951 int old_compatible_flag = compatible_flag;
7952 compatible_flag = 0;
7953 int old_escape_char = escape_char;
7954 escape_char = '\\';
7955 macro *mac = ci->set_macro(0);
7956 assert(mac != 0);
7957 environment *oldenv = curenv;
7958 environment env(envp);
7959 curenv = &env;
7960 curenv->set_composite();
7961 token old_tok = tok;
7962 input_stack::add_boundary();
7963 string_iterator *si =
7964 new string_iterator(*mac, "composite character", ci->nm);
7965 input_stack::push(si);
7966 // we don't use process_input_stack, because we don't want to recognise
7967 // requests
7968 for (;;) {
7969 tok.next();
7970 if (tok.eof())
7971 break;
7972 if (tok.newline()) {
7973 error("composite character mustn't contain newline");
7974 while (!tok.eof())
7975 tok.next();
7976 break;
7978 else
7979 tok.process();
7981 node *n = curenv->extract_output_line();
7982 input_stack::remove_boundary();
7983 ci->set_macro(mac);
7984 tok = old_tok;
7985 curenv = oldenv;
7986 compatible_flag = old_compatible_flag;
7987 escape_char = old_escape_char;
7988 have_input = 0;
7989 return n;
7992 static node *read_draw_node()
7994 token start;
7995 start.next();
7996 if (!start.delimiter(1)){
7997 do {
7998 tok.next();
7999 } while (tok != start && !tok.newline() && !tok.eof());
8001 else {
8002 tok.next();
8003 if (tok == start)
8004 error("missing argument");
8005 else {
8006 unsigned char type = tok.ch();
8007 if (type == 'F') {
8008 read_color_draw_node(start);
8009 return 0;
8011 tok.next();
8012 int maxpoints = 10;
8013 hvpair *point = new hvpair[maxpoints];
8014 int npoints = 0;
8015 int no_last_v = 0;
8016 int err = 0;
8017 int i;
8018 for (i = 0; tok != start; i++) {
8019 if (i == maxpoints) {
8020 hvpair *oldpoint = point;
8021 point = new hvpair[maxpoints*2];
8022 for (int j = 0; j < maxpoints; j++)
8023 point[j] = oldpoint[j];
8024 maxpoints *= 2;
8025 a_delete oldpoint;
8027 if (!get_hunits(&point[i].h,
8028 type == 'f' || type == 't' ? 'u' : 'm')) {
8029 err = 1;
8030 break;
8032 ++npoints;
8033 tok.skip();
8034 point[i].v = V0;
8035 if (tok == start) {
8036 no_last_v = 1;
8037 break;
8039 if (!get_vunits(&point[i].v, 'v')) {
8040 err = 1;
8041 break;
8043 tok.skip();
8045 while (tok != start && !tok.newline() && !tok.eof())
8046 tok.next();
8047 if (!err) {
8048 switch (type) {
8049 case 'l':
8050 if (npoints != 1 || no_last_v) {
8051 error("two arguments needed for line");
8052 npoints = 1;
8054 break;
8055 case 'c':
8056 if (npoints != 1 || !no_last_v) {
8057 error("one argument needed for circle");
8058 npoints = 1;
8059 point[0].v = V0;
8061 break;
8062 case 'e':
8063 if (npoints != 1 || no_last_v) {
8064 error("two arguments needed for ellipse");
8065 npoints = 1;
8067 break;
8068 case 'a':
8069 if (npoints != 2 || no_last_v) {
8070 error("four arguments needed for arc");
8071 npoints = 2;
8073 break;
8074 case '~':
8075 if (no_last_v)
8076 error("even number of arguments needed for spline");
8077 break;
8078 case 'f':
8079 if (npoints != 1 || !no_last_v) {
8080 error("one argument needed for gray shade");
8081 npoints = 1;
8082 point[0].v = V0;
8084 default:
8085 // silently pass it through
8086 break;
8088 draw_node *dn = new draw_node(type, point, npoints,
8089 curenv->get_font_size(),
8090 curenv->get_glyph_color(),
8091 curenv->get_fill_color());
8092 a_delete point;
8093 return dn;
8095 else {
8096 a_delete point;
8100 return 0;
8103 static void read_color_draw_node(token &start)
8105 tok.next();
8106 if (tok == start) {
8107 error("missing color scheme");
8108 return;
8110 unsigned char scheme = tok.ch();
8111 tok.next();
8112 color *col = 0;
8113 char end = start.ch();
8114 switch (scheme) {
8115 case 'c':
8116 col = read_cmy(end);
8117 break;
8118 case 'd':
8119 col = &default_color;
8120 break;
8121 case 'g':
8122 col = read_gray(end);
8123 break;
8124 case 'k':
8125 col = read_cmyk(end);
8126 break;
8127 case 'r':
8128 col = read_rgb(end);
8129 break;
8131 if (col)
8132 curenv->set_fill_color(col);
8133 while (tok != start) {
8134 if (tok.newline() || tok.eof()) {
8135 warning(WARN_DELIM, "missing closing delimiter");
8136 input_stack::push(make_temp_iterator("\n"));
8137 break;
8139 tok.next();
8141 have_input = 1;
8144 static struct {
8145 const char *name;
8146 int mask;
8147 } warning_table[] = { // FIXME const? make fun-local; use jumptable??
8148 { "char", WARN_CHAR },
8149 { "range", WARN_RANGE },
8150 { "break", WARN_BREAK },
8151 { "delim", WARN_DELIM },
8152 { "el", WARN_EL },
8153 { "scale", WARN_SCALE },
8154 { "number", WARN_NUMBER },
8155 { "syntax", WARN_SYNTAX },
8156 { "tab", WARN_TAB },
8157 { "right-brace", WARN_RIGHT_BRACE },
8158 { "missing", WARN_MISSING },
8159 { "input", WARN_INPUT },
8160 { "escape", WARN_ESCAPE },
8161 { "space", WARN_SPACE },
8162 { "font", WARN_FONT },
8163 { "di", WARN_DI },
8164 { "mac", WARN_MAC },
8165 { "reg", WARN_REG },
8166 { "ig", WARN_IG },
8167 { "color", WARN_COLOR },
8168 { "file", WARN_FILE },
8169 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
8170 { "w", WARN_TOTAL },
8171 { "default", DEFAULT_WARNING_MASK },
8174 static int lookup_warning(const char *name)
8176 int rv = 0;
8177 for (size_t i = 0; i < NELEM(warning_table); ++i)
8178 if (!strcmp(name, warning_table[i].name)) {
8179 rv = warning_table[i].mask;
8180 break;
8182 return rv;
8185 static void enable_warning(const char *name)
8187 int mask = lookup_warning(name);
8188 if (mask)
8189 warning_mask |= mask;
8190 else
8191 error("unknown warning `%1'", name);
8194 static void disable_warning(const char *name)
8196 int mask = lookup_warning(name);
8197 if (mask)
8198 warning_mask &= ~mask;
8199 else
8200 error("unknown warning `%1'", name);
8203 static void copy_mode_error(const char *format,
8204 const errarg &arg1,
8205 const errarg &arg2,
8206 const errarg &arg3)
8208 if (ignoring) {
8209 static const char prefix[] = "(in ignored input) ";
8210 char *s = new char[sizeof(prefix) + strlen(format)];
8211 strcpy(s, prefix);
8212 strcat(s, format);
8213 warning(WARN_IG, s, arg1, arg2, arg3);
8214 a_delete s;
8216 else
8217 error(format, arg1, arg2, arg3);
8220 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
8222 static void do_error(error_type type,
8223 const char *format,
8224 const errarg &arg1,
8225 const errarg &arg2,
8226 const errarg &arg3)
8228 const char *filename;
8229 int lineno;
8230 if (inhibit_errors && type < FATAL)
8231 return;
8232 if (backtrace_flag)
8233 input_stack::backtrace();
8234 if (!get_file_line(&filename, &lineno))
8235 filename = 0;
8236 if (filename)
8237 errprint("%1:%2: ", filename, lineno);
8238 else if (program_name)
8239 fprintf(stderr, "%s: ", program_name);
8240 switch (type) {
8241 case FATAL:
8242 fputs("fatal error: ", stderr);
8243 break;
8244 case ERROR:
8245 break;
8246 case WARNING:
8247 fputs("warning: ", stderr);
8248 break;
8249 case OUTPUT_WARNING:
8250 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
8251 fprintf(stderr, "warning [p %d, %.1f%c",
8252 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
8253 if (topdiv != curdiv) {
8254 double fromtop1 = curdiv->get_vertical_position().to_units()
8255 / warn_scale;
8256 fprintf(stderr, ", div `%s', %.1f%c",
8257 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
8259 fprintf(stderr, "]: ");
8260 break;
8262 errprint(format, arg1, arg2, arg3);
8263 fputc('\n', stderr);
8264 fflush(stderr);
8265 if (type == FATAL)
8266 cleanup_and_exit(1);
8269 int warning(warning_type t,
8270 const char *format,
8271 const errarg &arg1,
8272 const errarg &arg2,
8273 const errarg &arg3)
8275 if ((t & warning_mask) != 0) {
8276 do_error(WARNING, format, arg1, arg2, arg3);
8277 return 1;
8279 else
8280 return 0;
8283 int output_warning(warning_type t,
8284 const char *format,
8285 const errarg &arg1,
8286 const errarg &arg2,
8287 const errarg &arg3)
8289 if ((t & warning_mask) != 0) {
8290 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
8291 return 1;
8293 else
8294 return 0;
8297 void error(const char *format,
8298 const errarg &arg1,
8299 const errarg &arg2,
8300 const errarg &arg3)
8302 do_error(ERROR, format, arg1, arg2, arg3);
8305 void fatal(const char *format,
8306 const errarg &arg1,
8307 const errarg &arg2,
8308 const errarg &arg3)
8310 do_error(FATAL, format, arg1, arg2, arg3);
8313 void fatal_with_file_and_line(const char *filename, int lineno,
8314 const char *format,
8315 const errarg &arg1,
8316 const errarg &arg2,
8317 const errarg &arg3)
8319 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
8320 errprint(format, arg1, arg2, arg3);
8321 fputc('\n', stderr);
8322 fflush(stderr);
8323 cleanup_and_exit(1);
8326 void error_with_file_and_line(const char *filename, int lineno,
8327 const char *format,
8328 const errarg &arg1,
8329 const errarg &arg2,
8330 const errarg &arg3)
8332 fprintf(stderr, "%s:%d: error: ", filename, lineno);
8333 errprint(format, arg1, arg2, arg3);
8334 fputc('\n', stderr);
8335 fflush(stderr);
8338 dictionary charinfo_dictionary(501);
8340 charinfo *get_charinfo(symbol nm)
8342 void *p = charinfo_dictionary.lookup(nm);
8343 if (p != 0)
8344 return (charinfo *)p;
8345 charinfo *cp = new charinfo(nm);
8346 (void)charinfo_dictionary.lookup(nm, cp);
8347 return cp;
8350 int charinfo::next_index = 0;
8352 charinfo::charinfo(symbol s)
8353 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
8354 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
8355 not_found(0), transparent_translate(1), translate_input(0),
8356 mode(CHAR_NORMAL), nm(s)
8358 index = next_index++;
8359 number = -1;
8362 void charinfo::set_hyphenation_code(unsigned char c)
8364 hyphenation_code = c;
8367 void charinfo::set_translation(charinfo *ci, int tt, int ti)
8369 translation = ci;
8370 if (ci && ti) {
8371 if (hyphenation_code != 0)
8372 ci->set_hyphenation_code(hyphenation_code);
8373 if (asciify_code != 0)
8374 ci->set_asciify_code(asciify_code);
8375 else if (ascii_code != 0)
8376 ci->set_asciify_code(ascii_code);
8377 ci->set_translation_input();
8379 special_translation = TRANSLATE_NONE;
8380 transparent_translate = tt;
8383 void charinfo::set_special_translation(int c, int tt)
8385 special_translation = c;
8386 translation = 0;
8387 transparent_translate = tt;
8390 void charinfo::set_ascii_code(unsigned char c)
8392 ascii_code = c;
8395 void charinfo::set_asciify_code(unsigned char c)
8397 asciify_code = c;
8400 macro *charinfo::set_macro(macro *m)
8402 macro *tem = mac;
8403 mac = m;
8404 return tem;
8407 macro *charinfo::setx_macro(macro *m, char_mode cm)
8409 macro *tem = mac;
8410 mac = m;
8411 mode = cm;
8412 return tem;
8415 void charinfo::set_number(int n)
8417 assert(n >= 0);
8418 number = n;
8421 int charinfo::get_number()
8423 assert(number >= 0);
8424 return number;
8427 symbol UNNAMED_SYMBOL("---");
8429 // For numbered characters not between 0 and 255, we make a symbol out
8430 // of the number and store them in this dictionary.
8432 dictionary numbered_charinfo_dictionary(11);
8434 charinfo *get_charinfo_by_number(int n)
8436 static charinfo *number_table[UCHAR_MAX +1];
8438 if (n >= 0 && n <= UCHAR_MAX) {
8439 charinfo *ci = number_table[n];
8440 if (!ci) {
8441 ci = new charinfo(UNNAMED_SYMBOL);
8442 ci->set_number(n);
8443 number_table[n] = ci;
8445 return ci;
8447 else {
8448 symbol ns(i_to_a(n));
8449 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
8450 if (!ci) {
8451 ci = new charinfo(UNNAMED_SYMBOL);
8452 ci->set_number(n);
8453 (void)numbered_charinfo_dictionary.lookup(ns, ci);
8455 return ci;
8459 // This overrides the same function from libgroff; while reading font
8460 // definition files it puts single-letter glyph names into `charset_table'
8461 // and converts glyph names of the form `\x' (`x' a single letter) into `x'.
8462 // Consequently, symbol("x") refers to glyph name `\x', not `x'.
8464 glyph *name_to_glyph(const char *nm)
8466 charinfo *ci;
8467 if (nm[1] == 0)
8468 ci = charset_table[nm[0] & 0xff];
8469 else if (nm[0] == '\\' && nm[2] == 0)
8470 ci = get_charinfo(symbol(nm + 1));
8471 else
8472 ci = get_charinfo(symbol(nm));
8473 return ci->as_glyph();
8476 glyph *number_to_glyph(int n)
8478 return get_charinfo_by_number(n)->as_glyph();
8481 const char *glyph_to_name(glyph *g)
8483 charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
8484 return (ci->nm != UNNAMED_SYMBOL ? ci->nm.contents() : NULL);
8487 // s-it2-mode