If a macro is called as a string, inherit value of \n[.br] from the
[s-roff.git] / src / roff / troff / input.cpp
blob5605b232d27adee7a091566b76ef89012d68f7e7
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006, 2007, 2008
4 Free Software Foundation, Inc.
5 Written by James Clark (jjc@jclark.com)
7 This file is part of groff.
9 groff is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation; either version 2, or (at your option) any later
12 version.
14 groff is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 for more details.
19 You should have received a copy of the GNU General Public License along
20 with groff; see the file COPYING. If not, write to the Free Software
21 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
23 #define DEBUGGING
25 #include "troff.h"
26 #include "dictionary.h"
27 #include "hvunits.h"
28 #include "stringclass.h"
29 #include "mtsm.h"
30 #include "env.h"
31 #include "request.h"
32 #include "node.h"
33 #include "token.h"
34 #include "div.h"
35 #include "reg.h"
36 #include "font.h"
37 #include "charinfo.h"
38 #include "macropath.h"
39 #include "input.h"
40 #include "defs.h"
41 #include "unicode.h"
43 // Needed for getpid() and isatty()
44 #include "posix.h"
46 #include "nonposix.h"
48 #ifdef NEED_DECLARATION_PUTENV
49 extern "C" {
50 int putenv(const char *);
52 #endif /* NEED_DECLARATION_PUTENV */
54 #define MACRO_PREFIX "tmac."
55 #define MACRO_POSTFIX ".tmac"
56 #define INITIAL_STARTUP_FILE "troffrc"
57 #define FINAL_STARTUP_FILE "troffrc-end"
58 #define DEFAULT_INPUT_STACK_LIMIT 1000
60 #ifndef DEFAULT_WARNING_MASK
61 // warnings that are enabled by default
62 #define DEFAULT_WARNING_MASK \
63 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
64 #endif
66 // initial size of buffer for reading names; expanded as necessary
67 #define ABUF_SIZE 16
69 extern "C" const char *program_name;
70 extern "C" const char *Version_string;
72 #ifdef COLUMN
73 void init_column_requests();
74 #endif /* COLUMN */
76 static node *read_draw_node();
77 static void read_color_draw_node(token &);
78 static void push_token(const token &);
79 void copy_file();
80 #ifdef COLUMN
81 void vjustify();
82 #endif /* COLUMN */
83 void transparent_file();
85 token tok;
86 int break_flag = 0;
87 int color_flag = 1; // colors are on by default
88 static int backtrace_flag = 0;
89 #ifndef POPEN_MISSING
90 char *pipe_command = 0;
91 #endif
92 charinfo *charset_table[256];
93 unsigned char hpf_code_table[256];
95 static int warning_mask = DEFAULT_WARNING_MASK;
96 static int inhibit_errors = 0;
97 static int ignoring = 0;
99 static void enable_warning(const char *);
100 static void disable_warning(const char *);
102 static int escape_char = '\\';
103 static symbol end_macro_name;
104 static symbol blank_line_macro_name;
105 static int compatible_flag = 0;
106 int ascii_output_flag = 0;
107 int suppress_output_flag = 0;
108 int is_html = 0;
109 int begin_level = 0; // number of nested \O escapes
111 int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
112 // \R, \s, or \S has been processed in
113 // token::next()
114 int old_have_input = 0; // value of have_input right before \n
115 int tcommand_flag = 0;
116 int unsafe_flag = 0; // safer by default
118 int have_string_arg = 0; // whether we have \*[foo bar...]
120 double spread_limit = -3.0 - 1.0; // negative means deactivated
122 double warn_scale;
123 char warn_scaling_indicator;
124 int debug_state = 0; // turns on debugging of the html troff state
126 search_path *mac_path = &safer_macro_path;
128 // Defaults to the current directory.
129 search_path include_search_path(0, 0, 0, 1);
131 static int get_copy(node**, int = 0, int = 0);
132 static void copy_mode_error(const char *,
133 const errarg & = empty_errarg,
134 const errarg & = empty_errarg,
135 const errarg & = empty_errarg);
137 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
138 static symbol read_escape_name(read_mode = NO_ARGS);
139 static symbol read_long_escape_name(read_mode = NO_ARGS);
140 static void interpolate_string(symbol);
141 static void interpolate_string_with_args(symbol);
142 static void interpolate_macro(symbol, int = 0);
143 static void interpolate_number_format(symbol);
144 static void interpolate_environment_variable(symbol);
146 static symbol composite_glyph_name(symbol);
147 static void interpolate_arg(symbol);
148 static request_or_macro *lookup_request(symbol);
149 static int get_delim_number(units *, unsigned char);
150 static int get_delim_number(units *, unsigned char, units);
151 static symbol do_get_long_name(int, char);
152 static int get_line_arg(units *res, unsigned char si, charinfo **cp);
153 static int read_size(int *);
154 static symbol get_delim_name();
155 static void init_registers();
156 static void trapping_blank_line();
158 class input_iterator;
159 input_iterator *make_temp_iterator(const char *);
160 const char *input_char_description(int);
162 void process_input_stack();
163 void chop_macro(); // declare to avoid friend name injection
166 void set_escape_char()
168 if (has_arg()) {
169 if (tok.ch() == 0) {
170 error("bad escape character");
171 escape_char = '\\';
173 else
174 escape_char = tok.ch();
176 else
177 escape_char = '\\';
178 skip_line();
181 void escape_off()
183 escape_char = 0;
184 skip_line();
187 static int saved_escape_char = '\\';
189 void save_escape_char()
191 saved_escape_char = escape_char;
192 skip_line();
195 void restore_escape_char()
197 escape_char = saved_escape_char;
198 skip_line();
201 class input_iterator {
202 public:
203 input_iterator();
204 input_iterator(int is_div);
205 virtual ~input_iterator() {}
206 int get(node **);
207 friend class input_stack;
208 int is_diversion;
209 statem *diversion_state;
210 protected:
211 const unsigned char *ptr;
212 const unsigned char *eptr;
213 input_iterator *next;
214 private:
215 virtual int fill(node **);
216 virtual int peek();
217 virtual int has_args() { return 0; }
218 virtual int nargs() { return 0; }
219 virtual input_iterator *get_arg(int) { return 0; }
220 virtual int space_follows_arg(int) { return 0; }
221 virtual int get_break_flag() { return 0; }
222 virtual int get_location(int, const char **, int *) { return 0; }
223 virtual void backtrace() {}
224 virtual int set_location(const char *, int) { return 0; }
225 virtual int next_file(FILE *, const char *) { return 0; }
226 virtual void shift(int) {}
227 virtual int is_boundary() {return 0; }
228 virtual int is_file() { return 0; }
229 virtual int is_macro() { return 0; }
230 virtual void save_compatible_flag(int) {}
231 virtual int get_compatible_flag() { return 0; }
234 input_iterator::input_iterator()
235 : is_diversion(0), ptr(0), eptr(0)
239 input_iterator::input_iterator(int is_div)
240 : is_diversion(is_div), ptr(0), eptr(0)
244 int input_iterator::fill(node **)
246 return EOF;
249 int input_iterator::peek()
251 return EOF;
254 inline int input_iterator::get(node **p)
256 return ptr < eptr ? *ptr++ : fill(p);
259 class input_boundary : public input_iterator {
260 public:
261 int is_boundary() { return 1; }
264 class input_return_boundary : public input_iterator {
265 public:
266 int is_boundary() { return 2; }
269 class file_iterator : public input_iterator {
270 FILE *fp;
271 int lineno;
272 const char *filename;
273 int popened;
274 int newline_flag;
275 int seen_escape;
276 enum { BUF_SIZE = 512 };
277 unsigned char buf[BUF_SIZE];
278 void close();
279 public:
280 file_iterator(FILE *, const char *, int = 0);
281 ~file_iterator();
282 int fill(node **);
283 int peek();
284 int get_location(int, const char **, int *);
285 void backtrace();
286 int set_location(const char *, int);
287 int next_file(FILE *, const char *);
288 int is_file();
291 file_iterator::file_iterator(FILE *f, const char *fn, int po)
292 : fp(f), lineno(1), filename(fn), popened(po),
293 newline_flag(0), seen_escape(0)
295 if ((font::use_charnames_in_special) && (fn != 0)) {
296 if (!the_output)
297 init_output();
298 the_output->put_filename(fn, po);
302 file_iterator::~file_iterator()
304 close();
307 void file_iterator::close()
309 if (fp == stdin)
310 clearerr(stdin);
311 #ifndef POPEN_MISSING
312 else if (popened)
313 pclose(fp);
314 #endif /* not POPEN_MISSING */
315 else
316 fclose(fp);
319 int file_iterator::is_file()
321 return 1;
324 int file_iterator::next_file(FILE *f, const char *s)
326 close();
327 filename = s;
328 fp = f;
329 lineno = 1;
330 newline_flag = 0;
331 seen_escape = 0;
332 popened = 0;
333 ptr = 0;
334 eptr = 0;
335 return 1;
338 int file_iterator::fill(node **)
340 if (newline_flag)
341 lineno++;
342 newline_flag = 0;
343 unsigned char *p = buf;
344 ptr = p;
345 unsigned char *e = p + BUF_SIZE;
346 while (p < e) {
347 int c = getc(fp);
348 if (c == EOF)
349 break;
350 if (invalid_input_char(c))
351 warning(WARN_INPUT, "invalid input character code %1", int(c));
352 else {
353 *p++ = c;
354 if (c == '\n') {
355 seen_escape = 0;
356 newline_flag = 1;
357 break;
359 seen_escape = (c == '\\');
362 if (p > buf) {
363 eptr = p;
364 return *ptr++;
366 else {
367 eptr = p;
368 return EOF;
372 int file_iterator::peek()
374 int c = getc(fp);
375 while (invalid_input_char(c)) {
376 warning(WARN_INPUT, "invalid input character code %1", int(c));
377 c = getc(fp);
379 if (c != EOF)
380 ungetc(c, fp);
381 return c;
384 int file_iterator::get_location(int /*allow_macro*/,
385 const char **filenamep, int *linenop)
387 *linenop = lineno;
388 if (filename != 0 && strcmp(filename, "-") == 0)
389 *filenamep = "<standard input>";
390 else
391 *filenamep = filename;
392 return 1;
395 void file_iterator::backtrace()
397 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
398 popened ? "process" : "file");
401 int file_iterator::set_location(const char *f, int ln)
403 if (f) {
404 filename = f;
405 if (!the_output)
406 init_output();
407 the_output->put_filename(f, 0);
409 lineno = ln;
410 return 1;
413 input_iterator nil_iterator;
415 class input_stack {
416 public:
417 static int get(node **);
418 static int peek();
419 static void push(input_iterator *);
420 static input_iterator *get_arg(int);
421 static int space_follows_arg(int);
422 static int get_break_flag();
423 static int nargs();
424 static int get_location(int, const char **, int *);
425 static int set_location(const char *, int);
426 static void backtrace();
427 static void backtrace_all();
428 static void next_file(FILE *, const char *);
429 static void end_file();
430 static void shift(int n);
431 static void add_boundary();
432 static void add_return_boundary();
433 static int is_return_boundary();
434 static void remove_boundary();
435 static int get_level();
436 static int get_div_level();
437 static void increase_level();
438 static void decrease_level();
439 static void clear();
440 static void pop_macro();
441 static void save_compatible_flag(int);
442 static int get_compatible_flag();
443 static statem *get_diversion_state();
444 static void check_end_diversion(input_iterator *t);
445 static int limit;
446 static int div_level;
447 static statem *diversion_state;
448 private:
449 static input_iterator *top;
450 static int level;
451 static int finish_get(node **);
452 static int finish_peek();
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;
463 inline int input_stack::get_level()
465 return level;
468 inline void input_stack::increase_level()
470 level++;
473 inline void input_stack::decrease_level()
475 level--;
478 inline int input_stack::get_div_level()
480 return div_level;
483 inline int input_stack::get(node **np)
485 int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
486 if (res == '\n') {
487 old_have_input = have_input;
488 have_input = 0;
490 return res;
493 int input_stack::finish_get(node **np)
495 for (;;) {
496 int c = top->fill(np);
497 if (c != EOF || top->is_boundary())
498 return c;
499 if (top == &nil_iterator)
500 break;
501 input_iterator *tem = top;
502 check_end_diversion(tem);
503 #if defined(DEBUGGING)
504 if (debug_state)
505 if (tem->is_diversion)
506 fprintf(stderr,
507 "in diversion level = %d\n", input_stack::get_div_level());
508 #endif
509 top = top->next;
510 level--;
511 delete tem;
512 if (top->ptr < top->eptr)
513 return *top->ptr++;
515 assert(level == 0);
516 return EOF;
519 inline int input_stack::peek()
521 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
524 void input_stack::check_end_diversion(input_iterator *t)
526 if (t->is_diversion) {
527 div_level--;
528 diversion_state = t->diversion_state;
532 int input_stack::finish_peek()
534 for (;;) {
535 int c = top->peek();
536 if (c != EOF || top->is_boundary())
537 return c;
538 if (top == &nil_iterator)
539 break;
540 input_iterator *tem = top;
541 check_end_diversion(tem);
542 top = top->next;
543 level--;
544 delete tem;
545 if (top->ptr < top->eptr)
546 return *top->ptr;
548 assert(level == 0);
549 return EOF;
552 void input_stack::add_boundary()
554 push(new input_boundary);
557 void input_stack::add_return_boundary()
559 push(new input_return_boundary);
562 int input_stack::is_return_boundary()
564 return top->is_boundary() == 2;
567 void input_stack::remove_boundary()
569 assert(top->is_boundary());
570 input_iterator *temp = top->next;
571 check_end_diversion(top);
573 delete top;
574 top = temp;
575 level--;
578 void input_stack::push(input_iterator *in)
580 if (in == 0)
581 return;
582 if (++level > limit && limit > 0)
583 fatal("input stack limit exceeded (probable infinite loop)");
584 in->next = top;
585 top = in;
586 if (top->is_diversion) {
587 div_level++;
588 in->diversion_state = diversion_state;
589 diversion_state = curenv->construct_state(0);
590 #if defined(DEBUGGING)
591 if (debug_state) {
592 curenv->dump_troff_state();
593 fflush(stderr);
595 #endif
597 #if defined(DEBUGGING)
598 if (debug_state)
599 if (top->is_diversion) {
600 fprintf(stderr,
601 "in diversion level = %d\n", input_stack::get_div_level());
602 fflush(stderr);
604 #endif
607 statem *get_diversion_state()
609 return input_stack::get_diversion_state();
612 statem *input_stack::get_diversion_state()
614 if (diversion_state == NULL)
615 return NULL;
616 else
617 return new statem(diversion_state);
620 input_iterator *input_stack::get_arg(int i)
622 input_iterator *p;
623 for (p = top; p != 0; p = p->next)
624 if (p->has_args())
625 return p->get_arg(i);
626 return 0;
629 int input_stack::space_follows_arg(int i)
631 input_iterator *p;
632 for (p = top; p != 0; p = p->next)
633 if (p->has_args())
634 return p->space_follows_arg(i);
635 return 0;
638 int input_stack::get_break_flag()
640 return top->get_break_flag();
643 void input_stack::shift(int n)
645 for (input_iterator *p = top; p; p = p->next)
646 if (p->has_args()) {
647 p->shift(n);
648 return;
652 int input_stack::nargs()
654 for (input_iterator *p =top; p != 0; p = p->next)
655 if (p->has_args())
656 return p->nargs();
657 return 0;
660 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
662 for (input_iterator *p = top; p; p = p->next)
663 if (p->get_location(allow_macro, filenamep, linenop))
664 return 1;
665 return 0;
668 void input_stack::backtrace()
670 const char *f;
671 int n;
672 // only backtrace down to (not including) the topmost file
673 for (input_iterator *p = top;
674 p && !p->get_location(0, &f, &n);
675 p = p->next)
676 p->backtrace();
679 void input_stack::backtrace_all()
681 for (input_iterator *p = top; p; p = p->next)
682 p->backtrace();
685 int input_stack::set_location(const char *filename, int lineno)
687 for (input_iterator *p = top; p; p = p->next)
688 if (p->set_location(filename, lineno))
689 return 1;
690 return 0;
693 void input_stack::next_file(FILE *fp, const char *s)
695 input_iterator **pp;
696 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
697 if ((*pp)->next_file(fp, s))
698 return;
699 if (++level > limit && limit > 0)
700 fatal("input stack limit exceeded");
701 *pp = new file_iterator(fp, s);
702 (*pp)->next = &nil_iterator;
705 void input_stack::end_file()
707 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
708 if ((*pp)->is_file()) {
709 input_iterator *tem = *pp;
710 check_end_diversion(tem);
711 *pp = (*pp)->next;
712 delete tem;
713 level--;
714 return;
718 void input_stack::clear()
720 int nboundaries = 0;
721 while (top != &nil_iterator) {
722 if (top->is_boundary())
723 nboundaries++;
724 input_iterator *tem = top;
725 check_end_diversion(tem);
726 top = top->next;
727 level--;
728 delete tem;
730 // Keep while_request happy.
731 for (; nboundaries > 0; --nboundaries)
732 add_return_boundary();
735 void input_stack::pop_macro()
737 int nboundaries = 0;
738 int is_macro = 0;
739 do {
740 if (top->next == &nil_iterator)
741 break;
742 if (top->is_boundary())
743 nboundaries++;
744 is_macro = top->is_macro();
745 input_iterator *tem = top;
746 check_end_diversion(tem);
747 top = top->next;
748 level--;
749 delete tem;
750 } while (!is_macro);
751 // Keep while_request happy.
752 for (; nboundaries > 0; --nboundaries)
753 add_return_boundary();
756 inline void input_stack::save_compatible_flag(int f)
758 top->save_compatible_flag(f);
761 inline int input_stack::get_compatible_flag()
763 return top->get_compatible_flag();
766 void backtrace_request()
768 input_stack::backtrace_all();
769 fflush(stderr);
770 skip_line();
773 void next_file()
775 symbol nm = get_long_name();
776 while (!tok.newline() && !tok.eof())
777 tok.next();
778 if (nm.is_null())
779 input_stack::end_file();
780 else {
781 errno = 0;
782 FILE *fp = include_search_path.open_file_cautious(nm.contents());
783 if (!fp)
784 error("can't open `%1': %2", nm.contents(), strerror(errno));
785 else
786 input_stack::next_file(fp, nm.contents());
788 tok.next();
791 void shift()
793 int n;
794 if (!has_arg() || !get_integer(&n))
795 n = 1;
796 input_stack::shift(n);
797 skip_line();
800 static char get_char_for_escape_name(int allow_space = 0)
802 int c = get_copy(0, 0, 1);
803 switch (c) {
804 case EOF:
805 copy_mode_error("end of input in escape name");
806 return '\0';
807 default:
808 if (!invalid_input_char(c))
809 break;
810 // fall through
811 case '\n':
812 if (c == '\n')
813 input_stack::push(make_temp_iterator("\n"));
814 // fall through
815 case ' ':
816 if (c == ' ' && allow_space)
817 break;
818 // fall through
819 case '\t':
820 case '\001':
821 case '\b':
822 copy_mode_error("%1 is not allowed in an escape name",
823 input_char_description(c));
824 return '\0';
826 return c;
829 static symbol read_two_char_escape_name()
831 char buf[3];
832 buf[0] = get_char_for_escape_name();
833 if (buf[0] != '\0') {
834 buf[1] = get_char_for_escape_name();
835 if (buf[1] == '\0')
836 buf[0] = 0;
837 else
838 buf[2] = 0;
840 return symbol(buf);
843 static symbol read_long_escape_name(read_mode mode)
845 int start_level = input_stack::get_level();
846 char abuf[ABUF_SIZE];
847 char *buf = abuf;
848 int buf_size = ABUF_SIZE;
849 int i = 0;
850 char c;
851 int have_char = 0;
852 for (;;) {
853 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
854 if (c == 0) {
855 if (buf != abuf)
856 a_delete buf;
857 return NULL_SYMBOL;
859 have_char = 1;
860 if (mode == WITH_ARGS && c == ' ')
861 break;
862 if (i + 2 > buf_size) {
863 if (buf == abuf) {
864 buf = new char[ABUF_SIZE*2];
865 memcpy(buf, abuf, buf_size);
866 buf_size = ABUF_SIZE*2;
868 else {
869 char *old_buf = buf;
870 buf = new char[buf_size*2];
871 memcpy(buf, old_buf, buf_size);
872 buf_size *= 2;
873 a_delete old_buf;
876 if (c == ']' && input_stack::get_level() == start_level)
877 break;
878 buf[i++] = c;
880 buf[i] = 0;
881 if (c == ' ')
882 have_string_arg = 1;
883 if (buf == abuf) {
884 if (i == 0) {
885 if (mode != ALLOW_EMPTY)
886 copy_mode_error("empty escape name");
887 return EMPTY_SYMBOL;
889 return symbol(abuf);
891 else {
892 symbol s(buf);
893 a_delete buf;
894 return s;
898 static symbol read_escape_name(read_mode mode)
900 char c = get_char_for_escape_name();
901 if (c == 0)
902 return NULL_SYMBOL;
903 if (c == '(')
904 return read_two_char_escape_name();
905 if (c == '[' && !compatible_flag)
906 return read_long_escape_name(mode);
907 char buf[2];
908 buf[0] = c;
909 buf[1] = '\0';
910 return symbol(buf);
913 static symbol read_increment_and_escape_name(int *incp)
915 char c = get_char_for_escape_name();
916 switch (c) {
917 case 0:
918 *incp = 0;
919 return NULL_SYMBOL;
920 case '(':
921 *incp = 0;
922 return read_two_char_escape_name();
923 case '+':
924 *incp = 1;
925 return read_escape_name();
926 case '-':
927 *incp = -1;
928 return read_escape_name();
929 case '[':
930 if (!compatible_flag) {
931 *incp = 0;
932 return read_long_escape_name();
934 break;
936 *incp = 0;
937 char buf[2];
938 buf[0] = c;
939 buf[1] = '\0';
940 return symbol(buf);
943 static int get_copy(node **nd, int defining, int handle_escape_E)
945 for (;;) {
946 int c = input_stack::get(nd);
947 if (c == PUSH_GROFF_MODE) {
948 input_stack::save_compatible_flag(compatible_flag);
949 compatible_flag = 0;
950 continue;
952 if (c == PUSH_COMP_MODE) {
953 input_stack::save_compatible_flag(compatible_flag);
954 compatible_flag = 1;
955 continue;
957 if (c == POP_GROFFCOMP_MODE) {
958 compatible_flag = input_stack::get_compatible_flag();
959 continue;
961 if (c == BEGIN_QUOTE) {
962 input_stack::increase_level();
963 continue;
965 if (c == END_QUOTE) {
966 input_stack::decrease_level();
967 continue;
969 if (c == DOUBLE_QUOTE)
970 continue;
971 if (c == ESCAPE_E && handle_escape_E)
972 c = escape_char;
973 if (c == ESCAPE_NEWLINE) {
974 if (defining)
975 return c;
976 do {
977 c = input_stack::get(nd);
978 } while (c == ESCAPE_NEWLINE);
980 if (c != escape_char || escape_char <= 0)
981 return c;
982 again:
983 c = input_stack::peek();
984 switch(c) {
985 case 0:
986 return escape_char;
987 case '"':
988 (void)input_stack::get(0);
989 while ((c = input_stack::get(0)) != '\n' && c != EOF)
991 return c;
992 case '#': // Like \" but newline is ignored.
993 (void)input_stack::get(0);
994 while ((c = input_stack::get(0)) != '\n')
995 if (c == EOF)
996 return EOF;
997 break;
998 case '$':
1000 (void)input_stack::get(0);
1001 symbol s = read_escape_name();
1002 if (!(s.is_null() || s.is_empty()))
1003 interpolate_arg(s);
1004 break;
1006 case '*':
1008 (void)input_stack::get(0);
1009 symbol s = read_escape_name(WITH_ARGS);
1010 if (!(s.is_null() || s.is_empty())) {
1011 if (have_string_arg) {
1012 have_string_arg = 0;
1013 interpolate_string_with_args(s);
1015 else
1016 interpolate_string(s);
1018 break;
1020 case 'a':
1021 (void)input_stack::get(0);
1022 return '\001';
1023 case 'e':
1024 (void)input_stack::get(0);
1025 return ESCAPE_e;
1026 case 'E':
1027 (void)input_stack::get(0);
1028 if (handle_escape_E)
1029 goto again;
1030 return ESCAPE_E;
1031 case 'n':
1033 (void)input_stack::get(0);
1034 int inc;
1035 symbol s = read_increment_and_escape_name(&inc);
1036 if (!(s.is_null() || s.is_empty()))
1037 interpolate_number_reg(s, inc);
1038 break;
1040 case 'g':
1042 (void)input_stack::get(0);
1043 symbol s = read_escape_name();
1044 if (!(s.is_null() || s.is_empty()))
1045 interpolate_number_format(s);
1046 break;
1048 case 't':
1049 (void)input_stack::get(0);
1050 return '\t';
1051 case 'V':
1053 (void)input_stack::get(0);
1054 symbol s = read_escape_name();
1055 if (!(s.is_null() || s.is_empty()))
1056 interpolate_environment_variable(s);
1057 break;
1059 case '\n':
1060 (void)input_stack::get(0);
1061 if (defining)
1062 return ESCAPE_NEWLINE;
1063 break;
1064 case ' ':
1065 (void)input_stack::get(0);
1066 return ESCAPE_SPACE;
1067 case '~':
1068 (void)input_stack::get(0);
1069 return ESCAPE_TILDE;
1070 case ':':
1071 (void)input_stack::get(0);
1072 return ESCAPE_COLON;
1073 case '|':
1074 (void)input_stack::get(0);
1075 return ESCAPE_BAR;
1076 case '^':
1077 (void)input_stack::get(0);
1078 return ESCAPE_CIRCUMFLEX;
1079 case '{':
1080 (void)input_stack::get(0);
1081 return ESCAPE_LEFT_BRACE;
1082 case '}':
1083 (void)input_stack::get(0);
1084 return ESCAPE_RIGHT_BRACE;
1085 case '`':
1086 (void)input_stack::get(0);
1087 return ESCAPE_LEFT_QUOTE;
1088 case '\'':
1089 (void)input_stack::get(0);
1090 return ESCAPE_RIGHT_QUOTE;
1091 case '-':
1092 (void)input_stack::get(0);
1093 return ESCAPE_HYPHEN;
1094 case '_':
1095 (void)input_stack::get(0);
1096 return ESCAPE_UNDERSCORE;
1097 case 'c':
1098 (void)input_stack::get(0);
1099 return ESCAPE_c;
1100 case '!':
1101 (void)input_stack::get(0);
1102 return ESCAPE_BANG;
1103 case '?':
1104 (void)input_stack::get(0);
1105 return ESCAPE_QUESTION;
1106 case '&':
1107 (void)input_stack::get(0);
1108 return ESCAPE_AMPERSAND;
1109 case ')':
1110 (void)input_stack::get(0);
1111 return ESCAPE_RIGHT_PARENTHESIS;
1112 case '.':
1113 (void)input_stack::get(0);
1114 return c;
1115 case '%':
1116 (void)input_stack::get(0);
1117 return ESCAPE_PERCENT;
1118 default:
1119 if (c == escape_char) {
1120 (void)input_stack::get(0);
1121 return c;
1123 else
1124 return escape_char;
1129 class non_interpreted_char_node : public node {
1130 unsigned char c;
1131 public:
1132 non_interpreted_char_node(unsigned char);
1133 node *copy();
1134 int interpret(macro *);
1135 int same(node *);
1136 const char *type();
1137 int force_tprint();
1138 int is_tag();
1141 int non_interpreted_char_node::same(node *nd)
1143 return c == ((non_interpreted_char_node *)nd)->c;
1146 const char *non_interpreted_char_node::type()
1148 return "non_interpreted_char_node";
1151 int non_interpreted_char_node::force_tprint()
1153 return 0;
1156 int non_interpreted_char_node::is_tag()
1158 return 0;
1161 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1163 assert(n != 0);
1166 node *non_interpreted_char_node::copy()
1168 return new non_interpreted_char_node(c);
1171 int non_interpreted_char_node::interpret(macro *mac)
1173 mac->append(c);
1174 return 1;
1177 static void do_width();
1178 static node *do_non_interpreted();
1179 static node *do_special();
1180 static node *do_suppress(symbol nm);
1181 static void do_register();
1183 dictionary color_dictionary(501);
1185 static color *lookup_color(symbol nm)
1187 assert(!nm.is_null());
1188 if (nm == default_symbol)
1189 return &default_color;
1190 color *c = (color *)color_dictionary.lookup(nm);
1191 if (c == 0)
1192 warning(WARN_COLOR, "color `%1' not defined", nm.contents());
1193 return c;
1196 void do_glyph_color(symbol nm)
1198 if (nm.is_null())
1199 return;
1200 if (nm.is_empty())
1201 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1202 else {
1203 color *tem = lookup_color(nm);
1204 if (tem)
1205 curenv->set_glyph_color(tem);
1206 else
1207 (void)color_dictionary.lookup(nm, new color(nm));
1211 void do_fill_color(symbol nm)
1213 if (nm.is_null())
1214 return;
1215 if (nm.is_empty())
1216 curenv->set_fill_color(curenv->get_prev_fill_color());
1217 else {
1218 color *tem = lookup_color(nm);
1219 if (tem)
1220 curenv->set_fill_color(tem);
1221 else
1222 (void)color_dictionary.lookup(nm, new color(nm));
1226 static unsigned int get_color_element(const char *scheme, const char *col)
1228 units val;
1229 if (!get_number(&val, 'f')) {
1230 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1231 tok.next();
1232 return 0;
1234 if (val < 0) {
1235 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1236 return 0;
1238 if (val > color::MAX_COLOR_VAL+1) {
1239 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1240 // we change 0x10000 to 0xffff
1241 return color::MAX_COLOR_VAL;
1243 return (unsigned int)val;
1246 static color *read_rgb(char end = 0)
1248 symbol component = do_get_long_name(0, end);
1249 if (component.is_null()) {
1250 warning(WARN_COLOR, "missing rgb color values");
1251 return 0;
1253 const char *s = component.contents();
1254 color *col = new color;
1255 if (*s == '#') {
1256 if (!col->read_rgb(s)) {
1257 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1258 delete col;
1259 return 0;
1262 else {
1263 if (!end)
1264 input_stack::push(make_temp_iterator(" "));
1265 input_stack::push(make_temp_iterator(s));
1266 tok.next();
1267 unsigned int r = get_color_element("rgb color", "red component");
1268 unsigned int g = get_color_element("rgb color", "green component");
1269 unsigned int b = get_color_element("rgb color", "blue component");
1270 col->set_rgb(r, g, b);
1272 return col;
1275 static color *read_cmy(char end = 0)
1277 symbol component = do_get_long_name(0, end);
1278 if (component.is_null()) {
1279 warning(WARN_COLOR, "missing cmy color values");
1280 return 0;
1282 const char *s = component.contents();
1283 color *col = new color;
1284 if (*s == '#') {
1285 if (!col->read_cmy(s)) {
1286 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1287 delete col;
1288 return 0;
1291 else {
1292 if (!end)
1293 input_stack::push(make_temp_iterator(" "));
1294 input_stack::push(make_temp_iterator(s));
1295 tok.next();
1296 unsigned int c = get_color_element("cmy color", "cyan component");
1297 unsigned int m = get_color_element("cmy color", "magenta component");
1298 unsigned int y = get_color_element("cmy color", "yellow component");
1299 col->set_cmy(c, m, y);
1301 return col;
1304 static color *read_cmyk(char end = 0)
1306 symbol component = do_get_long_name(0, end);
1307 if (component.is_null()) {
1308 warning(WARN_COLOR, "missing cmyk color values");
1309 return 0;
1311 const char *s = component.contents();
1312 color *col = new color;
1313 if (*s == '#') {
1314 if (!col->read_cmyk(s)) {
1315 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1316 delete col;
1317 return 0;
1320 else {
1321 if (!end)
1322 input_stack::push(make_temp_iterator(" "));
1323 input_stack::push(make_temp_iterator(s));
1324 tok.next();
1325 unsigned int c = get_color_element("cmyk color", "cyan component");
1326 unsigned int m = get_color_element("cmyk color", "magenta component");
1327 unsigned int y = get_color_element("cmyk color", "yellow component");
1328 unsigned int k = get_color_element("cmyk color", "black component");
1329 col->set_cmyk(c, m, y, k);
1331 return col;
1334 static color *read_gray(char end = 0)
1336 symbol component = do_get_long_name(0, end);
1337 if (component.is_null()) {
1338 warning(WARN_COLOR, "missing gray values");
1339 return 0;
1341 const char *s = component.contents();
1342 color *col = new color;
1343 if (*s == '#') {
1344 if (!col->read_gray(s)) {
1345 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1346 delete col;
1347 return 0;
1350 else {
1351 if (!end)
1352 input_stack::push(make_temp_iterator("\n"));
1353 input_stack::push(make_temp_iterator(s));
1354 tok.next();
1355 unsigned int g = get_color_element("gray", "gray value");
1356 col->set_gray(g);
1358 return col;
1361 static void activate_color()
1363 int n;
1364 if (has_arg() && get_integer(&n))
1365 color_flag = n != 0;
1366 else
1367 color_flag = 1;
1368 skip_line();
1371 static void define_color()
1373 symbol color_name = get_long_name(1);
1374 if (color_name.is_null()) {
1375 skip_line();
1376 return;
1378 if (color_name == default_symbol) {
1379 warning(WARN_COLOR, "default color can't be redefined");
1380 skip_line();
1381 return;
1383 symbol style = get_long_name(1);
1384 if (style.is_null()) {
1385 skip_line();
1386 return;
1388 color *col;
1389 if (strcmp(style.contents(), "rgb") == 0)
1390 col = read_rgb();
1391 else if (strcmp(style.contents(), "cmyk") == 0)
1392 col = read_cmyk();
1393 else if (strcmp(style.contents(), "gray") == 0)
1394 col = read_gray();
1395 else if (strcmp(style.contents(), "grey") == 0)
1396 col = read_gray();
1397 else if (strcmp(style.contents(), "cmy") == 0)
1398 col = read_cmy();
1399 else {
1400 warning(WARN_COLOR,
1401 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1402 style.contents());
1403 skip_line();
1404 return;
1406 if (col) {
1407 col->nm = color_name;
1408 (void)color_dictionary.lookup(color_name, col);
1410 skip_line();
1413 static node *do_overstrike()
1415 token start;
1416 overstrike_node *on = new overstrike_node;
1417 int start_level = input_stack::get_level();
1418 start.next();
1419 for (;;) {
1420 tok.next();
1421 if (tok.newline() || tok.eof()) {
1422 warning(WARN_DELIM, "missing closing delimiter");
1423 input_stack::push(make_temp_iterator("\n"));
1424 break;
1426 if (tok == start
1427 && (compatible_flag || input_stack::get_level() == start_level))
1428 break;
1429 charinfo *ci = tok.get_char(1);
1430 if (ci) {
1431 node *n = curenv->make_char_node(ci);
1432 if (n)
1433 on->overstrike(n);
1436 return on;
1439 static node *do_bracket()
1441 token start;
1442 bracket_node *bn = new bracket_node;
1443 start.next();
1444 int start_level = input_stack::get_level();
1445 for (;;) {
1446 tok.next();
1447 if (tok.eof()) {
1448 warning(WARN_DELIM, "missing closing delimiter");
1449 break;
1451 if (tok.newline()) {
1452 warning(WARN_DELIM, "missing closing delimiter");
1453 input_stack::push(make_temp_iterator("\n"));
1454 break;
1456 if (tok == start
1457 && (compatible_flag || input_stack::get_level() == start_level))
1458 break;
1459 charinfo *ci = tok.get_char(1);
1460 if (ci) {
1461 node *n = curenv->make_char_node(ci);
1462 if (n)
1463 bn->bracket(n);
1466 return bn;
1469 static int do_name_test()
1471 token start;
1472 start.next();
1473 int start_level = input_stack::get_level();
1474 int bad_char = 0;
1475 int some_char = 0;
1476 for (;;) {
1477 tok.next();
1478 if (tok.newline() || tok.eof()) {
1479 warning(WARN_DELIM, "missing closing delimiter");
1480 input_stack::push(make_temp_iterator("\n"));
1481 break;
1483 if (tok == start
1484 && (compatible_flag || input_stack::get_level() == start_level))
1485 break;
1486 if (!tok.ch())
1487 bad_char = 1;
1488 some_char = 1;
1490 return some_char && !bad_char;
1493 static int do_expr_test()
1495 token start;
1496 start.next();
1497 int start_level = input_stack::get_level();
1498 if (!start.delimiter(1))
1499 return 0;
1500 tok.next();
1501 // disable all warning and error messages temporarily
1502 int saved_warning_mask = warning_mask;
1503 int saved_inhibit_errors = inhibit_errors;
1504 warning_mask = 0;
1505 inhibit_errors = 1;
1506 int dummy;
1507 int result = get_number_rigidly(&dummy, 'u');
1508 warning_mask = saved_warning_mask;
1509 inhibit_errors = saved_inhibit_errors;
1510 if (tok == start && input_stack::get_level() == start_level)
1511 return result;
1512 // ignore everything up to the delimiter in case we aren't right there
1513 for (;;) {
1514 tok.next();
1515 if (tok.newline() || tok.eof()) {
1516 warning(WARN_DELIM, "missing closing delimiter");
1517 input_stack::push(make_temp_iterator("\n"));
1518 break;
1520 if (tok == start && input_stack::get_level() == start_level)
1521 break;
1523 return 0;
1526 #if 0
1527 static node *do_zero_width()
1529 token start;
1530 start.next();
1531 int start_level = input_stack::get_level();
1532 environment env(curenv);
1533 environment *oldenv = curenv;
1534 curenv = &env;
1535 for (;;) {
1536 tok.next();
1537 if (tok.newline() || tok.eof()) {
1538 error("missing closing delimiter");
1539 break;
1541 if (tok == start
1542 && (compatible_flag || input_stack::get_level() == start_level))
1543 break;
1544 tok.process();
1546 curenv = oldenv;
1547 node *rev = env.extract_output_line();
1548 node *n = 0;
1549 while (rev) {
1550 node *tem = rev;
1551 rev = rev->next;
1552 tem->next = n;
1553 n = tem;
1555 return new zero_width_node(n);
1558 #else
1560 // It's undesirable for \Z to change environments, because then
1561 // \n(.w won't work as expected.
1563 static node *do_zero_width()
1565 node *rev = new dummy_node;
1566 token start;
1567 start.next();
1568 int start_level = input_stack::get_level();
1569 for (;;) {
1570 tok.next();
1571 if (tok.newline() || tok.eof()) {
1572 warning(WARN_DELIM, "missing closing delimiter");
1573 input_stack::push(make_temp_iterator("\n"));
1574 break;
1576 if (tok == start
1577 && (compatible_flag || input_stack::get_level() == start_level))
1578 break;
1579 if (!tok.add_to_node_list(&rev))
1580 error("invalid token in argument to \\Z");
1582 node *n = 0;
1583 while (rev) {
1584 node *tem = rev;
1585 rev = rev->next;
1586 tem->next = n;
1587 n = tem;
1589 return new zero_width_node(n);
1592 #endif
1594 token_node *node::get_token_node()
1596 return 0;
1599 class token_node : public node {
1600 public:
1601 token tk;
1602 token_node(const token &t);
1603 node *copy();
1604 token_node *get_token_node();
1605 int same(node *);
1606 const char *type();
1607 int force_tprint();
1608 int is_tag();
1611 token_node::token_node(const token &t) : tk(t)
1615 node *token_node::copy()
1617 return new token_node(tk);
1620 token_node *token_node::get_token_node()
1622 return this;
1625 int token_node::same(node *nd)
1627 return tk == ((token_node *)nd)->tk;
1630 const char *token_node::type()
1632 return "token_node";
1635 int token_node::force_tprint()
1637 return 0;
1640 int token_node::is_tag()
1642 return 0;
1645 token::token() : nd(0), type(TOKEN_EMPTY)
1649 token::~token()
1651 delete nd;
1654 token::token(const token &t)
1655 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1657 // Use two statements to work around bug in SGI C++.
1658 node *tem = t.nd;
1659 nd = tem ? tem->copy() : 0;
1662 void token::operator=(const token &t)
1664 delete nd;
1665 nm = t.nm;
1666 // Use two statements to work around bug in SGI C++.
1667 node *tem = t.nd;
1668 nd = tem ? tem->copy() : 0;
1669 c = t.c;
1670 val = t.val;
1671 dim = t.dim;
1672 type = t.type;
1675 void token::skip()
1677 while (space())
1678 next();
1681 int has_arg()
1683 while (tok.space())
1684 tok.next();
1685 return !tok.newline();
1688 void token::make_space()
1690 type = TOKEN_SPACE;
1693 void token::make_newline()
1695 type = TOKEN_NEWLINE;
1698 void token::next()
1700 if (nd) {
1701 delete nd;
1702 nd = 0;
1704 units x;
1705 for (;;) {
1706 node *n = 0;
1707 int cc = input_stack::get(&n);
1708 if (cc != escape_char || escape_char == 0) {
1709 handle_normal_char:
1710 switch(cc) {
1711 case PUSH_GROFF_MODE:
1712 input_stack::save_compatible_flag(compatible_flag);
1713 compatible_flag = 0;
1714 continue;
1715 case PUSH_COMP_MODE:
1716 input_stack::save_compatible_flag(compatible_flag);
1717 compatible_flag = 1;
1718 continue;
1719 case POP_GROFFCOMP_MODE:
1720 compatible_flag = input_stack::get_compatible_flag();
1721 continue;
1722 case BEGIN_QUOTE:
1723 input_stack::increase_level();
1724 continue;
1725 case END_QUOTE:
1726 input_stack::decrease_level();
1727 continue;
1728 case DOUBLE_QUOTE:
1729 continue;
1730 case EOF:
1731 type = TOKEN_EOF;
1732 return;
1733 case TRANSPARENT_FILE_REQUEST:
1734 case TITLE_REQUEST:
1735 case COPY_FILE_REQUEST:
1736 #ifdef COLUMN
1737 case VJUSTIFY_REQUEST:
1738 #endif /* COLUMN */
1739 type = TOKEN_REQUEST;
1740 c = cc;
1741 return;
1742 case BEGIN_TRAP:
1743 type = TOKEN_BEGIN_TRAP;
1744 return;
1745 case END_TRAP:
1746 type = TOKEN_END_TRAP;
1747 return;
1748 case LAST_PAGE_EJECTOR:
1749 seen_last_page_ejector = 1;
1750 // fall through
1751 case PAGE_EJECTOR:
1752 type = TOKEN_PAGE_EJECTOR;
1753 return;
1754 case ESCAPE_PERCENT:
1755 ESCAPE_PERCENT:
1756 type = TOKEN_HYPHEN_INDICATOR;
1757 return;
1758 case ESCAPE_SPACE:
1759 ESCAPE_SPACE:
1760 type = TOKEN_UNSTRETCHABLE_SPACE;
1761 return;
1762 case ESCAPE_TILDE:
1763 ESCAPE_TILDE:
1764 type = TOKEN_STRETCHABLE_SPACE;
1765 return;
1766 case ESCAPE_COLON:
1767 ESCAPE_COLON:
1768 type = TOKEN_ZERO_WIDTH_BREAK;
1769 return;
1770 case ESCAPE_e:
1771 ESCAPE_e:
1772 type = TOKEN_ESCAPE;
1773 return;
1774 case ESCAPE_E:
1775 goto handle_escape_char;
1776 case ESCAPE_BAR:
1777 ESCAPE_BAR:
1778 type = TOKEN_NODE;
1779 nd = new hmotion_node(curenv->get_narrow_space_width(),
1780 curenv->get_fill_color());
1781 return;
1782 case ESCAPE_CIRCUMFLEX:
1783 ESCAPE_CIRCUMFLEX:
1784 type = TOKEN_NODE;
1785 nd = new hmotion_node(curenv->get_half_narrow_space_width(),
1786 curenv->get_fill_color());
1787 return;
1788 case ESCAPE_NEWLINE:
1789 have_input = 0;
1790 break;
1791 case ESCAPE_LEFT_BRACE:
1792 ESCAPE_LEFT_BRACE:
1793 type = TOKEN_LEFT_BRACE;
1794 return;
1795 case ESCAPE_RIGHT_BRACE:
1796 ESCAPE_RIGHT_BRACE:
1797 type = TOKEN_RIGHT_BRACE;
1798 return;
1799 case ESCAPE_LEFT_QUOTE:
1800 ESCAPE_LEFT_QUOTE:
1801 type = TOKEN_SPECIAL;
1802 nm = symbol("ga");
1803 return;
1804 case ESCAPE_RIGHT_QUOTE:
1805 ESCAPE_RIGHT_QUOTE:
1806 type = TOKEN_SPECIAL;
1807 nm = symbol("aa");
1808 return;
1809 case ESCAPE_HYPHEN:
1810 ESCAPE_HYPHEN:
1811 type = TOKEN_SPECIAL;
1812 nm = symbol("-");
1813 return;
1814 case ESCAPE_UNDERSCORE:
1815 ESCAPE_UNDERSCORE:
1816 type = TOKEN_SPECIAL;
1817 nm = symbol("ul");
1818 return;
1819 case ESCAPE_c:
1820 ESCAPE_c:
1821 type = TOKEN_INTERRUPT;
1822 return;
1823 case ESCAPE_BANG:
1824 ESCAPE_BANG:
1825 type = TOKEN_TRANSPARENT;
1826 return;
1827 case ESCAPE_QUESTION:
1828 ESCAPE_QUESTION:
1829 nd = do_non_interpreted();
1830 if (nd) {
1831 type = TOKEN_NODE;
1832 return;
1834 break;
1835 case ESCAPE_AMPERSAND:
1836 ESCAPE_AMPERSAND:
1837 type = TOKEN_DUMMY;
1838 return;
1839 case ESCAPE_RIGHT_PARENTHESIS:
1840 ESCAPE_RIGHT_PARENTHESIS:
1841 type = TOKEN_TRANSPARENT_DUMMY;
1842 return;
1843 case '\b':
1844 type = TOKEN_BACKSPACE;
1845 return;
1846 case ' ':
1847 type = TOKEN_SPACE;
1848 return;
1849 case '\t':
1850 type = TOKEN_TAB;
1851 return;
1852 case '\n':
1853 type = TOKEN_NEWLINE;
1854 return;
1855 case '\001':
1856 type = TOKEN_LEADER;
1857 return;
1858 case 0:
1860 assert(n != 0);
1861 token_node *tn = n->get_token_node();
1862 if (tn) {
1863 *this = tn->tk;
1864 delete tn;
1866 else {
1867 nd = n;
1868 type = TOKEN_NODE;
1871 return;
1872 default:
1873 type = TOKEN_CHAR;
1874 c = cc;
1875 return;
1878 else {
1879 handle_escape_char:
1880 cc = input_stack::get(&n);
1881 switch(cc) {
1882 case '(':
1883 nm = read_two_char_escape_name();
1884 type = TOKEN_SPECIAL;
1885 return;
1886 case EOF:
1887 type = TOKEN_EOF;
1888 error("end of input after escape character");
1889 return;
1890 case '`':
1891 goto ESCAPE_LEFT_QUOTE;
1892 case '\'':
1893 goto ESCAPE_RIGHT_QUOTE;
1894 case '-':
1895 goto ESCAPE_HYPHEN;
1896 case '_':
1897 goto ESCAPE_UNDERSCORE;
1898 case '%':
1899 goto ESCAPE_PERCENT;
1900 case ' ':
1901 goto ESCAPE_SPACE;
1902 case '0':
1903 nd = new hmotion_node(curenv->get_digit_width(),
1904 curenv->get_fill_color());
1905 type = TOKEN_NODE;
1906 return;
1907 case '|':
1908 goto ESCAPE_BAR;
1909 case '^':
1910 goto ESCAPE_CIRCUMFLEX;
1911 case '/':
1912 type = TOKEN_ITALIC_CORRECTION;
1913 return;
1914 case ',':
1915 type = TOKEN_NODE;
1916 nd = new left_italic_corrected_node;
1917 return;
1918 case '&':
1919 goto ESCAPE_AMPERSAND;
1920 case ')':
1921 goto ESCAPE_RIGHT_PARENTHESIS;
1922 case '!':
1923 goto ESCAPE_BANG;
1924 case '?':
1925 goto ESCAPE_QUESTION;
1926 case '~':
1927 goto ESCAPE_TILDE;
1928 case ':':
1929 goto ESCAPE_COLON;
1930 case '"':
1931 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1933 if (cc == '\n')
1934 type = TOKEN_NEWLINE;
1935 else
1936 type = TOKEN_EOF;
1937 return;
1938 case '#': // Like \" but newline is ignored.
1939 while ((cc = input_stack::get(0)) != '\n')
1940 if (cc == EOF) {
1941 type = TOKEN_EOF;
1942 return;
1944 break;
1945 case '$':
1947 symbol s = read_escape_name();
1948 if (!(s.is_null() || s.is_empty()))
1949 interpolate_arg(s);
1950 break;
1952 case '*':
1954 symbol s = read_escape_name(WITH_ARGS);
1955 if (!(s.is_null() || s.is_empty())) {
1956 if (have_string_arg) {
1957 have_string_arg = 0;
1958 interpolate_string_with_args(s);
1960 else
1961 interpolate_string(s);
1963 break;
1965 case 'a':
1966 nd = new non_interpreted_char_node('\001');
1967 type = TOKEN_NODE;
1968 return;
1969 case 'A':
1970 c = '0' + do_name_test();
1971 type = TOKEN_CHAR;
1972 return;
1973 case 'b':
1974 nd = do_bracket();
1975 type = TOKEN_NODE;
1976 return;
1977 case 'B':
1978 c = '0' + do_expr_test();
1979 type = TOKEN_CHAR;
1980 return;
1981 case 'c':
1982 goto ESCAPE_c;
1983 case 'C':
1984 nm = get_delim_name();
1985 if (nm.is_null())
1986 break;
1987 type = TOKEN_SPECIAL;
1988 return;
1989 case 'd':
1990 type = TOKEN_NODE;
1991 nd = new vmotion_node(curenv->get_size() / 2,
1992 curenv->get_fill_color());
1993 return;
1994 case 'D':
1995 nd = read_draw_node();
1996 if (!nd)
1997 break;
1998 type = TOKEN_NODE;
1999 return;
2000 case 'e':
2001 goto ESCAPE_e;
2002 case 'E':
2003 goto handle_escape_char;
2004 case 'f':
2006 symbol s = read_escape_name(ALLOW_EMPTY);
2007 if (s.is_null())
2008 break;
2009 const char *p;
2010 for (p = s.contents(); *p != '\0'; p++)
2011 if (!csdigit(*p))
2012 break;
2013 if (*p || s.is_empty())
2014 curenv->set_font(s);
2015 else
2016 curenv->set_font(atoi(s.contents()));
2017 if (!compatible_flag)
2018 have_input = 1;
2019 break;
2021 case 'F':
2023 symbol s = read_escape_name(ALLOW_EMPTY);
2024 if (s.is_null())
2025 break;
2026 curenv->set_family(s);
2027 have_input = 1;
2028 break;
2030 case 'g':
2032 symbol s = read_escape_name();
2033 if (!(s.is_null() || s.is_empty()))
2034 interpolate_number_format(s);
2035 break;
2037 case 'h':
2038 if (!get_delim_number(&x, 'm'))
2039 break;
2040 type = TOKEN_NODE;
2041 nd = new hmotion_node(x, curenv->get_fill_color());
2042 return;
2043 case 'H':
2044 // don't take height increments relative to previous height if
2045 // in compatibility mode
2046 if (!compatible_flag && curenv->get_char_height()) {
2047 if (get_delim_number(&x, 'z', curenv->get_char_height()))
2048 curenv->set_char_height(x);
2050 else {
2051 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
2052 curenv->set_char_height(x);
2054 if (!compatible_flag)
2055 have_input = 1;
2056 break;
2057 case 'k':
2058 nm = read_escape_name();
2059 if (nm.is_null() || nm.is_empty())
2060 break;
2061 type = TOKEN_MARK_INPUT;
2062 return;
2063 case 'l':
2064 case 'L':
2066 charinfo *s = 0;
2067 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
2068 break;
2069 if (s == 0)
2070 s = get_charinfo(cc == 'l' ? "ru" : "br");
2071 type = TOKEN_NODE;
2072 node *char_node = curenv->make_char_node(s);
2073 if (cc == 'l')
2074 nd = new hline_node(x, char_node);
2075 else
2076 nd = new vline_node(x, char_node);
2077 return;
2079 case 'm':
2080 do_glyph_color(read_escape_name(ALLOW_EMPTY));
2081 if (!compatible_flag)
2082 have_input = 1;
2083 break;
2084 case 'M':
2085 do_fill_color(read_escape_name(ALLOW_EMPTY));
2086 if (!compatible_flag)
2087 have_input = 1;
2088 break;
2089 case 'n':
2091 int inc;
2092 symbol s = read_increment_and_escape_name(&inc);
2093 if (!(s.is_null() || s.is_empty()))
2094 interpolate_number_reg(s, inc);
2095 break;
2097 case 'N':
2098 if (!get_delim_number(&val, 0))
2099 break;
2100 if (val < 0) {
2101 warning(WARN_CHAR, "invalid numbered character %1", val);
2102 break;
2104 type = TOKEN_NUMBERED_CHAR;
2105 return;
2106 case 'o':
2107 nd = do_overstrike();
2108 type = TOKEN_NODE;
2109 return;
2110 case 'O':
2111 nd = do_suppress(read_escape_name());
2112 if (!nd)
2113 break;
2114 type = TOKEN_NODE;
2115 return;
2116 case 'p':
2117 type = TOKEN_SPREAD;
2118 return;
2119 case 'r':
2120 type = TOKEN_NODE;
2121 nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
2122 return;
2123 case 'R':
2124 do_register();
2125 if (!compatible_flag)
2126 have_input = 1;
2127 break;
2128 case 's':
2129 if (read_size(&x))
2130 curenv->set_size(x);
2131 if (!compatible_flag)
2132 have_input = 1;
2133 break;
2134 case 'S':
2135 if (get_delim_number(&x, 0))
2136 curenv->set_char_slant(x);
2137 if (!compatible_flag)
2138 have_input = 1;
2139 break;
2140 case 't':
2141 type = TOKEN_NODE;
2142 nd = new non_interpreted_char_node('\t');
2143 return;
2144 case 'u':
2145 type = TOKEN_NODE;
2146 nd = new vmotion_node(-curenv->get_size() / 2,
2147 curenv->get_fill_color());
2148 return;
2149 case 'v':
2150 if (!get_delim_number(&x, 'v'))
2151 break;
2152 type = TOKEN_NODE;
2153 nd = new vmotion_node(x, curenv->get_fill_color());
2154 return;
2155 case 'V':
2157 symbol s = read_escape_name();
2158 if (!(s.is_null() || s.is_empty()))
2159 interpolate_environment_variable(s);
2160 break;
2162 case 'w':
2163 do_width();
2164 break;
2165 case 'x':
2166 if (!get_delim_number(&x, 'v'))
2167 break;
2168 type = TOKEN_NODE;
2169 nd = new extra_size_node(x);
2170 return;
2171 case 'X':
2172 nd = do_special();
2173 if (!nd)
2174 break;
2175 type = TOKEN_NODE;
2176 return;
2177 case 'Y':
2179 symbol s = read_escape_name();
2180 if (s.is_null() || s.is_empty())
2181 break;
2182 request_or_macro *p = lookup_request(s);
2183 macro *m = p->to_macro();
2184 if (!m) {
2185 error("can't transparently throughput a request");
2186 break;
2188 nd = new special_node(*m);
2189 type = TOKEN_NODE;
2190 return;
2192 case 'z':
2194 next();
2195 if (type == TOKEN_NODE)
2196 nd = new zero_width_node(nd);
2197 else {
2198 charinfo *ci = get_char(1);
2199 if (ci == 0)
2200 break;
2201 node *gn = curenv->make_char_node(ci);
2202 if (gn == 0)
2203 break;
2204 nd = new zero_width_node(gn);
2205 type = TOKEN_NODE;
2207 return;
2209 case 'Z':
2210 nd = do_zero_width();
2211 if (nd == 0)
2212 break;
2213 type = TOKEN_NODE;
2214 return;
2215 case '{':
2216 goto ESCAPE_LEFT_BRACE;
2217 case '}':
2218 goto ESCAPE_RIGHT_BRACE;
2219 case '\n':
2220 break;
2221 case '[':
2222 if (!compatible_flag) {
2223 symbol s = read_long_escape_name(WITH_ARGS);
2224 if (s.is_null() || s.is_empty())
2225 break;
2226 if (have_string_arg) {
2227 have_string_arg = 0;
2228 nm = composite_glyph_name(s);
2230 else {
2231 const char *gn = check_unicode_name(s.contents());
2232 if (gn) {
2233 const char *gn_decomposed = decompose_unicode(gn);
2234 if (gn_decomposed)
2235 gn = &gn_decomposed[1];
2236 const char *groff_gn = unicode_to_glyph_name(gn);
2237 if (groff_gn)
2238 nm = symbol(groff_gn);
2239 else {
2240 char *buf = new char[strlen(gn) + 1 + 1];
2241 strcpy(buf, "u");
2242 strcat(buf, gn);
2243 nm = symbol(buf);
2244 a_delete buf;
2247 else
2248 nm = symbol(s.contents());
2250 type = TOKEN_SPECIAL;
2251 return;
2253 goto handle_normal_char;
2254 default:
2255 if (cc != escape_char && cc != '.')
2256 warning(WARN_ESCAPE, "escape character ignored before %1",
2257 input_char_description(cc));
2258 goto handle_normal_char;
2264 int token::operator==(const token &t)
2266 if (type != t.type)
2267 return 0;
2268 switch(type) {
2269 case TOKEN_CHAR:
2270 return c == t.c;
2271 case TOKEN_SPECIAL:
2272 return nm == t.nm;
2273 case TOKEN_NUMBERED_CHAR:
2274 return val == t.val;
2275 default:
2276 return 1;
2280 int token::operator!=(const token &t)
2282 return !(*this == t);
2285 // is token a suitable delimiter (like ')?
2287 int token::delimiter(int err)
2289 switch(type) {
2290 case TOKEN_CHAR:
2291 switch(c) {
2292 case '0':
2293 case '1':
2294 case '2':
2295 case '3':
2296 case '4':
2297 case '5':
2298 case '6':
2299 case '7':
2300 case '8':
2301 case '9':
2302 case '+':
2303 case '-':
2304 case '/':
2305 case '*':
2306 case '%':
2307 case '<':
2308 case '>':
2309 case '=':
2310 case '&':
2311 case ':':
2312 case '(':
2313 case ')':
2314 case '.':
2315 if (err)
2316 error("cannot use character `%1' as a starting delimiter", char(c));
2317 return 0;
2318 default:
2319 return 1;
2321 case TOKEN_NODE:
2322 case TOKEN_SPACE:
2323 case TOKEN_STRETCHABLE_SPACE:
2324 case TOKEN_UNSTRETCHABLE_SPACE:
2325 case TOKEN_TAB:
2326 case TOKEN_NEWLINE:
2327 if (err)
2328 error("cannot use %1 as a starting delimiter", description());
2329 return 0;
2330 default:
2331 return 1;
2335 const char *token::description()
2337 static char buf[4];
2338 switch (type) {
2339 case TOKEN_BACKSPACE:
2340 return "a backspace character";
2341 case TOKEN_CHAR:
2342 buf[0] = '`';
2343 buf[1] = c;
2344 buf[2] = '\'';
2345 buf[3] = '\0';
2346 return buf;
2347 case TOKEN_DUMMY:
2348 return "`\\&'";
2349 case TOKEN_ESCAPE:
2350 return "`\\e'";
2351 case TOKEN_HYPHEN_INDICATOR:
2352 return "`\\%'";
2353 case TOKEN_INTERRUPT:
2354 return "`\\c'";
2355 case TOKEN_ITALIC_CORRECTION:
2356 return "`\\/'";
2357 case TOKEN_LEADER:
2358 return "a leader character";
2359 case TOKEN_LEFT_BRACE:
2360 return "`\\{'";
2361 case TOKEN_MARK_INPUT:
2362 return "`\\k'";
2363 case TOKEN_NEWLINE:
2364 return "newline";
2365 case TOKEN_NODE:
2366 return "a node";
2367 case TOKEN_NUMBERED_CHAR:
2368 return "`\\N'";
2369 case TOKEN_RIGHT_BRACE:
2370 return "`\\}'";
2371 case TOKEN_SPACE:
2372 return "a space";
2373 case TOKEN_SPECIAL:
2374 return "a special character";
2375 case TOKEN_SPREAD:
2376 return "`\\p'";
2377 case TOKEN_STRETCHABLE_SPACE:
2378 return "`\\~'";
2379 case TOKEN_UNSTRETCHABLE_SPACE:
2380 return "`\\ '";
2381 case TOKEN_TAB:
2382 return "a tab character";
2383 case TOKEN_TRANSPARENT:
2384 return "`\\!'";
2385 case TOKEN_TRANSPARENT_DUMMY:
2386 return "`\\)'";
2387 case TOKEN_ZERO_WIDTH_BREAK:
2388 return "`\\:'";
2389 case TOKEN_EOF:
2390 return "end of input";
2391 default:
2392 break;
2394 return "a magic token";
2397 void skip_line()
2399 while (!tok.newline())
2400 if (tok.eof())
2401 return;
2402 else
2403 tok.next();
2404 tok.next();
2407 void compatible()
2409 int n;
2410 if (has_arg() && get_integer(&n))
2411 compatible_flag = n != 0;
2412 else
2413 compatible_flag = 1;
2414 skip_line();
2417 static void empty_name_warning(int required)
2419 if (tok.newline() || tok.eof()) {
2420 if (required)
2421 warning(WARN_MISSING, "missing name");
2423 else if (tok.right_brace() || tok.tab()) {
2424 const char *start = tok.description();
2425 do {
2426 tok.next();
2427 } while (tok.space() || tok.right_brace() || tok.tab());
2428 if (!tok.newline() && !tok.eof())
2429 error("%1 is not allowed before an argument", start);
2430 else if (required)
2431 warning(WARN_MISSING, "missing name");
2433 else if (required)
2434 error("name expected (got %1)", tok.description());
2435 else
2436 error("name expected (got %1): treated as missing", tok.description());
2439 static void non_empty_name_warning()
2441 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2442 && !tok.right_brace()
2443 // We don't want to give a warning for .el\{
2444 && !tok.left_brace())
2445 error("%1 is not allowed in a name", tok.description());
2448 symbol get_name(int required)
2450 if (compatible_flag) {
2451 char buf[3];
2452 tok.skip();
2453 if ((buf[0] = tok.ch()) != 0) {
2454 tok.next();
2455 if ((buf[1] = tok.ch()) != 0) {
2456 buf[2] = 0;
2457 tok.make_space();
2459 else
2460 non_empty_name_warning();
2461 return symbol(buf);
2463 else {
2464 empty_name_warning(required);
2465 return NULL_SYMBOL;
2468 else
2469 return get_long_name(required);
2472 symbol get_long_name(int required)
2474 return do_get_long_name(required, 0);
2477 static symbol do_get_long_name(int required, char end)
2479 while (tok.space())
2480 tok.next();
2481 char abuf[ABUF_SIZE];
2482 char *buf = abuf;
2483 int buf_size = ABUF_SIZE;
2484 int i = 0;
2485 for (;;) {
2486 // If end != 0 we normally have to append a null byte
2487 if (i + 2 > buf_size) {
2488 if (buf == abuf) {
2489 buf = new char[ABUF_SIZE*2];
2490 memcpy(buf, abuf, buf_size);
2491 buf_size = ABUF_SIZE*2;
2493 else {
2494 char *old_buf = buf;
2495 buf = new char[buf_size*2];
2496 memcpy(buf, old_buf, buf_size);
2497 buf_size *= 2;
2498 a_delete old_buf;
2501 if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
2502 break;
2503 i++;
2504 tok.next();
2506 if (i == 0) {
2507 empty_name_warning(required);
2508 return NULL_SYMBOL;
2510 if (end && buf[i] == end)
2511 buf[i+1] = '\0';
2512 else
2513 non_empty_name_warning();
2514 if (buf == abuf)
2515 return symbol(buf);
2516 else {
2517 symbol s(buf);
2518 a_delete buf;
2519 return s;
2523 void exit_troff()
2525 exit_started = 1;
2526 topdiv->set_last_page();
2527 if (!end_macro_name.is_null()) {
2528 spring_trap(end_macro_name);
2529 tok.next();
2530 process_input_stack();
2532 curenv->final_break();
2533 tok.next();
2534 process_input_stack();
2535 end_diversions();
2536 if (topdiv->get_page_length() > 0) {
2537 done_end_macro = 1;
2538 topdiv->set_ejecting();
2539 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2540 input_stack::push(make_temp_iterator((char *)buf));
2541 topdiv->space(topdiv->get_page_length(), 1);
2542 tok.next();
2543 process_input_stack();
2544 seen_last_page_ejector = 1; // should be set already
2545 topdiv->set_ejecting();
2546 push_page_ejector();
2547 topdiv->space(topdiv->get_page_length(), 1);
2548 tok.next();
2549 process_input_stack();
2551 // This will only happen if a trap-invoked macro starts a diversion,
2552 // or if vertical position traps have been disabled.
2553 cleanup_and_exit(0);
2556 // This implements .ex. The input stack must be cleared before calling
2557 // exit_troff().
2559 void exit_request()
2561 input_stack::clear();
2562 if (exit_started)
2563 tok.next();
2564 else
2565 exit_troff();
2568 void return_macro_request()
2570 if (has_arg() && tok.ch())
2571 input_stack::pop_macro();
2572 input_stack::pop_macro();
2573 tok.next();
2576 void end_macro()
2578 end_macro_name = get_name();
2579 skip_line();
2582 void blank_line_macro()
2584 blank_line_macro_name = get_name();
2585 skip_line();
2588 static void trapping_blank_line()
2590 if (!blank_line_macro_name.is_null())
2591 spring_trap(blank_line_macro_name);
2592 else
2593 blank_line();
2596 void do_request()
2598 int old_compatible_flag = compatible_flag;
2599 compatible_flag = 0;
2600 symbol nm = get_name();
2601 if (nm.is_null())
2602 skip_line();
2603 else
2604 interpolate_macro(nm, 1);
2605 compatible_flag = old_compatible_flag;
2606 request_or_macro *p = lookup_request(nm);
2607 macro *m = p->to_macro();
2608 if (m)
2609 tok.next();
2612 inline int possibly_handle_first_page_transition()
2614 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2615 handle_first_page_transition();
2616 return 1;
2618 else
2619 return 0;
2622 static int transparent_translate(int cc)
2624 if (!invalid_input_char(cc)) {
2625 charinfo *ci = charset_table[cc];
2626 switch (ci->get_special_translation(1)) {
2627 case charinfo::TRANSLATE_SPACE:
2628 return ' ';
2629 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2630 return ESCAPE_TILDE;
2631 case charinfo::TRANSLATE_DUMMY:
2632 return ESCAPE_AMPERSAND;
2633 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2634 return ESCAPE_PERCENT;
2636 // This is really ugly.
2637 ci = ci->get_translation(1);
2638 if (ci) {
2639 int c = ci->get_ascii_code();
2640 if (c != '\0')
2641 return c;
2642 error("can't translate %1 to special character `%2'"
2643 " in transparent throughput",
2644 input_char_description(cc),
2645 ci->nm.contents());
2648 return cc;
2651 class int_stack {
2652 struct int_stack_element {
2653 int n;
2654 int_stack_element *next;
2655 } *top;
2656 public:
2657 int_stack();
2658 ~int_stack();
2659 void push(int);
2660 int is_empty();
2661 int pop();
2664 int_stack::int_stack()
2666 top = 0;
2669 int_stack::~int_stack()
2671 while (top != 0) {
2672 int_stack_element *temp = top;
2673 top = top->next;
2674 delete temp;
2678 int int_stack::is_empty()
2680 return top == 0;
2683 void int_stack::push(int n)
2685 int_stack_element *p = new int_stack_element;
2686 p->next = top;
2687 p->n = n;
2688 top = p;
2691 int int_stack::pop()
2693 assert(top != 0);
2694 int_stack_element *p = top;
2695 top = top->next;
2696 int n = p->n;
2697 delete p;
2698 return n;
2701 int node::reread(int *)
2703 return 0;
2706 int global_diverted_space = 0;
2708 int diverted_space_node::reread(int *bolp)
2710 global_diverted_space = 1;
2711 if (curenv->get_fill())
2712 trapping_blank_line();
2713 else
2714 curdiv->space(n);
2715 global_diverted_space = 0;
2716 *bolp = 1;
2717 return 1;
2720 int diverted_copy_file_node::reread(int *bolp)
2722 curdiv->copy_file(filename.contents());
2723 *bolp = 1;
2724 return 1;
2727 int word_space_node::reread(int *)
2729 if (unformat) {
2730 for (width_list *w = orig_width; w; w = w->next)
2731 curenv->space(w->width, w->sentence_width);
2732 unformat = 0;
2733 return 1;
2735 return 0;
2738 int unbreakable_space_node::reread(int *)
2740 return 0;
2743 int hmotion_node::reread(int *)
2745 if (unformat && was_tab) {
2746 curenv->handle_tab(0);
2747 unformat = 0;
2748 return 1;
2750 return 0;
2753 void process_input_stack()
2755 int_stack trap_bol_stack;
2756 int bol = 1;
2757 for (;;) {
2758 int suppress_next = 0;
2759 switch (tok.type) {
2760 case token::TOKEN_CHAR:
2762 unsigned char ch = tok.c;
2763 if (bol && !have_input
2764 && (ch == curenv->control_char
2765 || ch == curenv->no_break_control_char)) {
2766 break_flag = ch == curenv->control_char;
2767 // skip tabs as well as spaces here
2768 do {
2769 tok.next();
2770 } while (tok.white_space());
2771 symbol nm = get_name();
2772 #if defined(DEBUGGING)
2773 if (debug_state) {
2774 if (! nm.is_null()) {
2775 if (strcmp(nm.contents(), "test") == 0) {
2776 fprintf(stderr, "found it!\n");
2777 fflush(stderr);
2779 fprintf(stderr, "interpreting [%s]", nm.contents());
2780 if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
2781 fprintf(stderr, " currently in diversion: %s",
2782 curdiv->get_diversion_name());
2783 fprintf(stderr, "\n");
2784 fflush(stderr);
2787 #endif
2788 if (nm.is_null())
2789 skip_line();
2790 else {
2791 interpolate_macro(nm);
2792 #if defined(DEBUGGING)
2793 if (debug_state) {
2794 fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
2795 curenv->dump_troff_state();
2797 #endif
2799 suppress_next = 1;
2801 else {
2802 if (possibly_handle_first_page_transition())
2804 else {
2805 for (;;) {
2806 #if defined(DEBUGGING)
2807 if (debug_state) {
2808 fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
2810 #endif
2811 curenv->add_char(charset_table[ch]);
2812 tok.next();
2813 if (tok.type != token::TOKEN_CHAR)
2814 break;
2815 ch = tok.c;
2817 suppress_next = 1;
2818 bol = 0;
2821 break;
2823 case token::TOKEN_TRANSPARENT:
2825 if (bol) {
2826 if (possibly_handle_first_page_transition())
2828 else {
2829 int cc;
2830 do {
2831 node *n;
2832 cc = get_copy(&n);
2833 if (cc != EOF)
2834 if (cc != '\0')
2835 curdiv->transparent_output(transparent_translate(cc));
2836 else
2837 curdiv->transparent_output(n);
2838 } while (cc != '\n' && cc != EOF);
2839 if (cc == EOF)
2840 curdiv->transparent_output('\n');
2843 break;
2845 case token::TOKEN_NEWLINE:
2847 if (bol && !old_have_input
2848 && !curenv->get_prev_line_interrupted())
2849 trapping_blank_line();
2850 else {
2851 curenv->newline();
2852 bol = 1;
2854 break;
2856 case token::TOKEN_REQUEST:
2858 int request_code = tok.c;
2859 tok.next();
2860 switch (request_code) {
2861 case TITLE_REQUEST:
2862 title();
2863 break;
2864 case COPY_FILE_REQUEST:
2865 copy_file();
2866 break;
2867 case TRANSPARENT_FILE_REQUEST:
2868 transparent_file();
2869 break;
2870 #ifdef COLUMN
2871 case VJUSTIFY_REQUEST:
2872 vjustify();
2873 break;
2874 #endif /* COLUMN */
2875 default:
2876 assert(0);
2877 break;
2879 suppress_next = 1;
2880 break;
2882 case token::TOKEN_SPACE:
2884 if (possibly_handle_first_page_transition())
2886 else if (bol && !curenv->get_prev_line_interrupted()) {
2887 int nspaces = 0;
2888 // save space_width now so that it isn't changed by \f or \s
2889 // which we wouldn't notice here
2890 hunits space_width = curenv->get_space_width();
2891 do {
2892 nspaces += tok.nspaces();
2893 tok.next();
2894 } while (tok.space());
2895 if (tok.newline())
2896 trapping_blank_line();
2897 else {
2898 push_token(tok);
2899 curenv->do_break();
2900 curenv->add_node(new hmotion_node(space_width * nspaces,
2901 curenv->get_fill_color()));
2902 bol = 0;
2905 else {
2906 curenv->space();
2907 bol = 0;
2909 break;
2911 case token::TOKEN_EOF:
2912 return;
2913 case token::TOKEN_NODE:
2915 if (possibly_handle_first_page_transition())
2917 else if (tok.nd->reread(&bol)) {
2918 delete tok.nd;
2919 tok.nd = 0;
2921 else {
2922 curenv->add_node(tok.nd);
2923 tok.nd = 0;
2924 bol = 0;
2925 curenv->possibly_break_line(1);
2927 break;
2929 case token::TOKEN_PAGE_EJECTOR:
2931 continue_page_eject();
2932 // I think we just want to preserve bol.
2933 // bol = 1;
2934 break;
2936 case token::TOKEN_BEGIN_TRAP:
2938 trap_bol_stack.push(bol);
2939 bol = 1;
2940 have_input = 0;
2941 break;
2943 case token::TOKEN_END_TRAP:
2945 if (trap_bol_stack.is_empty())
2946 error("spurious end trap token detected!");
2947 else
2948 bol = trap_bol_stack.pop();
2949 have_input = 0;
2951 /* I'm not totally happy about this. But I can't think of any other
2952 way to do it. Doing an output_pending_lines() whenever a
2953 TOKEN_END_TRAP is detected doesn't work: for example,
2955 .wh -1i x
2956 .de x
2959 .wh -.5i y
2960 .de y
2961 .tl ''-%-''
2964 .ll .5i
2965 .sp |\n(.pu-1i-.5v
2966 a\%very\%very\%long\%word
2968 will print all but the first lines from the word immediately
2969 after the footer, rather than on the next page. */
2971 if (trap_bol_stack.is_empty())
2972 curenv->output_pending_lines();
2973 break;
2975 default:
2977 bol = 0;
2978 tok.process();
2979 break;
2982 if (!suppress_next)
2983 tok.next();
2984 trap_sprung_flag = 0;
2988 #ifdef WIDOW_CONTROL
2990 void flush_pending_lines()
2992 while (!tok.newline() && !tok.eof())
2993 tok.next();
2994 curenv->output_pending_lines();
2995 tok.next();
2998 #endif /* WIDOW_CONTROL */
3000 request_or_macro::request_or_macro()
3004 macro *request_or_macro::to_macro()
3006 return 0;
3009 request::request(REQUEST_FUNCP pp) : p(pp)
3013 void request::invoke(symbol, int)
3015 (*p)();
3018 struct char_block {
3019 enum { SIZE = 128 };
3020 unsigned char s[SIZE];
3021 char_block *next;
3022 char_block();
3025 char_block::char_block()
3026 : next(0)
3030 class char_list {
3031 public:
3032 char_list();
3033 ~char_list();
3034 void append(unsigned char);
3035 void set(unsigned char, int);
3036 unsigned char get(int);
3037 int length();
3038 private:
3039 unsigned char *ptr;
3040 int len;
3041 char_block *head;
3042 char_block *tail;
3043 friend class macro_header;
3044 friend class string_iterator;
3047 char_list::char_list()
3048 : ptr(0), len(0), head(0), tail(0)
3052 char_list::~char_list()
3054 while (head != 0) {
3055 char_block *tem = head;
3056 head = head->next;
3057 delete tem;
3061 int char_list::length()
3063 return len;
3066 void char_list::append(unsigned char c)
3068 if (tail == 0) {
3069 head = tail = new char_block;
3070 ptr = tail->s;
3072 else {
3073 if (ptr >= tail->s + char_block::SIZE) {
3074 tail->next = new char_block;
3075 tail = tail->next;
3076 ptr = tail->s;
3079 *ptr++ = c;
3080 len++;
3083 void char_list::set(unsigned char c, int offset)
3085 assert(len > offset);
3086 // optimization for access at the end
3087 int boundary = len - len % char_block::SIZE;
3088 if (offset >= boundary) {
3089 *(tail->s + offset - boundary) = c;
3090 return;
3092 char_block *tem = head;
3093 int l = 0;
3094 for (;;) {
3095 l += char_block::SIZE;
3096 if (l > offset) {
3097 *(tem->s + offset % char_block::SIZE) = c;
3098 return;
3100 tem = tem->next;
3104 unsigned char char_list::get(int offset)
3106 assert(len > offset);
3107 // optimization for access at the end
3108 int boundary = len - len % char_block::SIZE;
3109 if (offset >= boundary)
3110 return *(tail->s + offset - boundary);
3111 char_block *tem = head;
3112 int l = 0;
3113 for (;;) {
3114 l += char_block::SIZE;
3115 if (l > offset)
3116 return *(tem->s + offset % char_block::SIZE);
3117 tem = tem->next;
3121 class node_list {
3122 node *head;
3123 node *tail;
3124 public:
3125 node_list();
3126 ~node_list();
3127 void append(node *);
3128 int length();
3129 node *extract();
3131 friend class macro_header;
3132 friend class string_iterator;
3135 void node_list::append(node *n)
3137 if (head == 0) {
3138 n->next = 0;
3139 head = tail = n;
3141 else {
3142 n->next = 0;
3143 tail = tail->next = n;
3147 int node_list::length()
3149 int total = 0;
3150 for (node *n = head; n != 0; n = n->next)
3151 ++total;
3152 return total;
3155 node_list::node_list()
3157 head = tail = 0;
3160 node *node_list::extract()
3162 node *temp = head;
3163 head = tail = 0;
3164 return temp;
3167 node_list::~node_list()
3169 delete_node_list(head);
3172 class macro_header {
3173 public:
3174 int count;
3175 char_list cl;
3176 node_list nl;
3177 macro_header() { count = 1; }
3178 macro_header *copy(int);
3181 macro::~macro()
3183 if (p != 0 && --(p->count) <= 0)
3184 delete p;
3187 macro::macro()
3188 : is_a_diversion(0)
3190 if (!input_stack::get_location(1, &filename, &lineno)) {
3191 filename = 0;
3192 lineno = 0;
3194 len = 0;
3195 empty_macro = 1;
3196 p = 0;
3199 macro::macro(const macro &m)
3200 : filename(m.filename), lineno(m.lineno), len(m.len),
3201 empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion), p(m.p)
3203 if (p != 0)
3204 p->count++;
3207 macro::macro(int is_div)
3208 : is_a_diversion(is_div)
3210 if (!input_stack::get_location(1, &filename, &lineno)) {
3211 filename = 0;
3212 lineno = 0;
3214 len = 0;
3215 empty_macro = 1;
3216 p = 0;
3219 int macro::is_diversion()
3221 return is_a_diversion;
3224 macro &macro::operator=(const macro &m)
3226 // don't assign object
3227 if (m.p != 0)
3228 m.p->count++;
3229 if (p != 0 && --(p->count) <= 0)
3230 delete p;
3231 p = m.p;
3232 filename = m.filename;
3233 lineno = m.lineno;
3234 len = m.len;
3235 empty_macro = m.empty_macro;
3236 is_a_diversion = m.is_a_diversion;
3237 return *this;
3240 void macro::append(unsigned char c)
3242 assert(c != 0);
3243 if (p == 0)
3244 p = new macro_header;
3245 if (p->cl.length() != len) {
3246 macro_header *tem = p->copy(len);
3247 if (--(p->count) <= 0)
3248 delete p;
3249 p = tem;
3251 p->cl.append(c);
3252 ++len;
3253 if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
3254 empty_macro = 0;
3257 void macro::set(unsigned char c, int offset)
3259 assert(p != 0);
3260 assert(c != 0);
3261 p->cl.set(c, offset);
3264 unsigned char macro::get(int offset)
3266 assert(p != 0);
3267 return p->cl.get(offset);
3270 int macro::length()
3272 return len;
3275 void macro::append_str(const char *s)
3277 int i = 0;
3279 if (s) {
3280 while (s[i] != (char)0) {
3281 append(s[i]);
3282 i++;
3287 void macro::append(node *n)
3289 assert(n != 0);
3290 if (p == 0)
3291 p = new macro_header;
3292 if (p->cl.length() != len) {
3293 macro_header *tem = p->copy(len);
3294 if (--(p->count) <= 0)
3295 delete p;
3296 p = tem;
3298 p->cl.append(0);
3299 p->nl.append(n);
3300 ++len;
3301 empty_macro = 0;
3304 void macro::append_unsigned(unsigned int i)
3306 unsigned int j = i / 10;
3307 if (j != 0)
3308 append_unsigned(j);
3309 append(((unsigned char)(((int)'0') + i % 10)));
3312 void macro::append_int(int i)
3314 if (i < 0) {
3315 append('-');
3316 i = -i;
3318 append_unsigned((unsigned int)i);
3321 void macro::print_size()
3323 errprint("%1", len);
3326 // make a copy of the first n bytes
3328 macro_header *macro_header::copy(int n)
3330 macro_header *p = new macro_header;
3331 char_block *bp = cl.head;
3332 unsigned char *ptr = bp->s;
3333 node *nd = nl.head;
3334 while (--n >= 0) {
3335 if (ptr >= bp->s + char_block::SIZE) {
3336 bp = bp->next;
3337 ptr = bp->s;
3339 unsigned char c = *ptr++;
3340 p->cl.append(c);
3341 if (c == 0) {
3342 p->nl.append(nd->copy());
3343 nd = nd->next;
3346 return p;
3349 void print_macros()
3351 object_dictionary_iterator iter(request_dictionary);
3352 request_or_macro *rm;
3353 symbol s;
3354 while (iter.get(&s, (object **)&rm)) {
3355 assert(!s.is_null());
3356 macro *m = rm->to_macro();
3357 if (m) {
3358 errprint("%1\t", s.contents());
3359 m->print_size();
3360 errprint("\n");
3363 fflush(stderr);
3364 skip_line();
3367 class string_iterator : public input_iterator {
3368 macro mac;
3369 const char *how_invoked;
3370 int newline_flag;
3371 int lineno;
3372 char_block *bp;
3373 int count; // of characters remaining
3374 node *nd;
3375 int saved_compatible_flag;
3376 int with_break; // inherited from the caller
3377 protected:
3378 symbol nm;
3379 string_iterator();
3380 public:
3381 string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
3382 int fill(node **);
3383 int peek();
3384 int get_location(int, const char **, int *);
3385 void backtrace();
3386 int get_break_flag() { return with_break; }
3387 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3388 int get_compatible_flag() { return saved_compatible_flag; }
3389 int is_diversion();
3392 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3393 : input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
3394 lineno(1), nm(s)
3396 count = mac.len;
3397 if (count != 0) {
3398 bp = mac.p->cl.head;
3399 nd = mac.p->nl.head;
3400 ptr = eptr = bp->s;
3402 else {
3403 bp = 0;
3404 nd = 0;
3405 ptr = eptr = 0;
3407 with_break = input_stack::get_break_flag();
3410 string_iterator::string_iterator()
3412 bp = 0;
3413 nd = 0;
3414 ptr = eptr = 0;
3415 newline_flag = 0;
3416 how_invoked = 0;
3417 lineno = 1;
3418 count = 0;
3419 with_break = input_stack::get_break_flag();
3422 int string_iterator::is_diversion()
3424 return mac.is_diversion();
3427 int string_iterator::fill(node **np)
3429 if (newline_flag)
3430 lineno++;
3431 newline_flag = 0;
3432 if (count <= 0)
3433 return EOF;
3434 const unsigned char *p = eptr;
3435 if (p >= bp->s + char_block::SIZE) {
3436 bp = bp->next;
3437 p = bp->s;
3439 if (*p == '\0') {
3440 if (np) {
3441 *np = nd->copy();
3442 if (is_diversion())
3443 (*np)->div_nest_level = input_stack::get_div_level();
3444 else
3445 (*np)->div_nest_level = 0;
3447 nd = nd->next;
3448 eptr = ptr = p + 1;
3449 count--;
3450 return 0;
3452 const unsigned char *e = bp->s + char_block::SIZE;
3453 if (e - p > count)
3454 e = p + count;
3455 ptr = p;
3456 while (p < e) {
3457 unsigned char c = *p;
3458 if (c == '\n' || c == ESCAPE_NEWLINE) {
3459 newline_flag = 1;
3460 p++;
3461 break;
3463 if (c == '\0')
3464 break;
3465 p++;
3467 eptr = p;
3468 count -= p - ptr;
3469 return *ptr++;
3472 int string_iterator::peek()
3474 if (count <= 0)
3475 return EOF;
3476 const unsigned char *p = eptr;
3477 if (p >= bp->s + char_block::SIZE) {
3478 p = bp->next->s;
3480 return *p;
3483 int string_iterator::get_location(int allow_macro,
3484 const char **filep, int *linep)
3486 if (!allow_macro)
3487 return 0;
3488 if (mac.filename == 0)
3489 return 0;
3490 *filep = mac.filename;
3491 *linep = mac.lineno + lineno - 1;
3492 return 1;
3495 void string_iterator::backtrace()
3497 if (mac.filename) {
3498 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3499 if (how_invoked) {
3500 if (!nm.is_null())
3501 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3502 else
3503 errprint(": %1\n", how_invoked);
3505 else
3506 errprint("\n");
3510 class temp_iterator : public input_iterator {
3511 unsigned char *base;
3512 temp_iterator(const char *, int len);
3513 public:
3514 ~temp_iterator();
3515 friend input_iterator *make_temp_iterator(const char *);
3518 #ifdef __GNUG__
3519 inline
3520 #endif
3521 temp_iterator::temp_iterator(const char *s, int len)
3523 base = new unsigned char[len];
3524 memcpy(base, s, len);
3525 ptr = base;
3526 eptr = base + len;
3529 temp_iterator::~temp_iterator()
3531 a_delete base;
3534 class small_temp_iterator : public input_iterator {
3535 private:
3536 small_temp_iterator(const char *, int);
3537 ~small_temp_iterator();
3538 enum { BLOCK = 16 };
3539 static small_temp_iterator *free_list;
3540 void *operator new(size_t);
3541 void operator delete(void *);
3542 enum { SIZE = 12 };
3543 unsigned char buf[SIZE];
3544 friend input_iterator *make_temp_iterator(const char *);
3547 small_temp_iterator *small_temp_iterator::free_list = 0;
3549 void *small_temp_iterator::operator new(size_t n)
3551 assert(n == sizeof(small_temp_iterator));
3552 if (!free_list) {
3553 free_list =
3554 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3555 for (int i = 0; i < BLOCK - 1; i++)
3556 free_list[i].next = free_list + i + 1;
3557 free_list[BLOCK-1].next = 0;
3559 small_temp_iterator *p = free_list;
3560 free_list = (small_temp_iterator *)(free_list->next);
3561 p->next = 0;
3562 return p;
3565 #ifdef __GNUG__
3566 inline
3567 #endif
3568 void small_temp_iterator::operator delete(void *p)
3570 if (p) {
3571 ((small_temp_iterator *)p)->next = free_list;
3572 free_list = (small_temp_iterator *)p;
3576 small_temp_iterator::~small_temp_iterator()
3580 #ifdef __GNUG__
3581 inline
3582 #endif
3583 small_temp_iterator::small_temp_iterator(const char *s, int len)
3585 for (int i = 0; i < len; i++)
3586 buf[i] = s[i];
3587 ptr = buf;
3588 eptr = buf + len;
3591 input_iterator *make_temp_iterator(const char *s)
3593 if (s == 0)
3594 return new small_temp_iterator(s, 0);
3595 else {
3596 int n = strlen(s);
3597 if (n <= small_temp_iterator::SIZE)
3598 return new small_temp_iterator(s, n);
3599 else
3600 return new temp_iterator(s, n);
3604 // this is used when macros with arguments are interpolated
3606 struct arg_list {
3607 macro mac;
3608 int space_follows;
3609 arg_list *next;
3610 arg_list(const macro &, int);
3611 ~arg_list();
3614 arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
3618 arg_list::~arg_list()
3622 class macro_iterator : public string_iterator {
3623 arg_list *args;
3624 int argc;
3625 int with_break; // whether called as .foo or 'foo
3626 public:
3627 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
3628 macro_iterator();
3629 ~macro_iterator();
3630 int has_args() { return 1; }
3631 input_iterator *get_arg(int);
3632 int space_follows_arg(int);
3633 int get_break_flag() { return with_break; }
3634 int nargs() { return argc; }
3635 void add_arg(const macro &, int);
3636 void shift(int);
3637 int is_macro() { return 1; }
3638 int is_diversion();
3641 input_iterator *macro_iterator::get_arg(int i)
3643 if (i == 0)
3644 return make_temp_iterator(nm.contents());
3645 if (i > 0 && i <= argc) {
3646 arg_list *p = args;
3647 for (int j = 1; j < i; j++) {
3648 assert(p != 0);
3649 p = p->next;
3651 return new string_iterator(p->mac);
3653 else
3654 return 0;
3657 int macro_iterator::space_follows_arg(int i)
3659 if (i > 0 && i <= argc) {
3660 arg_list *p = args;
3661 for (int j = 1; j < i; j++) {
3662 assert(p != 0);
3663 p = p->next;
3665 return p->space_follows;
3667 else
3668 return 0;
3671 void macro_iterator::add_arg(const macro &m, int s)
3673 arg_list **p;
3674 for (p = &args; *p; p = &((*p)->next))
3676 *p = new arg_list(m, s);
3677 ++argc;
3680 void macro_iterator::shift(int n)
3682 while (n > 0 && argc > 0) {
3683 arg_list *tem = args;
3684 args = args->next;
3685 delete tem;
3686 --argc;
3687 --n;
3691 // This gets used by eg .if '\?xxx\?''.
3693 int operator==(const macro &m1, const macro &m2)
3695 if (m1.len != m2.len)
3696 return 0;
3697 string_iterator iter1(m1);
3698 string_iterator iter2(m2);
3699 int n = m1.len;
3700 while (--n >= 0) {
3701 node *nd1 = 0;
3702 int c1 = iter1.get(&nd1);
3703 assert(c1 != EOF);
3704 node *nd2 = 0;
3705 int c2 = iter2.get(&nd2);
3706 assert(c2 != EOF);
3707 if (c1 != c2) {
3708 if (c1 == 0)
3709 delete nd1;
3710 else if (c2 == 0)
3711 delete nd2;
3712 return 0;
3714 if (c1 == 0) {
3715 assert(nd1 != 0);
3716 assert(nd2 != 0);
3717 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3718 delete nd1;
3719 delete nd2;
3720 if (!are_same)
3721 return 0;
3724 return 1;
3727 static void interpolate_macro(symbol nm, int no_next)
3729 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3730 if (p == 0) {
3731 int warned = 0;
3732 const char *s = nm.contents();
3733 if (strlen(s) > 2) {
3734 request_or_macro *r;
3735 char buf[3];
3736 buf[0] = s[0];
3737 buf[1] = s[1];
3738 buf[2] = '\0';
3739 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3740 if (r) {
3741 macro *m = r->to_macro();
3742 if (!m || !m->empty())
3743 warned = warning(WARN_SPACE,
3744 "macro `%1' not defined "
3745 "(possibly missing space after `%2')",
3746 nm.contents(), buf);
3749 if (!warned) {
3750 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
3751 p = new macro;
3752 request_dictionary.define(nm, p);
3755 if (p)
3756 p->invoke(nm, no_next);
3757 else {
3758 skip_line();
3759 return;
3763 static void decode_args(macro_iterator *mi)
3765 if (!tok.newline() && !tok.eof()) {
3766 node *n;
3767 int c = get_copy(&n);
3768 for (;;) {
3769 while (c == ' ')
3770 c = get_copy(&n);
3771 if (c == '\n' || c == EOF)
3772 break;
3773 macro arg;
3774 int quote_input_level = 0;
3775 int done_tab_warning = 0;
3776 arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
3777 // we store discarded double quotes for \$^
3778 if (c == '"') {
3779 arg.append(DOUBLE_QUOTE);
3780 quote_input_level = input_stack::get_level();
3781 c = get_copy(&n);
3783 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3784 if (quote_input_level > 0 && c == '"'
3785 && (compatible_flag
3786 || input_stack::get_level() == quote_input_level)) {
3787 arg.append(DOUBLE_QUOTE);
3788 c = get_copy(&n);
3789 if (c == '"') {
3790 arg.append(c);
3791 c = get_copy(&n);
3793 else
3794 break;
3796 else {
3797 if (c == 0)
3798 arg.append(n);
3799 else {
3800 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3801 warning(WARN_TAB, "tab character in unquoted macro argument");
3802 done_tab_warning = 1;
3804 arg.append(c);
3806 c = get_copy(&n);
3809 arg.append(POP_GROFFCOMP_MODE);
3810 mi->add_arg(arg, (c == ' '));
3815 static void decode_string_args(macro_iterator *mi)
3817 node *n;
3818 int c = get_copy(&n);
3819 for (;;) {
3820 while (c == ' ')
3821 c = get_copy(&n);
3822 if (c == '\n' || c == EOF) {
3823 error("missing `]'");
3824 break;
3826 if (c == ']')
3827 break;
3828 macro arg;
3829 int quote_input_level = 0;
3830 int done_tab_warning = 0;
3831 if (c == '"') {
3832 quote_input_level = input_stack::get_level();
3833 c = get_copy(&n);
3835 while (c != EOF && c != '\n'
3836 && !(c == ']' && quote_input_level == 0)
3837 && !(c == ' ' && quote_input_level == 0)) {
3838 if (quote_input_level > 0 && c == '"'
3839 && input_stack::get_level() == quote_input_level) {
3840 c = get_copy(&n);
3841 if (c == '"') {
3842 arg.append(c);
3843 c = get_copy(&n);
3845 else
3846 break;
3848 else {
3849 if (c == 0)
3850 arg.append(n);
3851 else {
3852 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3853 warning(WARN_TAB, "tab character in unquoted string argument");
3854 done_tab_warning = 1;
3856 arg.append(c);
3858 c = get_copy(&n);
3861 mi->add_arg(arg, (c == ' '));
3865 void macro::invoke(symbol nm, int no_next)
3867 macro_iterator *mi = new macro_iterator(nm, *this);
3868 decode_args(mi);
3869 input_stack::push(mi);
3870 // we must delay tok.next() in case the function has been called by
3871 // do_request to assure proper handling of compatible_flag
3872 if (!no_next)
3873 tok.next();
3876 macro *macro::to_macro()
3878 return this;
3881 int macro::empty()
3883 return empty_macro == 1;
3886 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called)
3887 : string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
3891 macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
3895 macro_iterator::~macro_iterator()
3897 while (args != 0) {
3898 arg_list *tem = args;
3899 args = args->next;
3900 delete tem;
3904 dictionary composite_dictionary(17);
3906 void composite_request()
3908 symbol from = get_name(1);
3909 if (!from.is_null()) {
3910 const char *from_gn = glyph_name_to_unicode(from.contents());
3911 if (!from_gn) {
3912 from_gn = check_unicode_name(from.contents());
3913 if (!from_gn) {
3914 error("invalid composite glyph name `%1'", from.contents());
3915 skip_line();
3916 return;
3919 const char *from_decomposed = decompose_unicode(from_gn);
3920 if (from_decomposed)
3921 from_gn = &from_decomposed[1];
3922 symbol to = get_name(1);
3923 if (to.is_null())
3924 composite_dictionary.remove(symbol(from_gn));
3925 else {
3926 const char *to_gn = glyph_name_to_unicode(to.contents());
3927 if (!to_gn) {
3928 to_gn = check_unicode_name(to.contents());
3929 if (!to_gn) {
3930 error("invalid composite glyph name `%1'", to.contents());
3931 skip_line();
3932 return;
3935 const char *to_decomposed = decompose_unicode(to_gn);
3936 if (to_decomposed)
3937 to_gn = &to_decomposed[1];
3938 if (strcmp(from_gn, to_gn) == 0)
3939 composite_dictionary.remove(symbol(from_gn));
3940 else
3941 (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
3944 skip_line();
3947 static symbol composite_glyph_name(symbol nm)
3949 macro_iterator *mi = new macro_iterator();
3950 decode_string_args(mi);
3951 input_stack::push(mi);
3952 const char *gn = glyph_name_to_unicode(nm.contents());
3953 if (!gn) {
3954 gn = check_unicode_name(nm.contents());
3955 if (!gn) {
3956 error("invalid base glyph `%1' in composite glyph name", nm.contents());
3957 return EMPTY_SYMBOL;
3960 const char *gn_decomposed = decompose_unicode(gn);
3961 string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
3962 string gl;
3963 int n = input_stack::nargs();
3964 for (int i = 1; i <= n; i++) {
3965 glyph_name += '_';
3966 input_iterator *p = input_stack::get_arg(i);
3967 gl.clear();
3968 int c;
3969 while ((c = p->get(0)) != EOF)
3970 if (c != DOUBLE_QUOTE)
3971 gl += c;
3972 gl += '\0';
3973 const char *u = glyph_name_to_unicode(gl.contents());
3974 if (!u) {
3975 u = check_unicode_name(gl.contents());
3976 if (!u) {
3977 error("invalid component `%1' in composite glyph name",
3978 gl.contents());
3979 return EMPTY_SYMBOL;
3982 const char *decomposed = decompose_unicode(u);
3983 if (decomposed)
3984 u = &decomposed[1];
3985 void *mapped_composite = composite_dictionary.lookup(symbol(u));
3986 if (mapped_composite)
3987 u = (const char *)mapped_composite;
3988 glyph_name += u;
3990 glyph_name += '\0';
3991 const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
3992 if (groff_gn)
3993 return symbol(groff_gn);
3994 gl.clear();
3995 gl += 'u';
3996 gl += glyph_name;
3997 return symbol(gl.contents());
4000 int trap_sprung_flag = 0;
4001 int postpone_traps_flag = 0;
4002 symbol postponed_trap;
4004 void spring_trap(symbol nm)
4006 assert(!nm.is_null());
4007 trap_sprung_flag = 1;
4008 if (postpone_traps_flag) {
4009 postponed_trap = nm;
4010 return;
4012 static char buf[2] = { BEGIN_TRAP, 0 };
4013 static char buf2[2] = { END_TRAP, '\0' };
4014 input_stack::push(make_temp_iterator(buf2));
4015 request_or_macro *p = lookup_request(nm);
4016 macro *m = p->to_macro();
4017 if (m)
4018 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
4019 else
4020 error("you can't invoke a request with a trap");
4021 input_stack::push(make_temp_iterator(buf));
4024 void postpone_traps()
4026 postpone_traps_flag = 1;
4029 int unpostpone_traps()
4031 postpone_traps_flag = 0;
4032 if (!postponed_trap.is_null()) {
4033 spring_trap(postponed_trap);
4034 postponed_trap = NULL_SYMBOL;
4035 return 1;
4037 else
4038 return 0;
4041 void read_request()
4043 macro_iterator *mi = new macro_iterator;
4044 int reading_from_terminal = isatty(fileno(stdin));
4045 int had_prompt = 0;
4046 if (!tok.newline() && !tok.eof()) {
4047 int c = get_copy(0);
4048 while (c == ' ')
4049 c = get_copy(0);
4050 while (c != EOF && c != '\n' && c != ' ') {
4051 if (!invalid_input_char(c)) {
4052 if (reading_from_terminal)
4053 fputc(c, stderr);
4054 had_prompt = 1;
4056 c = get_copy(0);
4058 if (c == ' ') {
4059 tok.make_space();
4060 decode_args(mi);
4063 if (reading_from_terminal) {
4064 fputc(had_prompt ? ':' : '\a', stderr);
4065 fflush(stderr);
4067 input_stack::push(mi);
4068 macro mac;
4069 int nl = 0;
4070 int c;
4071 while ((c = getchar()) != EOF) {
4072 if (invalid_input_char(c))
4073 warning(WARN_INPUT, "invalid input character code %1", int(c));
4074 else {
4075 if (c == '\n') {
4076 if (nl)
4077 break;
4078 else
4079 nl = 1;
4081 else
4082 nl = 0;
4083 mac.append(c);
4086 if (reading_from_terminal)
4087 clearerr(stdin);
4088 input_stack::push(new string_iterator(mac));
4089 tok.next();
4092 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
4093 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
4094 enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
4096 void do_define_string(define_mode mode, comp_mode comp)
4098 symbol nm;
4099 node *n = 0; // pacify compiler
4100 int c;
4101 nm = get_name(1);
4102 if (nm.is_null()) {
4103 skip_line();
4104 return;
4106 if (tok.newline())
4107 c = '\n';
4108 else if (tok.tab())
4109 c = '\t';
4110 else if (!tok.space()) {
4111 error("bad string definition");
4112 skip_line();
4113 return;
4115 else
4116 c = get_copy(&n);
4117 while (c == ' ')
4118 c = get_copy(&n);
4119 if (c == '"')
4120 c = get_copy(&n);
4121 macro mac;
4122 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
4123 macro *mm = rm ? rm->to_macro() : 0;
4124 if (mode == DEFINE_APPEND && mm)
4125 mac = *mm;
4126 if (comp == COMP_DISABLE)
4127 mac.append(PUSH_GROFF_MODE);
4128 else if (comp == COMP_ENABLE)
4129 mac.append(PUSH_COMP_MODE);
4130 while (c != '\n' && c != EOF) {
4131 if (c == 0)
4132 mac.append(n);
4133 else
4134 mac.append((unsigned char)c);
4135 c = get_copy(&n);
4137 if (!mm) {
4138 mm = new macro;
4139 request_dictionary.define(nm, mm);
4141 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4142 mac.append(POP_GROFFCOMP_MODE);
4143 *mm = mac;
4144 tok.next();
4147 void define_string()
4149 do_define_string(DEFINE_NORMAL,
4150 compatible_flag ? COMP_ENABLE: COMP_IGNORE);
4153 void define_nocomp_string()
4155 do_define_string(DEFINE_NORMAL, COMP_DISABLE);
4158 void append_string()
4160 do_define_string(DEFINE_APPEND,
4161 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4164 void append_nocomp_string()
4166 do_define_string(DEFINE_APPEND, COMP_DISABLE);
4169 void do_define_character(char_mode mode, const char *font_name)
4171 node *n = 0; // pacify compiler
4172 int c;
4173 tok.skip();
4174 charinfo *ci = tok.get_char(1);
4175 if (ci == 0) {
4176 skip_line();
4177 return;
4179 if (font_name) {
4180 string s(font_name);
4181 s += ' ';
4182 s += ci->nm.contents();
4183 s += '\0';
4184 ci = get_charinfo(symbol(s.contents()));
4186 tok.next();
4187 if (tok.newline())
4188 c = '\n';
4189 else if (tok.tab())
4190 c = '\t';
4191 else if (!tok.space()) {
4192 error("bad character definition");
4193 skip_line();
4194 return;
4196 else
4197 c = get_copy(&n);
4198 while (c == ' ' || c == '\t')
4199 c = get_copy(&n);
4200 if (c == '"')
4201 c = get_copy(&n);
4202 macro *m = new macro;
4203 while (c != '\n' && c != EOF) {
4204 if (c == 0)
4205 m->append(n);
4206 else
4207 m->append((unsigned char)c);
4208 c = get_copy(&n);
4210 m = ci->setx_macro(m, mode);
4211 if (m)
4212 delete m;
4213 tok.next();
4216 void define_character()
4218 do_define_character(CHAR_NORMAL);
4221 void define_fallback_character()
4223 do_define_character(CHAR_FALLBACK);
4226 void define_special_character()
4228 do_define_character(CHAR_SPECIAL);
4231 static void remove_character()
4233 tok.skip();
4234 while (!tok.newline() && !tok.eof()) {
4235 if (!tok.space() && !tok.tab()) {
4236 charinfo *ci = tok.get_char(1);
4237 if (!ci)
4238 break;
4239 macro *m = ci->set_macro(0);
4240 if (m)
4241 delete m;
4243 tok.next();
4245 skip_line();
4248 static void interpolate_string(symbol nm)
4250 request_or_macro *p = lookup_request(nm);
4251 macro *m = p->to_macro();
4252 if (!m)
4253 error("you can only invoke a string or macro using \\*");
4254 else {
4255 string_iterator *si = new string_iterator(*m, "string", nm);
4256 input_stack::push(si);
4260 static void interpolate_string_with_args(symbol s)
4262 request_or_macro *p = lookup_request(s);
4263 macro *m = p->to_macro();
4264 if (!m)
4265 error("you can only invoke a string or macro using \\*");
4266 else {
4267 macro_iterator *mi = new macro_iterator(s, *m);
4268 decode_string_args(mi);
4269 input_stack::push(mi);
4273 static void interpolate_arg(symbol nm)
4275 const char *s = nm.contents();
4276 if (!s || *s == '\0')
4277 copy_mode_error("missing argument name");
4278 else if (s[1] == 0 && csdigit(s[0]))
4279 input_stack::push(input_stack::get_arg(s[0] - '0'));
4280 else if (s[0] == '*' && s[1] == '\0') {
4281 int limit = input_stack::nargs();
4282 string args;
4283 for (int i = 1; i <= limit; i++) {
4284 input_iterator *p = input_stack::get_arg(i);
4285 int c;
4286 while ((c = p->get(0)) != EOF)
4287 if (c != DOUBLE_QUOTE)
4288 args += c;
4289 if (i != limit)
4290 args += ' ';
4292 if (limit > 0) {
4293 args += '\0';
4294 input_stack::push(make_temp_iterator(args.contents()));
4297 else if (s[0] == '@' && s[1] == '\0') {
4298 int limit = input_stack::nargs();
4299 string args;
4300 for (int i = 1; i <= limit; i++) {
4301 args += '"';
4302 args += char(BEGIN_QUOTE);
4303 input_iterator *p = input_stack::get_arg(i);
4304 int c;
4305 while ((c = p->get(0)) != EOF)
4306 if (c != DOUBLE_QUOTE)
4307 args += c;
4308 args += char(END_QUOTE);
4309 args += '"';
4310 if (i != limit)
4311 args += ' ';
4313 if (limit > 0) {
4314 args += '\0';
4315 input_stack::push(make_temp_iterator(args.contents()));
4318 else if (s[0] == '^' && s[1] == '\0') {
4319 int limit = input_stack::nargs();
4320 string args;
4321 int c = input_stack::peek();
4322 for (int i = 1; i <= limit; i++) {
4323 input_iterator *p = input_stack::get_arg(i);
4324 while ((c = p->get(0)) != EOF) {
4325 if (c == DOUBLE_QUOTE)
4326 c = '"';
4327 args += c;
4329 if (input_stack::space_follows_arg(i))
4330 args += ' ';
4332 if (limit > 0) {
4333 args += '\0';
4334 input_stack::push(make_temp_iterator(args.contents()));
4337 else {
4338 const char *p;
4339 for (p = s; *p && csdigit(*p); p++)
4341 if (*p)
4342 copy_mode_error("bad argument name `%1'", s);
4343 else
4344 input_stack::push(input_stack::get_arg(atoi(s)));
4348 void handle_first_page_transition()
4350 push_token(tok);
4351 topdiv->begin_page();
4354 // We push back a token by wrapping it up in a token_node, and
4355 // wrapping that up in a string_iterator.
4357 static void push_token(const token &t)
4359 macro m;
4360 m.append(new token_node(t));
4361 input_stack::push(new string_iterator(m));
4364 void push_page_ejector()
4366 static char buf[2] = { PAGE_EJECTOR, '\0' };
4367 input_stack::push(make_temp_iterator(buf));
4370 void handle_initial_request(unsigned char code)
4372 char buf[2];
4373 buf[0] = code;
4374 buf[1] = '\0';
4375 macro mac;
4376 mac.append(new token_node(tok));
4377 input_stack::push(new string_iterator(mac));
4378 input_stack::push(make_temp_iterator(buf));
4379 topdiv->begin_page();
4380 tok.next();
4383 void handle_initial_title()
4385 handle_initial_request(TITLE_REQUEST);
4388 // this should be local to define_macro, but cfront 1.2 doesn't support that
4389 static symbol dot_symbol(".");
4391 void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
4393 symbol nm, term;
4394 if (calling == CALLING_INDIRECT) {
4395 symbol temp1 = get_name(1);
4396 if (temp1.is_null()) {
4397 skip_line();
4398 return;
4400 symbol temp2 = get_name();
4401 input_stack::push(make_temp_iterator("\n"));
4402 if (!temp2.is_null()) {
4403 interpolate_string(temp2);
4404 input_stack::push(make_temp_iterator(" "));
4406 interpolate_string(temp1);
4407 input_stack::push(make_temp_iterator(" "));
4408 tok.next();
4410 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4411 nm = get_name(1);
4412 if (nm.is_null()) {
4413 skip_line();
4414 return;
4417 term = get_name(); // the request that terminates the definition
4418 if (term.is_null())
4419 term = dot_symbol;
4420 while (!tok.newline() && !tok.eof())
4421 tok.next();
4422 const char *start_filename;
4423 int start_lineno;
4424 int have_start_location = input_stack::get_location(0, &start_filename,
4425 &start_lineno);
4426 node *n;
4427 // doing this here makes the line numbers come out right
4428 int c = get_copy(&n, 1);
4429 macro mac;
4430 macro *mm = 0;
4431 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4432 request_or_macro *rm =
4433 (request_or_macro *)request_dictionary.lookup(nm);
4434 if (rm)
4435 mm = rm->to_macro();
4436 if (mm && mode == DEFINE_APPEND)
4437 mac = *mm;
4439 int bol = 1;
4440 if (comp == COMP_DISABLE)
4441 mac.append(PUSH_GROFF_MODE);
4442 else if (comp == COMP_ENABLE)
4443 mac.append(PUSH_COMP_MODE);
4444 for (;;) {
4445 while (c == ESCAPE_NEWLINE) {
4446 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
4447 mac.append(c);
4448 c = get_copy(&n, 1);
4450 if (bol && c == '.') {
4451 const char *s = term.contents();
4452 int d = 0;
4453 // see if it matches term
4454 int i = 0;
4455 if (s[0] != 0) {
4456 while ((d = get_copy(&n)) == ' ' || d == '\t')
4458 if ((unsigned char)s[0] == d) {
4459 for (i = 1; s[i] != 0; i++) {
4460 d = get_copy(&n);
4461 if ((unsigned char)s[i] != d)
4462 break;
4466 if (s[i] == 0
4467 && ((i == 2 && compatible_flag)
4468 || (d = get_copy(&n)) == ' '
4469 || d == '\n')) { // we found it
4470 if (d == '\n')
4471 tok.make_newline();
4472 else
4473 tok.make_space();
4474 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4475 if (!mm) {
4476 mm = new macro;
4477 request_dictionary.define(nm, mm);
4479 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4480 mac.append(POP_GROFFCOMP_MODE);
4481 *mm = mac;
4483 if (term != dot_symbol) {
4484 ignoring = 0;
4485 interpolate_macro(term);
4487 else
4488 skip_line();
4489 return;
4491 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4492 mac.append(c);
4493 for (int j = 0; j < i; j++)
4494 mac.append(s[j]);
4496 c = d;
4498 if (c == EOF) {
4499 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4500 if (have_start_location)
4501 error_with_file_and_line(start_filename, start_lineno,
4502 "end of file while defining macro `%1'",
4503 nm.contents());
4504 else
4505 error("end of file while defining macro `%1'", nm.contents());
4507 else {
4508 if (have_start_location)
4509 error_with_file_and_line(start_filename, start_lineno,
4510 "end of file while ignoring input lines");
4511 else
4512 error("end of file while ignoring input lines");
4514 tok.next();
4515 return;
4517 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4518 if (c == 0)
4519 mac.append(n);
4520 else
4521 mac.append(c);
4523 bol = (c == '\n');
4524 c = get_copy(&n, 1);
4528 void define_macro()
4530 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
4531 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4534 void define_nocomp_macro()
4536 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
4539 void define_indirect_macro()
4541 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
4542 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4545 void define_indirect_nocomp_macro()
4547 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
4550 void append_macro()
4552 do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
4553 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4556 void append_nocomp_macro()
4558 do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
4561 void append_indirect_macro()
4563 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
4564 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
4567 void append_indirect_nocomp_macro()
4569 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
4572 void ignore()
4574 ignoring = 1;
4575 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
4576 ignoring = 0;
4579 void remove_macro()
4581 for (;;) {
4582 symbol s = get_name();
4583 if (s.is_null())
4584 break;
4585 request_dictionary.remove(s);
4587 skip_line();
4590 void rename_macro()
4592 symbol s1 = get_name(1);
4593 if (!s1.is_null()) {
4594 symbol s2 = get_name(1);
4595 if (!s2.is_null())
4596 request_dictionary.rename(s1, s2);
4598 skip_line();
4601 void alias_macro()
4603 symbol s1 = get_name(1);
4604 if (!s1.is_null()) {
4605 symbol s2 = get_name(1);
4606 if (!s2.is_null()) {
4607 if (!request_dictionary.alias(s1, s2))
4608 warning(WARN_MAC, "macro `%1' not defined", s2.contents());
4611 skip_line();
4614 void chop_macro()
4616 symbol s = get_name(1);
4617 if (!s.is_null()) {
4618 request_or_macro *p = lookup_request(s);
4619 macro *m = p->to_macro();
4620 if (!m)
4621 error("cannot chop request");
4622 else if (m->empty())
4623 error("cannot chop empty macro");
4624 else {
4625 int have_restore = 0;
4626 // we have to check for additional save/restore pairs which could be
4627 // there due to empty am1 requests.
4628 for (;;) {
4629 if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
4630 break;
4631 have_restore = 1;
4632 m->len -= 1;
4633 if (m->get(m->len - 1) != PUSH_GROFF_MODE
4634 && m->get(m->len - 1) != PUSH_COMP_MODE)
4635 break;
4636 have_restore = 0;
4637 m->len -= 1;
4638 if (m->len == 0)
4639 break;
4641 if (m->len == 0)
4642 error("cannot chop empty macro");
4643 else {
4644 if (have_restore)
4645 m->set(POP_GROFFCOMP_MODE, m->len - 1);
4646 else
4647 m->len -= 1;
4651 skip_line();
4654 void substring_request()
4656 int start; // 0, 1, ..., n-1 or -1, -2, ...
4657 symbol s = get_name(1);
4658 if (!s.is_null() && get_integer(&start)) {
4659 request_or_macro *p = lookup_request(s);
4660 macro *m = p->to_macro();
4661 if (!m)
4662 error("cannot apply `substring' on a request");
4663 else {
4664 int end = -1;
4665 if (!has_arg() || get_integer(&end)) {
4666 int real_length = 0; // 1, 2, ..., n
4667 string_iterator iter1(*m);
4668 for (int l = 0; l < m->len; l++) {
4669 int c = iter1.get(0);
4670 if (c == PUSH_GROFF_MODE
4671 || c == PUSH_COMP_MODE
4672 || c == POP_GROFFCOMP_MODE)
4673 continue;
4674 if (c == EOF)
4675 break;
4676 real_length++;
4678 if (start < 0)
4679 start += real_length;
4680 if (end < 0)
4681 end += real_length;
4682 if (start > end) {
4683 int tem = start;
4684 start = end;
4685 end = tem;
4687 if (start >= real_length || end < 0) {
4688 warning(WARN_RANGE,
4689 "start and end index of substring out of range");
4690 m->len = 0;
4691 if (m->p) {
4692 if (--(m->p->count) <= 0)
4693 delete m->p;
4694 m->p = 0;
4696 skip_line();
4697 return;
4699 if (start < 0) {
4700 warning(WARN_RANGE,
4701 "start index of substring out of range, set to 0");
4702 start = 0;
4704 if (end >= real_length) {
4705 warning(WARN_RANGE,
4706 "end index of substring out of range, set to string length");
4707 end = real_length - 1;
4709 // now extract the substring
4710 string_iterator iter(*m);
4711 int i;
4712 for (i = 0; i < start; i++) {
4713 int c = iter.get(0);
4714 while (c == PUSH_GROFF_MODE
4715 || c == PUSH_COMP_MODE
4716 || c == POP_GROFFCOMP_MODE)
4717 c = iter.get(0);
4718 if (c == EOF)
4719 break;
4721 macro mac;
4722 for (; i <= end; i++) {
4723 node *nd = 0; // pacify compiler
4724 int c = iter.get(&nd);
4725 while (c == PUSH_GROFF_MODE
4726 || c == PUSH_COMP_MODE
4727 || c == POP_GROFFCOMP_MODE)
4728 c = iter.get(0);
4729 if (c == EOF)
4730 break;
4731 if (c == 0)
4732 mac.append(nd);
4733 else
4734 mac.append((unsigned char)c);
4736 *m = mac;
4740 skip_line();
4743 void length_request()
4745 symbol ret;
4746 ret = get_name(1);
4747 if (ret.is_null()) {
4748 skip_line();
4749 return;
4751 int c;
4752 node *n;
4753 if (tok.newline())
4754 c = '\n';
4755 else if (tok.tab())
4756 c = '\t';
4757 else if (!tok.space()) {
4758 error("bad string definition");
4759 skip_line();
4760 return;
4762 else
4763 c = get_copy(&n);
4764 while (c == ' ')
4765 c = get_copy(&n);
4766 if (c == '"')
4767 c = get_copy(&n);
4768 int len = 0;
4769 while (c != '\n' && c != EOF) {
4770 ++len;
4771 c = get_copy(&n);
4773 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4774 if (r)
4775 r->set_value(len);
4776 else
4777 set_number_reg(ret, len);
4778 tok.next();
4781 void asciify_macro()
4783 symbol s = get_name(1);
4784 if (!s.is_null()) {
4785 request_or_macro *p = lookup_request(s);
4786 macro *m = p->to_macro();
4787 if (!m)
4788 error("cannot asciify request");
4789 else {
4790 macro am;
4791 string_iterator iter(*m);
4792 for (;;) {
4793 node *nd = 0; // pacify compiler
4794 int c = iter.get(&nd);
4795 if (c == EOF)
4796 break;
4797 if (c != 0)
4798 am.append(c);
4799 else
4800 nd->asciify(&am);
4802 *m = am;
4805 skip_line();
4808 void unformat_macro()
4810 symbol s = get_name(1);
4811 if (!s.is_null()) {
4812 request_or_macro *p = lookup_request(s);
4813 macro *m = p->to_macro();
4814 if (!m)
4815 error("cannot unformat request");
4816 else {
4817 macro am;
4818 string_iterator iter(*m);
4819 for (;;) {
4820 node *nd = 0; // pacify compiler
4821 int c = iter.get(&nd);
4822 if (c == EOF)
4823 break;
4824 if (c != 0)
4825 am.append(c);
4826 else {
4827 if (nd->set_unformat_flag())
4828 am.append(nd);
4831 *m = am;
4834 skip_line();
4837 static void interpolate_environment_variable(symbol nm)
4839 const char *s = getenv(nm.contents());
4840 if (s && *s)
4841 input_stack::push(make_temp_iterator(s));
4844 void interpolate_number_reg(symbol nm, int inc)
4846 reg *r = lookup_number_reg(nm);
4847 if (inc < 0)
4848 r->decrement();
4849 else if (inc > 0)
4850 r->increment();
4851 input_stack::push(make_temp_iterator(r->get_string()));
4854 static void interpolate_number_format(symbol nm)
4856 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4857 if (r)
4858 input_stack::push(make_temp_iterator(r->get_format()));
4861 static int get_delim_number(units *n, unsigned char si, int prev_value)
4863 token start;
4864 start.next();
4865 if (start.delimiter(1)) {
4866 tok.next();
4867 if (get_number(n, si, prev_value)) {
4868 if (start != tok)
4869 warning(WARN_DELIM, "closing delimiter does not match");
4870 return 1;
4873 return 0;
4876 static int get_delim_number(units *n, unsigned char si)
4878 token start;
4879 start.next();
4880 if (start.delimiter(1)) {
4881 tok.next();
4882 if (get_number(n, si)) {
4883 if (start != tok)
4884 warning(WARN_DELIM, "closing delimiter does not match");
4885 return 1;
4888 return 0;
4891 static int get_line_arg(units *n, unsigned char si, charinfo **cp)
4893 token start;
4894 start.next();
4895 int start_level = input_stack::get_level();
4896 if (!start.delimiter(1))
4897 return 0;
4898 tok.next();
4899 if (get_number(n, si)) {
4900 if (tok.dummy() || tok.transparent_dummy())
4901 tok.next();
4902 if (!(start == tok && input_stack::get_level() == start_level)) {
4903 *cp = tok.get_char(1);
4904 tok.next();
4906 if (!(start == tok && input_stack::get_level() == start_level))
4907 warning(WARN_DELIM, "closing delimiter does not match");
4908 return 1;
4910 return 0;
4913 static int read_size(int *x)
4915 tok.next();
4916 int c = tok.ch();
4917 int inc = 0;
4918 if (c == '-') {
4919 inc = -1;
4920 tok.next();
4921 c = tok.ch();
4923 else if (c == '+') {
4924 inc = 1;
4925 tok.next();
4926 c = tok.ch();
4928 int val = 0; // pacify compiler
4929 int bad = 0;
4930 if (c == '(') {
4931 tok.next();
4932 c = tok.ch();
4933 if (!inc) {
4934 // allow an increment either before or after the left parenthesis
4935 if (c == '-') {
4936 inc = -1;
4937 tok.next();
4938 c = tok.ch();
4940 else if (c == '+') {
4941 inc = 1;
4942 tok.next();
4943 c = tok.ch();
4946 if (!csdigit(c))
4947 bad = 1;
4948 else {
4949 val = c - '0';
4950 tok.next();
4951 c = tok.ch();
4952 if (!csdigit(c))
4953 bad = 1;
4954 else {
4955 val = val*10 + (c - '0');
4956 val *= sizescale;
4960 else if (csdigit(c)) {
4961 val = c - '0';
4962 if (!inc && c != '0' && c < '4') {
4963 tok.next();
4964 c = tok.ch();
4965 if (!csdigit(c))
4966 bad = 1;
4967 else
4968 val = val*10 + (c - '0');
4970 val *= sizescale;
4972 else if (!tok.delimiter(1))
4973 return 0;
4974 else {
4975 token start(tok);
4976 tok.next();
4977 c = tok.ch();
4978 if (!inc && (c == '-' || c == '+')) {
4979 inc = c == '+' ? 1 : -1;
4980 tok.next();
4982 if (!get_number(&val, 'z'))
4983 return 0;
4984 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
4985 if (start.ch() == '[')
4986 error("missing `]'");
4987 else
4988 error("missing closing delimiter");
4989 return 0;
4992 if (!bad) {
4993 switch (inc) {
4994 case 0:
4995 if (val == 0) {
4996 // special case -- \s[0] and \s0 means to revert to previous size
4997 *x = 0;
4998 return 1;
5000 *x = val;
5001 break;
5002 case 1:
5003 *x = curenv->get_requested_point_size() + val;
5004 break;
5005 case -1:
5006 *x = curenv->get_requested_point_size() - val;
5007 break;
5008 default:
5009 assert(0);
5011 if (*x <= 0) {
5012 warning(WARN_RANGE,
5013 "\\s escape results in non-positive point size; set to 1");
5014 *x = 1;
5016 return 1;
5018 else {
5019 error("bad digit in point size");
5020 return 0;
5024 static symbol get_delim_name()
5026 token start;
5027 start.next();
5028 if (start.eof()) {
5029 error("end of input at start of delimited name");
5030 return NULL_SYMBOL;
5032 if (start.newline()) {
5033 error("can't delimit name with a newline");
5034 return NULL_SYMBOL;
5036 int start_level = input_stack::get_level();
5037 char abuf[ABUF_SIZE];
5038 char *buf = abuf;
5039 int buf_size = ABUF_SIZE;
5040 int i = 0;
5041 for (;;) {
5042 if (i + 1 > buf_size) {
5043 if (buf == abuf) {
5044 buf = new char[ABUF_SIZE*2];
5045 memcpy(buf, abuf, buf_size);
5046 buf_size = ABUF_SIZE*2;
5048 else {
5049 char *old_buf = buf;
5050 buf = new char[buf_size*2];
5051 memcpy(buf, old_buf, buf_size);
5052 buf_size *= 2;
5053 a_delete old_buf;
5056 tok.next();
5057 if (tok == start
5058 && (compatible_flag || input_stack::get_level() == start_level))
5059 break;
5060 if ((buf[i] = tok.ch()) == 0) {
5061 error("missing delimiter (got %1)", tok.description());
5062 if (buf != abuf)
5063 a_delete buf;
5064 return NULL_SYMBOL;
5066 i++;
5068 buf[i] = '\0';
5069 if (buf == abuf) {
5070 if (i == 0) {
5071 error("empty delimited name");
5072 return NULL_SYMBOL;
5074 else
5075 return symbol(buf);
5077 else {
5078 symbol s(buf);
5079 a_delete buf;
5080 return s;
5084 // Implement \R
5086 static void do_register()
5088 token start;
5089 start.next();
5090 if (!start.delimiter(1))
5091 return;
5092 tok.next();
5093 symbol nm = get_long_name(1);
5094 if (nm.is_null())
5095 return;
5096 while (tok.space())
5097 tok.next();
5098 reg *r = (reg *)number_reg_dictionary.lookup(nm);
5099 int prev_value;
5100 if (!r || !r->get_value(&prev_value))
5101 prev_value = 0;
5102 int val;
5103 if (!get_number(&val, 'u', prev_value))
5104 return;
5105 if (start != tok)
5106 warning(WARN_DELIM, "closing delimiter does not match");
5107 if (r)
5108 r->set_value(val);
5109 else
5110 set_number_reg(nm, val);
5113 // this implements the \w escape sequence
5115 static void do_width()
5117 token start;
5118 start.next();
5119 int start_level = input_stack::get_level();
5120 environment env(curenv);
5121 environment *oldenv = curenv;
5122 curenv = &env;
5123 for (;;) {
5124 tok.next();
5125 if (tok.eof()) {
5126 warning(WARN_DELIM, "missing closing delimiter");
5127 break;
5129 if (tok.newline()) {
5130 warning(WARN_DELIM, "missing closing delimiter");
5131 input_stack::push(make_temp_iterator("\n"));
5132 break;
5134 if (tok == start
5135 && (compatible_flag || input_stack::get_level() == start_level))
5136 break;
5137 tok.process();
5139 env.wrap_up_tab();
5140 units x = env.get_input_line_position().to_units();
5141 input_stack::push(make_temp_iterator(i_to_a(x)));
5142 env.width_registers();
5143 curenv = oldenv;
5144 have_input = 0;
5147 charinfo *page_character;
5149 void set_page_character()
5151 page_character = get_optional_char();
5152 skip_line();
5155 static const symbol percent_symbol("%");
5157 void read_title_parts(node **part, hunits *part_width)
5159 tok.skip();
5160 if (tok.newline() || tok.eof())
5161 return;
5162 token start(tok);
5163 int start_level = input_stack::get_level();
5164 tok.next();
5165 for (int i = 0; i < 3; i++) {
5166 while (!tok.newline() && !tok.eof()) {
5167 if (tok == start
5168 && (compatible_flag || input_stack::get_level() == start_level)) {
5169 tok.next();
5170 break;
5172 if (page_character != 0 && tok.get_char() == page_character)
5173 interpolate_number_reg(percent_symbol, 0);
5174 else
5175 tok.process();
5176 tok.next();
5178 curenv->wrap_up_tab();
5179 part_width[i] = curenv->get_input_line_position();
5180 part[i] = curenv->extract_output_line();
5182 while (!tok.newline() && !tok.eof())
5183 tok.next();
5186 class non_interpreted_node : public node {
5187 macro mac;
5188 public:
5189 non_interpreted_node(const macro &);
5190 int interpret(macro *);
5191 node *copy();
5192 int ends_sentence();
5193 int same(node *);
5194 const char *type();
5195 int force_tprint();
5196 int is_tag();
5199 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
5203 int non_interpreted_node::ends_sentence()
5205 return 2;
5208 int non_interpreted_node::same(node *nd)
5210 return mac == ((non_interpreted_node *)nd)->mac;
5213 const char *non_interpreted_node::type()
5215 return "non_interpreted_node";
5218 int non_interpreted_node::force_tprint()
5220 return 0;
5223 int non_interpreted_node::is_tag()
5225 return 0;
5228 node *non_interpreted_node::copy()
5230 return new non_interpreted_node(mac);
5233 int non_interpreted_node::interpret(macro *m)
5235 string_iterator si(mac);
5236 node *n = 0; // pacify compiler
5237 for (;;) {
5238 int c = si.get(&n);
5239 if (c == EOF)
5240 break;
5241 if (c == 0)
5242 m->append(n);
5243 else
5244 m->append(c);
5246 return 1;
5249 static node *do_non_interpreted()
5251 node *n;
5252 int c;
5253 macro mac;
5254 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
5255 if (c == 0)
5256 mac.append(n);
5257 else
5258 mac.append(c);
5259 if (c == EOF || c == '\n') {
5260 error("missing \\?");
5261 return 0;
5263 return new non_interpreted_node(mac);
5266 static void encode_char(macro *mac, char c)
5268 if (c == '\0') {
5269 if ((font::use_charnames_in_special) && tok.special()) {
5270 charinfo *ci = tok.get_char(1);
5271 const char *s = ci->get_symbol()->contents();
5272 if (s[0] != (char)0) {
5273 mac->append('\\');
5274 mac->append('[');
5275 int i = 0;
5276 while (s[i] != (char)0) {
5277 mac->append(s[i]);
5278 i++;
5280 mac->append(']');
5283 else if (tok.stretchable_space()
5284 || tok.unstretchable_space())
5285 mac->append(' ');
5286 else if (!(tok.hyphen_indicator()
5287 || tok.dummy()
5288 || tok.transparent_dummy()
5289 || tok.zero_width_break()))
5290 error("%1 is invalid within \\X", tok.description());
5292 else {
5293 if ((font::use_charnames_in_special) && (c == '\\')) {
5295 * add escape escape sequence
5297 mac->append(c);
5299 mac->append(c);
5303 node *do_special()
5305 token start;
5306 start.next();
5307 int start_level = input_stack::get_level();
5308 macro mac;
5309 for (tok.next();
5310 tok != start || input_stack::get_level() != start_level;
5311 tok.next()) {
5312 if (tok.eof()) {
5313 warning(WARN_DELIM, "missing closing delimiter");
5314 return 0;
5316 if (tok.newline()) {
5317 input_stack::push(make_temp_iterator("\n"));
5318 warning(WARN_DELIM, "missing closing delimiter");
5319 break;
5321 unsigned char c;
5322 if (tok.space())
5323 c = ' ';
5324 else if (tok.tab())
5325 c = '\t';
5326 else if (tok.leader())
5327 c = '\001';
5328 else if (tok.backspace())
5329 c = '\b';
5330 else
5331 c = tok.ch();
5332 encode_char(&mac, c);
5334 return new special_node(mac);
5337 void device_request()
5339 if (!tok.newline() && !tok.eof()) {
5340 int c;
5341 macro mac;
5342 for (;;) {
5343 c = get_copy(0);
5344 if (c == '"') {
5345 c = get_copy(0);
5346 break;
5348 if (c != ' ' && c != '\t')
5349 break;
5351 for (; c != '\n' && c != EOF; c = get_copy(0))
5352 mac.append(c);
5353 curenv->add_node(new special_node(mac));
5355 tok.next();
5358 void device_macro_request()
5360 symbol s = get_name(1);
5361 if (!(s.is_null() || s.is_empty())) {
5362 request_or_macro *p = lookup_request(s);
5363 macro *m = p->to_macro();
5364 if (m)
5365 curenv->add_node(new special_node(*m));
5366 else
5367 error("can't transparently throughput a request");
5369 skip_line();
5372 void output_request()
5374 if (!tok.newline() && !tok.eof()) {
5375 int c;
5376 for (;;) {
5377 c = get_copy(0);
5378 if (c == '"') {
5379 c = get_copy(0);
5380 break;
5382 if (c != ' ' && c != '\t')
5383 break;
5385 for (; c != '\n' && c != EOF; c = get_copy(0))
5386 topdiv->transparent_output(c);
5387 topdiv->transparent_output('\n');
5389 tok.next();
5392 extern int image_no; // from node.cpp
5394 static node *do_suppress(symbol nm)
5396 if (nm.is_null() || nm.is_empty()) {
5397 error("expecting an argument to escape \\O");
5398 return 0;
5400 const char *s = nm.contents();
5401 switch (*s) {
5402 case '0':
5403 if (begin_level == 0)
5404 // suppress generation of glyphs
5405 return new suppress_node(0, 0);
5406 break;
5407 case '1':
5408 if (begin_level == 0)
5409 // enable generation of glyphs
5410 return new suppress_node(1, 0);
5411 break;
5412 case '2':
5413 if (begin_level == 0)
5414 return new suppress_node(1, 1);
5415 break;
5416 case '3':
5417 begin_level++;
5418 break;
5419 case '4':
5420 begin_level--;
5421 break;
5422 case '5':
5424 s++; // move over '5'
5425 char position = *s;
5426 if (*s == (char)0) {
5427 error("missing position and filename in \\O");
5428 return 0;
5430 if (!(position == 'l'
5431 || position == 'r'
5432 || position == 'c'
5433 || position == 'i')) {
5434 error("l, r, c, or i position expected (got %1 in \\O)", position);
5435 return 0;
5437 s++; // onto image name
5438 if (s == (char *)0) {
5439 error("missing image name for \\O");
5440 return 0;
5442 image_no++;
5443 if (begin_level == 0)
5444 return new suppress_node(symbol(s), position, image_no);
5446 break;
5447 default:
5448 error("`%1' is an invalid argument to \\O", *s);
5450 return 0;
5453 void special_node::tprint(troff_output_file *out)
5455 tprint_start(out);
5456 string_iterator iter(mac);
5457 for (;;) {
5458 int c = iter.get(0);
5459 if (c == EOF)
5460 break;
5461 for (const char *s = ::asciify(c); *s; s++)
5462 tprint_char(out, *s);
5464 tprint_end(out);
5467 int get_file_line(const char **filename, int *lineno)
5469 return input_stack::get_location(0, filename, lineno);
5472 void line_file()
5474 int n;
5475 if (get_integer(&n)) {
5476 const char *filename = 0;
5477 if (has_arg()) {
5478 symbol s = get_long_name();
5479 filename = s.contents();
5481 (void)input_stack::set_location(filename, n-1);
5483 skip_line();
5486 static int nroff_mode = 0;
5488 static void nroff_request()
5490 nroff_mode = 1;
5491 skip_line();
5494 static void troff_request()
5496 nroff_mode = 0;
5497 skip_line();
5500 static void skip_alternative()
5502 int level = 0;
5503 // ensure that ``.if 0\{'' works as expected
5504 if (tok.left_brace())
5505 level++;
5506 int c;
5507 for (;;) {
5508 c = input_stack::get(0);
5509 if (c == EOF)
5510 break;
5511 if (c == ESCAPE_LEFT_BRACE)
5512 ++level;
5513 else if (c == ESCAPE_RIGHT_BRACE)
5514 --level;
5515 else if (c == escape_char && escape_char > 0)
5516 switch(input_stack::get(0)) {
5517 case '{':
5518 ++level;
5519 break;
5520 case '}':
5521 --level;
5522 break;
5523 case '"':
5524 while ((c = input_stack::get(0)) != '\n' && c != EOF)
5528 Note that the level can properly be < 0, eg
5530 .if 1 \{\
5531 .if 0 \{\
5532 .\}\}
5534 So don't give an error message in this case.
5536 if (level <= 0 && c == '\n')
5537 break;
5539 tok.next();
5542 static void begin_alternative()
5544 while (tok.space() || tok.left_brace())
5545 tok.next();
5548 void nop_request()
5550 while (tok.space())
5551 tok.next();
5554 static int_stack if_else_stack;
5556 int do_if_request()
5558 int invert = 0;
5559 while (tok.space())
5560 tok.next();
5561 while (tok.ch() == '!') {
5562 tok.next();
5563 invert = !invert;
5565 int result;
5566 unsigned char c = tok.ch();
5567 if (c == 't') {
5568 tok.next();
5569 result = !nroff_mode;
5571 else if (c == 'n') {
5572 tok.next();
5573 result = nroff_mode;
5575 else if (c == 'v') {
5576 tok.next();
5577 result = 0;
5579 else if (c == 'o') {
5580 result = (topdiv->get_page_number() & 1);
5581 tok.next();
5583 else if (c == 'e') {
5584 result = !(topdiv->get_page_number() & 1);
5585 tok.next();
5587 else if (c == 'd' || c == 'r') {
5588 tok.next();
5589 symbol nm = get_name(1);
5590 if (nm.is_null()) {
5591 skip_alternative();
5592 return 0;
5594 result = (c == 'd'
5595 ? request_dictionary.lookup(nm) != 0
5596 : number_reg_dictionary.lookup(nm) != 0);
5598 else if (c == 'm') {
5599 tok.next();
5600 symbol nm = get_long_name(1);
5601 if (nm.is_null()) {
5602 skip_alternative();
5603 return 0;
5605 result = (nm == default_symbol
5606 || color_dictionary.lookup(nm) != 0);
5608 else if (c == 'c') {
5609 tok.next();
5610 tok.skip();
5611 charinfo *ci = tok.get_char(1);
5612 if (ci == 0) {
5613 skip_alternative();
5614 return 0;
5616 result = character_exists(ci, curenv);
5617 tok.next();
5619 else if (c == 'F') {
5620 tok.next();
5621 symbol nm = get_long_name(1);
5622 if (nm.is_null()) {
5623 skip_alternative();
5624 return 0;
5626 result = check_font(curenv->get_family()->nm, nm);
5628 else if (c == 'S') {
5629 tok.next();
5630 symbol nm = get_long_name(1);
5631 if (nm.is_null()) {
5632 skip_alternative();
5633 return 0;
5635 result = check_style(nm);
5637 else if (tok.space())
5638 result = 0;
5639 else if (tok.delimiter()) {
5640 token delim = tok;
5641 int delim_level = input_stack::get_level();
5642 environment env1(curenv);
5643 environment env2(curenv);
5644 environment *oldenv = curenv;
5645 curenv = &env1;
5646 suppress_push = 1;
5647 for (int i = 0; i < 2; i++) {
5648 for (;;) {
5649 tok.next();
5650 if (tok.newline() || tok.eof()) {
5651 warning(WARN_DELIM, "missing closing delimiter");
5652 tok.next();
5653 curenv = oldenv;
5654 return 0;
5656 if (tok == delim
5657 && (compatible_flag || input_stack::get_level() == delim_level))
5658 break;
5659 tok.process();
5661 curenv = &env2;
5663 node *n1 = env1.extract_output_line();
5664 node *n2 = env2.extract_output_line();
5665 result = same_node_list(n1, n2);
5666 delete_node_list(n1);
5667 delete_node_list(n2);
5668 curenv = oldenv;
5669 have_input = 0;
5670 suppress_push = 0;
5671 tok.next();
5673 else {
5674 units n;
5675 if (!get_number(&n, 'u')) {
5676 skip_alternative();
5677 return 0;
5679 else
5680 result = n > 0;
5682 if (invert)
5683 result = !result;
5684 if (result)
5685 begin_alternative();
5686 else
5687 skip_alternative();
5688 return result;
5691 void if_else_request()
5693 if_else_stack.push(do_if_request());
5696 void if_request()
5698 do_if_request();
5701 void else_request()
5703 if (if_else_stack.is_empty()) {
5704 warning(WARN_EL, "unbalanced .el request");
5705 skip_alternative();
5707 else {
5708 if (if_else_stack.pop())
5709 skip_alternative();
5710 else
5711 begin_alternative();
5715 static int while_depth = 0;
5716 static int while_break_flag = 0;
5718 void while_request()
5720 macro mac;
5721 int escaped = 0;
5722 int level = 0;
5723 mac.append(new token_node(tok));
5724 for (;;) {
5725 node *n = 0; // pacify compiler
5726 int c = input_stack::get(&n);
5727 if (c == EOF)
5728 break;
5729 if (c == 0) {
5730 escaped = 0;
5731 mac.append(n);
5733 else if (escaped) {
5734 if (c == '{')
5735 level += 1;
5736 else if (c == '}')
5737 level -= 1;
5738 escaped = 0;
5739 mac.append(c);
5741 else {
5742 if (c == ESCAPE_LEFT_BRACE)
5743 level += 1;
5744 else if (c == ESCAPE_RIGHT_BRACE)
5745 level -= 1;
5746 else if (c == escape_char)
5747 escaped = 1;
5748 mac.append(c);
5749 if (c == '\n' && level <= 0)
5750 break;
5753 if (level != 0)
5754 error("unbalanced \\{ \\}");
5755 else {
5756 while_depth++;
5757 input_stack::add_boundary();
5758 for (;;) {
5759 input_stack::push(new string_iterator(mac, "while loop"));
5760 tok.next();
5761 if (!do_if_request()) {
5762 while (input_stack::get(0) != EOF)
5764 break;
5766 process_input_stack();
5767 if (while_break_flag || input_stack::is_return_boundary()) {
5768 while_break_flag = 0;
5769 break;
5772 input_stack::remove_boundary();
5773 while_depth--;
5775 tok.next();
5778 void while_break_request()
5780 if (!while_depth) {
5781 error("no while loop");
5782 skip_line();
5784 else {
5785 while_break_flag = 1;
5786 while (input_stack::get(0) != EOF)
5788 tok.next();
5792 void while_continue_request()
5794 if (!while_depth) {
5795 error("no while loop");
5796 skip_line();
5798 else {
5799 while (input_stack::get(0) != EOF)
5801 tok.next();
5805 // .so
5807 void source()
5809 symbol nm = get_long_name(1);
5810 if (nm.is_null())
5811 skip_line();
5812 else {
5813 while (!tok.newline() && !tok.eof())
5814 tok.next();
5815 errno = 0;
5816 FILE *fp = include_search_path.open_file_cautious(nm.contents());
5817 if (fp)
5818 input_stack::push(new file_iterator(fp, nm.contents()));
5819 else
5820 error("can't open `%1': %2", nm.contents(), strerror(errno));
5821 tok.next();
5825 // like .so but use popen()
5827 void pipe_source()
5829 if (!unsafe_flag) {
5830 error(".pso request not allowed in safer mode");
5831 skip_line();
5833 else {
5834 #ifdef POPEN_MISSING
5835 error("pipes not available on this system");
5836 skip_line();
5837 #else /* not POPEN_MISSING */
5838 if (tok.newline() || tok.eof())
5839 error("missing command");
5840 else {
5841 int c;
5842 while ((c = get_copy(0)) == ' ' || c == '\t')
5844 int buf_size = 24;
5845 char *buf = new char[buf_size];
5846 int buf_used = 0;
5847 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5848 const char *s = asciify(c);
5849 int slen = strlen(s);
5850 if (buf_used + slen + 1> buf_size) {
5851 char *old_buf = buf;
5852 int old_buf_size = buf_size;
5853 buf_size *= 2;
5854 buf = new char[buf_size];
5855 memcpy(buf, old_buf, old_buf_size);
5856 a_delete old_buf;
5858 strcpy(buf + buf_used, s);
5859 buf_used += slen;
5861 buf[buf_used] = '\0';
5862 errno = 0;
5863 FILE *fp = popen(buf, POPEN_RT);
5864 if (fp)
5865 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5866 else
5867 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5868 a_delete buf;
5870 tok.next();
5871 #endif /* not POPEN_MISSING */
5875 // .psbb
5877 static int llx_reg_contents = 0;
5878 static int lly_reg_contents = 0;
5879 static int urx_reg_contents = 0;
5880 static int ury_reg_contents = 0;
5882 struct bounding_box {
5883 int llx, lly, urx, ury;
5886 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5887 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5889 int parse_bounding_box(char *p, bounding_box *bb)
5891 if (sscanf(p, "%d %d %d %d",
5892 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5893 return 1;
5894 else {
5895 /* The Document Structuring Conventions say that the numbers
5896 should be integers. Unfortunately some broken applications
5897 get this wrong. */
5898 double x1, x2, x3, x4;
5899 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5900 bb->llx = (int)x1;
5901 bb->lly = (int)x2;
5902 bb->urx = (int)x3;
5903 bb->ury = (int)x4;
5904 return 1;
5906 else {
5907 for (; *p == ' ' || *p == '\t'; p++)
5909 if (strncmp(p, "(atend)", 7) == 0) {
5910 return 2;
5914 bb->llx = bb->lly = bb->urx = bb->ury = 0;
5915 return 0;
5918 // This version is taken from psrm.cpp
5920 #define PS_LINE_MAX 255
5921 cset white_space("\n\r \t");
5923 int ps_get_line(char *buf, FILE *fp, const char* filename)
5925 int c = getc(fp);
5926 if (c == EOF) {
5927 buf[0] = '\0';
5928 return 0;
5930 int i = 0;
5931 int err = 0;
5932 while (c != '\r' && c != '\n' && c != EOF) {
5933 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
5934 error("invalid input character code %1 in `%2'", int(c), filename);
5935 else if (i < PS_LINE_MAX)
5936 buf[i++] = c;
5937 else if (!err) {
5938 err = 1;
5939 error("PostScript file `%1' is non-conforming "
5940 "because length of line exceeds 255", filename);
5942 c = getc(fp);
5944 buf[i++] = '\n';
5945 buf[i] = '\0';
5946 if (c == '\r') {
5947 c = getc(fp);
5948 if (c != EOF && c != '\n')
5949 ungetc(c, fp);
5951 return 1;
5954 inline void assign_registers(int llx, int lly, int urx, int ury)
5956 llx_reg_contents = llx;
5957 lly_reg_contents = lly;
5958 urx_reg_contents = urx;
5959 ury_reg_contents = ury;
5962 void do_ps_file(FILE *fp, const char* filename)
5964 bounding_box bb;
5965 int bb_at_end = 0;
5966 char buf[PS_LINE_MAX];
5967 llx_reg_contents = lly_reg_contents =
5968 urx_reg_contents = ury_reg_contents = 0;
5969 if (!ps_get_line(buf, fp, filename)) {
5970 error("`%1' is empty", filename);
5971 return;
5973 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
5974 error("`%1' is not conforming to the Document Structuring Conventions",
5975 filename);
5976 return;
5978 while (ps_get_line(buf, fp, filename) != 0) {
5979 // in header comments, `%X' (`X' any printable character except
5980 // whitespace) is possible too
5981 if (buf[0] == '%') {
5982 if (strncmp(buf + 1, "%EndComments", 12) == 0)
5983 break;
5984 if (white_space(buf[1]))
5985 break;
5987 else
5988 break;
5989 if (strncmp(buf + 1, "%BoundingBox:", 13) == 0) {
5990 int res = parse_bounding_box(buf + 14, &bb);
5991 if (res == 1) {
5992 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5993 return;
5995 else if (res == 2) {
5996 bb_at_end = 1;
5997 break;
5999 else {
6000 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6001 filename);
6002 return;
6006 if (bb_at_end) {
6007 long offset;
6008 int last_try = 0;
6009 // in the trailer, the last BoundingBox comment is significant
6010 for (offset = 512; !last_try; offset *= 2) {
6011 int had_trailer = 0;
6012 int got_bb = 0;
6013 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
6014 last_try = 1;
6015 if (fseek(fp, 0L, 0) == -1)
6016 break;
6018 while (ps_get_line(buf, fp, filename) != 0) {
6019 if (buf[0] == '%' && buf[1] == '%') {
6020 if (!had_trailer) {
6021 if (strncmp(buf + 2, "Trailer", 7) == 0)
6022 had_trailer = 1;
6024 else {
6025 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
6026 int res = parse_bounding_box(buf + 14, &bb);
6027 if (res == 1)
6028 got_bb = 1;
6029 else if (res == 2) {
6030 error("`(atend)' not allowed in trailer of `%1'", filename);
6031 return;
6033 else {
6034 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6035 filename);
6036 return;
6042 if (got_bb) {
6043 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6044 return;
6048 error("%%%%BoundingBox comment not found in `%1'", filename);
6051 void ps_bbox_request()
6053 symbol nm = get_long_name(1);
6054 if (nm.is_null())
6055 skip_line();
6056 else {
6057 while (!tok.newline() && !tok.eof())
6058 tok.next();
6059 errno = 0;
6060 // PS files might contain non-printable characters, such as ^Z
6061 // and CRs not followed by an LF, so open them in binary mode.
6062 FILE *fp = include_search_path.open_file_cautious(nm.contents(),
6063 0, FOPEN_RB);
6064 if (fp) {
6065 do_ps_file(fp, nm.contents());
6066 fclose(fp);
6068 else
6069 error("can't open `%1': %2", nm.contents(), strerror(errno));
6070 tok.next();
6074 const char *asciify(int c)
6076 static char buf[3];
6077 buf[0] = escape_char == '\0' ? '\\' : escape_char;
6078 buf[1] = buf[2] = '\0';
6079 switch (c) {
6080 case ESCAPE_QUESTION:
6081 buf[1] = '?';
6082 break;
6083 case ESCAPE_AMPERSAND:
6084 buf[1] = '&';
6085 break;
6086 case ESCAPE_RIGHT_PARENTHESIS:
6087 buf[1] = ')';
6088 break;
6089 case ESCAPE_UNDERSCORE:
6090 buf[1] = '_';
6091 break;
6092 case ESCAPE_BAR:
6093 buf[1] = '|';
6094 break;
6095 case ESCAPE_CIRCUMFLEX:
6096 buf[1] = '^';
6097 break;
6098 case ESCAPE_LEFT_BRACE:
6099 buf[1] = '{';
6100 break;
6101 case ESCAPE_RIGHT_BRACE:
6102 buf[1] = '}';
6103 break;
6104 case ESCAPE_LEFT_QUOTE:
6105 buf[1] = '`';
6106 break;
6107 case ESCAPE_RIGHT_QUOTE:
6108 buf[1] = '\'';
6109 break;
6110 case ESCAPE_HYPHEN:
6111 buf[1] = '-';
6112 break;
6113 case ESCAPE_BANG:
6114 buf[1] = '!';
6115 break;
6116 case ESCAPE_c:
6117 buf[1] = 'c';
6118 break;
6119 case ESCAPE_e:
6120 buf[1] = 'e';
6121 break;
6122 case ESCAPE_E:
6123 buf[1] = 'E';
6124 break;
6125 case ESCAPE_PERCENT:
6126 buf[1] = '%';
6127 break;
6128 case ESCAPE_SPACE:
6129 buf[1] = ' ';
6130 break;
6131 case ESCAPE_TILDE:
6132 buf[1] = '~';
6133 break;
6134 case ESCAPE_COLON:
6135 buf[1] = ':';
6136 break;
6137 case PUSH_GROFF_MODE:
6138 case PUSH_COMP_MODE:
6139 case POP_GROFFCOMP_MODE:
6140 buf[0] = '\0';
6141 break;
6142 default:
6143 if (invalid_input_char(c))
6144 buf[0] = '\0';
6145 else
6146 buf[0] = c;
6147 break;
6149 return buf;
6152 const char *input_char_description(int c)
6154 switch (c) {
6155 case '\n':
6156 return "a newline character";
6157 case '\b':
6158 return "a backspace character";
6159 case '\001':
6160 return "a leader character";
6161 case '\t':
6162 return "a tab character";
6163 case ' ':
6164 return "a space character";
6165 case '\0':
6166 return "a node";
6168 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
6169 if (invalid_input_char(c)) {
6170 const char *s = asciify(c);
6171 if (*s) {
6172 buf[0] = '`';
6173 strcpy(buf + 1, s);
6174 strcat(buf, "'");
6175 return buf;
6177 sprintf(buf, "magic character code %d", c);
6178 return buf;
6180 if (csprint(c)) {
6181 buf[0] = '`';
6182 buf[1] = c;
6183 buf[2] = '\'';
6184 return buf;
6186 sprintf(buf, "character code %d", c);
6187 return buf;
6190 void tag()
6192 if (!tok.newline() && !tok.eof()) {
6193 string s;
6194 int c;
6195 for (;;) {
6196 c = get_copy(0);
6197 if (c == '"') {
6198 c = get_copy(0);
6199 break;
6201 if (c != ' ' && c != '\t')
6202 break;
6204 s = "x X ";
6205 for (; c != '\n' && c != EOF; c = get_copy(0))
6206 s += (char)c;
6207 s += '\n';
6208 curenv->add_node(new tag_node(s, 0));
6210 tok.next();
6213 void taga()
6215 if (!tok.newline() && !tok.eof()) {
6216 string s;
6217 int c;
6218 for (;;) {
6219 c = get_copy(0);
6220 if (c == '"') {
6221 c = get_copy(0);
6222 break;
6224 if (c != ' ' && c != '\t')
6225 break;
6227 s = "x X ";
6228 for (; c != '\n' && c != EOF; c = get_copy(0))
6229 s += (char)c;
6230 s += '\n';
6231 curenv->add_node(new tag_node(s, 1));
6233 tok.next();
6236 // .tm, .tm1, and .tmc
6238 void do_terminal(int newline, int string_like)
6240 if (!tok.newline() && !tok.eof()) {
6241 int c;
6242 for (;;) {
6243 c = get_copy(0);
6244 if (string_like && c == '"') {
6245 c = get_copy(0);
6246 break;
6248 if (c != ' ' && c != '\t')
6249 break;
6251 for (; c != '\n' && c != EOF; c = get_copy(0))
6252 fputs(asciify(c), stderr);
6254 if (newline)
6255 fputc('\n', stderr);
6256 fflush(stderr);
6257 tok.next();
6260 void terminal()
6262 do_terminal(1, 0);
6265 void terminal1()
6267 do_terminal(1, 1);
6270 void terminal_continue()
6272 do_terminal(0, 1);
6275 dictionary stream_dictionary(20);
6277 void do_open(int append)
6279 symbol stream = get_name(1);
6280 if (!stream.is_null()) {
6281 symbol filename = get_long_name(1);
6282 if (!filename.is_null()) {
6283 errno = 0;
6284 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
6285 if (!fp) {
6286 error("can't open `%1' for %2: %3",
6287 filename.contents(),
6288 append ? "appending" : "writing",
6289 strerror(errno));
6290 fp = (FILE *)stream_dictionary.remove(stream);
6292 else
6293 fp = (FILE *)stream_dictionary.lookup(stream, fp);
6294 if (fp)
6295 fclose(fp);
6298 skip_line();
6301 void open_request()
6303 if (!unsafe_flag) {
6304 error(".open request not allowed in safer mode");
6305 skip_line();
6307 else
6308 do_open(0);
6311 void opena_request()
6313 if (!unsafe_flag) {
6314 error(".opena request not allowed in safer mode");
6315 skip_line();
6317 else
6318 do_open(1);
6321 void close_request()
6323 symbol stream = get_name(1);
6324 if (!stream.is_null()) {
6325 FILE *fp = (FILE *)stream_dictionary.remove(stream);
6326 if (!fp)
6327 error("no stream named `%1'", stream.contents());
6328 else
6329 fclose(fp);
6331 skip_line();
6334 // .write and .writec
6336 void do_write_request(int newline)
6338 symbol stream = get_name(1);
6339 if (stream.is_null()) {
6340 skip_line();
6341 return;
6343 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6344 if (!fp) {
6345 error("no stream named `%1'", stream.contents());
6346 skip_line();
6347 return;
6349 int c;
6350 while ((c = get_copy(0)) == ' ')
6352 if (c == '"')
6353 c = get_copy(0);
6354 for (; c != '\n' && c != EOF; c = get_copy(0))
6355 fputs(asciify(c), fp);
6356 if (newline)
6357 fputc('\n', fp);
6358 fflush(fp);
6359 tok.next();
6362 void write_request()
6364 do_write_request(1);
6367 void write_request_continue()
6369 do_write_request(0);
6372 void write_macro_request()
6374 symbol stream = get_name(1);
6375 if (stream.is_null()) {
6376 skip_line();
6377 return;
6379 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6380 if (!fp) {
6381 error("no stream named `%1'", stream.contents());
6382 skip_line();
6383 return;
6385 symbol s = get_name(1);
6386 if (s.is_null()) {
6387 skip_line();
6388 return;
6390 request_or_macro *p = lookup_request(s);
6391 macro *m = p->to_macro();
6392 if (!m)
6393 error("cannot write request");
6394 else {
6395 string_iterator iter(*m);
6396 for (;;) {
6397 int c = iter.get(0);
6398 if (c == EOF)
6399 break;
6400 fputs(asciify(c), fp);
6402 fflush(fp);
6404 skip_line();
6407 void warnscale_request()
6409 if (has_arg()) {
6410 char c = tok.ch();
6411 if (c == 'u')
6412 warn_scale = 1.0;
6413 else if (c == 'i')
6414 warn_scale = (double)units_per_inch;
6415 else if (c == 'c')
6416 warn_scale = (double)units_per_inch / 2.54;
6417 else if (c == 'p')
6418 warn_scale = (double)units_per_inch / 72.0;
6419 else if (c == 'P')
6420 warn_scale = (double)units_per_inch / 6.0;
6421 else {
6422 warning(WARN_SCALE,
6423 "invalid scaling indicator `%1', using `i' instead", c);
6424 c = 'i';
6426 warn_scaling_indicator = c;
6428 skip_line();
6431 void spreadwarn_request()
6433 hunits n;
6434 if (has_arg() && get_hunits(&n, 'm')) {
6435 if (n < 0)
6436 n = 0;
6437 hunits em = curenv->get_size();
6438 spread_limit = (double)n.to_units()
6439 / (em.is_zero() ? hresolution : em.to_units());
6441 else
6442 spread_limit = -spread_limit - 1; // no arg toggles on/off without
6443 // changing value; we mirror at
6444 // -0.5 to make zero a valid value
6445 skip_line();
6448 static void init_charset_table()
6450 char buf[16];
6451 strcpy(buf, "char");
6452 for (int i = 0; i < 256; i++) {
6453 strcpy(buf + 4, i_to_a(i));
6454 charset_table[i] = get_charinfo(symbol(buf));
6455 charset_table[i]->set_ascii_code(i);
6456 if (csalpha(i))
6457 charset_table[i]->set_hyphenation_code(cmlower(i));
6459 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
6460 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
6461 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
6462 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
6463 charset_table['"']->set_flags(charinfo::TRANSPARENT);
6464 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
6465 charset_table[')']->set_flags(charinfo::TRANSPARENT);
6466 charset_table[']']->set_flags(charinfo::TRANSPARENT);
6467 charset_table['*']->set_flags(charinfo::TRANSPARENT);
6468 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
6469 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
6470 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
6471 get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
6472 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6473 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6474 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6475 get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6476 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6477 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
6478 page_character = charset_table['%'];
6481 static void init_hpf_code_table()
6483 for (int i = 0; i < 256; i++)
6484 hpf_code_table[i] = i;
6487 static void do_translate(int translate_transparent, int translate_input)
6489 tok.skip();
6490 while (!tok.newline() && !tok.eof()) {
6491 if (tok.space()) {
6492 // This is a really bizarre troff feature.
6493 tok.next();
6494 translate_space_to_dummy = tok.dummy();
6495 if (tok.newline() || tok.eof())
6496 break;
6497 tok.next();
6498 continue;
6500 charinfo *ci1 = tok.get_char(1);
6501 if (ci1 == 0)
6502 break;
6503 tok.next();
6504 if (tok.newline() || tok.eof()) {
6505 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6506 translate_transparent);
6507 break;
6509 if (tok.space())
6510 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6511 translate_transparent);
6512 else if (tok.stretchable_space())
6513 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
6514 translate_transparent);
6515 else if (tok.dummy())
6516 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
6517 translate_transparent);
6518 else if (tok.hyphen_indicator())
6519 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
6520 translate_transparent);
6521 else {
6522 charinfo *ci2 = tok.get_char(1);
6523 if (ci2 == 0)
6524 break;
6525 if (ci1 == ci2)
6526 ci1->set_translation(0, translate_transparent, translate_input);
6527 else
6528 ci1->set_translation(ci2, translate_transparent, translate_input);
6530 tok.next();
6532 skip_line();
6535 void translate()
6537 do_translate(1, 0);
6540 void translate_no_transparent()
6542 do_translate(0, 0);
6545 void translate_input()
6547 do_translate(1, 1);
6550 void char_flags()
6552 int flags;
6553 if (get_integer(&flags))
6554 while (has_arg()) {
6555 charinfo *ci = tok.get_char(1);
6556 if (ci) {
6557 charinfo *tem = ci->get_translation();
6558 if (tem)
6559 ci = tem;
6560 ci->set_flags(flags);
6562 tok.next();
6564 skip_line();
6567 void hyphenation_code()
6569 tok.skip();
6570 while (!tok.newline() && !tok.eof()) {
6571 charinfo *ci = tok.get_char(1);
6572 if (ci == 0)
6573 break;
6574 tok.next();
6575 tok.skip();
6576 unsigned char c = tok.ch();
6577 if (c == 0) {
6578 error("hyphenation code must be ordinary character");
6579 break;
6581 if (csdigit(c)) {
6582 error("hyphenation code cannot be digit");
6583 break;
6585 ci->set_hyphenation_code(c);
6586 if (ci->get_translation()
6587 && ci->get_translation()->get_translation_input())
6588 ci->get_translation()->set_hyphenation_code(c);
6589 tok.next();
6590 tok.skip();
6592 skip_line();
6595 void hyphenation_patterns_file_code()
6597 tok.skip();
6598 while (!tok.newline() && !tok.eof()) {
6599 int n1, n2;
6600 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
6601 if (!has_arg()) {
6602 error("missing output hyphenation code");
6603 break;
6605 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
6606 hpf_code_table[n1] = n2;
6607 tok.skip();
6609 else {
6610 error("output hyphenation code must be integer in the range 0..255");
6611 break;
6614 else {
6615 error("input hyphenation code must be integer in the range 0..255");
6616 break;
6619 skip_line();
6622 charinfo *token::get_char(int required)
6624 if (type == TOKEN_CHAR)
6625 return charset_table[c];
6626 if (type == TOKEN_SPECIAL)
6627 return get_charinfo(nm);
6628 if (type == TOKEN_NUMBERED_CHAR)
6629 return get_charinfo_by_number(val);
6630 if (type == TOKEN_ESCAPE) {
6631 if (escape_char != 0)
6632 return charset_table[escape_char];
6633 else {
6634 error("`\\e' used while no current escape character");
6635 return 0;
6638 if (required) {
6639 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6640 warning(WARN_MISSING, "missing normal or special character");
6641 else
6642 error("normal or special character expected (got %1)", description());
6644 return 0;
6647 charinfo *get_optional_char()
6649 while (tok.space())
6650 tok.next();
6651 charinfo *ci = tok.get_char();
6652 if (!ci)
6653 check_missing_character();
6654 else
6655 tok.next();
6656 return ci;
6659 void check_missing_character()
6661 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6662 error("normal or special character expected (got %1): "
6663 "treated as missing",
6664 tok.description());
6667 // this is for \Z
6669 int token::add_to_node_list(node **pp)
6671 hunits w;
6672 int s;
6673 node *n = 0;
6674 switch (type) {
6675 case TOKEN_CHAR:
6676 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6677 break;
6678 case TOKEN_DUMMY:
6679 n = new dummy_node;
6680 break;
6681 case TOKEN_ESCAPE:
6682 if (escape_char != 0)
6683 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6684 break;
6685 case TOKEN_HYPHEN_INDICATOR:
6686 *pp = (*pp)->add_discretionary_hyphen();
6687 break;
6688 case TOKEN_ITALIC_CORRECTION:
6689 *pp = (*pp)->add_italic_correction(&w);
6690 break;
6691 case TOKEN_LEFT_BRACE:
6692 break;
6693 case TOKEN_MARK_INPUT:
6694 set_number_reg(nm, curenv->get_input_line_position().to_units());
6695 break;
6696 case TOKEN_NODE:
6697 n = nd;
6698 nd = 0;
6699 break;
6700 case TOKEN_NUMBERED_CHAR:
6701 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6702 break;
6703 case TOKEN_RIGHT_BRACE:
6704 break;
6705 case TOKEN_SPACE:
6706 n = new hmotion_node(curenv->get_space_width(),
6707 curenv->get_fill_color());
6708 break;
6709 case TOKEN_SPECIAL:
6710 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6711 break;
6712 case TOKEN_STRETCHABLE_SPACE:
6713 n = new unbreakable_space_node(curenv->get_space_width(),
6714 curenv->get_fill_color());
6715 break;
6716 case TOKEN_UNSTRETCHABLE_SPACE:
6717 n = new space_char_hmotion_node(curenv->get_space_width(),
6718 curenv->get_fill_color());
6719 break;
6720 case TOKEN_TRANSPARENT_DUMMY:
6721 n = new transparent_dummy_node;
6722 break;
6723 case TOKEN_ZERO_WIDTH_BREAK:
6724 n = new space_node(H0, curenv->get_fill_color());
6725 n->freeze_space();
6726 n->is_escape_colon();
6727 break;
6728 default:
6729 return 0;
6731 if (n) {
6732 n->next = *pp;
6733 *pp = n;
6735 return 1;
6738 void token::process()
6740 if (possibly_handle_first_page_transition())
6741 return;
6742 switch (type) {
6743 case TOKEN_BACKSPACE:
6744 curenv->add_node(new hmotion_node(-curenv->get_space_width(),
6745 curenv->get_fill_color()));
6746 break;
6747 case TOKEN_CHAR:
6748 curenv->add_char(charset_table[c]);
6749 break;
6750 case TOKEN_DUMMY:
6751 curenv->add_node(new dummy_node);
6752 break;
6753 case TOKEN_EMPTY:
6754 assert(0);
6755 break;
6756 case TOKEN_EOF:
6757 assert(0);
6758 break;
6759 case TOKEN_ESCAPE:
6760 if (escape_char != 0)
6761 curenv->add_char(charset_table[escape_char]);
6762 break;
6763 case TOKEN_BEGIN_TRAP:
6764 case TOKEN_END_TRAP:
6765 case TOKEN_PAGE_EJECTOR:
6766 // these are all handled in process_input_stack()
6767 break;
6768 case TOKEN_HYPHEN_INDICATOR:
6769 curenv->add_hyphen_indicator();
6770 break;
6771 case TOKEN_INTERRUPT:
6772 curenv->interrupt();
6773 break;
6774 case TOKEN_ITALIC_CORRECTION:
6775 curenv->add_italic_correction();
6776 break;
6777 case TOKEN_LEADER:
6778 curenv->handle_tab(1);
6779 break;
6780 case TOKEN_LEFT_BRACE:
6781 break;
6782 case TOKEN_MARK_INPUT:
6783 set_number_reg(nm, curenv->get_input_line_position().to_units());
6784 break;
6785 case TOKEN_NEWLINE:
6786 curenv->newline();
6787 break;
6788 case TOKEN_NODE:
6789 curenv->add_node(nd);
6790 nd = 0;
6791 break;
6792 case TOKEN_NUMBERED_CHAR:
6793 curenv->add_char(get_charinfo_by_number(val));
6794 break;
6795 case TOKEN_REQUEST:
6796 // handled in process_input_stack()
6797 break;
6798 case TOKEN_RIGHT_BRACE:
6799 break;
6800 case TOKEN_SPACE:
6801 curenv->space();
6802 break;
6803 case TOKEN_SPECIAL:
6804 curenv->add_char(get_charinfo(nm));
6805 break;
6806 case TOKEN_SPREAD:
6807 curenv->spread();
6808 break;
6809 case TOKEN_STRETCHABLE_SPACE:
6810 curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
6811 curenv->get_fill_color()));
6812 break;
6813 case TOKEN_UNSTRETCHABLE_SPACE:
6814 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
6815 curenv->get_fill_color()));
6816 break;
6817 case TOKEN_TAB:
6818 curenv->handle_tab(0);
6819 break;
6820 case TOKEN_TRANSPARENT:
6821 break;
6822 case TOKEN_TRANSPARENT_DUMMY:
6823 curenv->add_node(new transparent_dummy_node);
6824 break;
6825 case TOKEN_ZERO_WIDTH_BREAK:
6827 node *tmp = new space_node(H0, curenv->get_fill_color());
6828 tmp->freeze_space();
6829 tmp->is_escape_colon();
6830 curenv->add_node(tmp);
6831 break;
6833 default:
6834 assert(0);
6838 class nargs_reg : public reg {
6839 public:
6840 const char *get_string();
6843 const char *nargs_reg::get_string()
6845 return i_to_a(input_stack::nargs());
6848 class lineno_reg : public reg {
6849 public:
6850 const char *get_string();
6853 const char *lineno_reg::get_string()
6855 int line;
6856 const char *file;
6857 if (!input_stack::get_location(0, &file, &line))
6858 line = 0;
6859 return i_to_a(line);
6862 class writable_lineno_reg : public general_reg {
6863 public:
6864 writable_lineno_reg();
6865 void set_value(units);
6866 int get_value(units *);
6869 writable_lineno_reg::writable_lineno_reg()
6873 int writable_lineno_reg::get_value(units *res)
6875 int line;
6876 const char *file;
6877 if (!input_stack::get_location(0, &file, &line))
6878 return 0;
6879 *res = line;
6880 return 1;
6883 void writable_lineno_reg::set_value(units n)
6885 input_stack::set_location(0, n);
6888 class filename_reg : public reg {
6889 public:
6890 const char *get_string();
6893 const char *filename_reg::get_string()
6895 int line;
6896 const char *file;
6897 if (input_stack::get_location(0, &file, &line))
6898 return file;
6899 else
6900 return 0;
6903 class break_flag_reg : public reg {
6904 public:
6905 const char *get_string();
6908 const char *break_flag_reg::get_string()
6910 return i_to_a(input_stack::get_break_flag());
6913 class constant_reg : public reg {
6914 const char *s;
6915 public:
6916 constant_reg(const char *);
6917 const char *get_string();
6920 constant_reg::constant_reg(const char *p) : s(p)
6924 const char *constant_reg::get_string()
6926 return s;
6929 constant_int_reg::constant_int_reg(int *q) : p(q)
6933 const char *constant_int_reg::get_string()
6935 return i_to_a(*p);
6938 void abort_request()
6940 int c;
6941 if (tok.eof())
6942 c = EOF;
6943 else if (tok.newline())
6944 c = '\n';
6945 else {
6946 while ((c = get_copy(0)) == ' ')
6949 if (c == EOF || c == '\n')
6950 fputs("User Abort.", stderr);
6951 else {
6952 for (; c != '\n' && c != EOF; c = get_copy(0))
6953 fputs(asciify(c), stderr);
6955 fputc('\n', stderr);
6956 cleanup_and_exit(1);
6959 char *read_string()
6961 int len = 256;
6962 char *s = new char[len];
6963 int c;
6964 while ((c = get_copy(0)) == ' ')
6966 int i = 0;
6967 while (c != '\n' && c != EOF) {
6968 if (!invalid_input_char(c)) {
6969 if (i + 2 > len) {
6970 char *tem = s;
6971 s = new char[len*2];
6972 memcpy(s, tem, len);
6973 len *= 2;
6974 a_delete tem;
6976 s[i++] = c;
6978 c = get_copy(0);
6980 s[i] = '\0';
6981 tok.next();
6982 if (i == 0) {
6983 a_delete s;
6984 return 0;
6986 return s;
6989 void pipe_output()
6991 if (!unsafe_flag) {
6992 error(".pi request not allowed in safer mode");
6993 skip_line();
6995 else {
6996 #ifdef POPEN_MISSING
6997 error("pipes not available on this system");
6998 skip_line();
6999 #else /* not POPEN_MISSING */
7000 if (the_output) {
7001 error("can't pipe: output already started");
7002 skip_line();
7004 else {
7005 char *pc;
7006 if ((pc = read_string()) == 0)
7007 error("can't pipe to empty command");
7008 if (pipe_command) {
7009 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
7010 strcpy(s, pipe_command);
7011 strcat(s, "|");
7012 strcat(s, pc);
7013 a_delete pipe_command;
7014 a_delete pc;
7015 pipe_command = s;
7017 else
7018 pipe_command = pc;
7020 #endif /* not POPEN_MISSING */
7024 static int system_status;
7026 void system_request()
7028 if (!unsafe_flag) {
7029 error(".sy request not allowed in safer mode");
7030 skip_line();
7032 else {
7033 char *command = read_string();
7034 if (!command)
7035 error("empty command");
7036 else {
7037 system_status = system(command);
7038 a_delete command;
7043 void copy_file()
7045 if (curdiv == topdiv && topdiv->before_first_page) {
7046 handle_initial_request(COPY_FILE_REQUEST);
7047 return;
7049 symbol filename = get_long_name(1);
7050 while (!tok.newline() && !tok.eof())
7051 tok.next();
7052 if (break_flag)
7053 curenv->do_break();
7054 if (!filename.is_null())
7055 curdiv->copy_file(filename.contents());
7056 tok.next();
7059 #ifdef COLUMN
7061 void vjustify()
7063 if (curdiv == topdiv && topdiv->before_first_page) {
7064 handle_initial_request(VJUSTIFY_REQUEST);
7065 return;
7067 symbol type = get_long_name(1);
7068 if (!type.is_null())
7069 curdiv->vjustify(type);
7070 skip_line();
7073 #endif /* COLUMN */
7075 void transparent_file()
7077 if (curdiv == topdiv && topdiv->before_first_page) {
7078 handle_initial_request(TRANSPARENT_FILE_REQUEST);
7079 return;
7081 symbol filename = get_long_name(1);
7082 while (!tok.newline() && !tok.eof())
7083 tok.next();
7084 if (break_flag)
7085 curenv->do_break();
7086 if (!filename.is_null()) {
7087 errno = 0;
7088 FILE *fp = include_search_path.open_file_cautious(filename.contents());
7089 if (!fp)
7090 error("can't open `%1': %2", filename.contents(), strerror(errno));
7091 else {
7092 int bol = 1;
7093 for (;;) {
7094 int c = getc(fp);
7095 if (c == EOF)
7096 break;
7097 if (invalid_input_char(c))
7098 warning(WARN_INPUT, "invalid input character code %1", int(c));
7099 else {
7100 curdiv->transparent_output(c);
7101 bol = c == '\n';
7104 if (!bol)
7105 curdiv->transparent_output('\n');
7106 fclose(fp);
7109 tok.next();
7112 class page_range {
7113 int first;
7114 int last;
7115 public:
7116 page_range *next;
7117 page_range(int, int, page_range *);
7118 int contains(int n);
7121 page_range::page_range(int i, int j, page_range *p)
7122 : first(i), last(j), next(p)
7126 int page_range::contains(int n)
7128 return n >= first && (last <= 0 || n <= last);
7131 page_range *output_page_list = 0;
7133 int in_output_page_list(int n)
7135 if (!output_page_list)
7136 return 1;
7137 for (page_range *p = output_page_list; p; p = p->next)
7138 if (p->contains(n))
7139 return 1;
7140 return 0;
7143 static void parse_output_page_list(char *p)
7145 for (;;) {
7146 int i;
7147 if (*p == '-')
7148 i = 1;
7149 else if (csdigit(*p)) {
7150 i = 0;
7152 i = i*10 + *p++ - '0';
7153 while (csdigit(*p));
7155 else
7156 break;
7157 int j;
7158 if (*p == '-') {
7159 p++;
7160 j = 0;
7161 if (csdigit(*p)) {
7163 j = j*10 + *p++ - '0';
7164 while (csdigit(*p));
7167 else
7168 j = i;
7169 if (j == 0)
7170 last_page_number = -1;
7171 else if (last_page_number >= 0 && j > last_page_number)
7172 last_page_number = j;
7173 output_page_list = new page_range(i, j, output_page_list);
7174 if (*p != ',')
7175 break;
7176 ++p;
7178 if (*p != '\0') {
7179 error("bad output page list");
7180 output_page_list = 0;
7184 static FILE *open_mac_file(const char *mac, char **path)
7186 // Try first FOOBAR.tmac, then tmac.FOOBAR
7187 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
7188 strcpy(s1, mac);
7189 strcat(s1, MACRO_POSTFIX);
7190 FILE *fp = mac_path->open_file(s1, path);
7191 a_delete s1;
7192 if (!fp) {
7193 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
7194 strcpy(s2, MACRO_PREFIX);
7195 strcat(s2, mac);
7196 fp = mac_path->open_file(s2, path);
7197 a_delete s2;
7199 return fp;
7202 static void process_macro_file(const char *mac)
7204 char *path;
7205 FILE *fp = open_mac_file(mac, &path);
7206 if (!fp)
7207 fatal("can't find macro file %1", mac);
7208 const char *s = symbol(path).contents();
7209 a_delete path;
7210 input_stack::push(new file_iterator(fp, s));
7211 tok.next();
7212 process_input_stack();
7215 static void process_startup_file(const char *filename)
7217 char *path;
7218 search_path *orig_mac_path = mac_path;
7219 mac_path = &config_macro_path;
7220 FILE *fp = mac_path->open_file(filename, &path);
7221 if (fp) {
7222 input_stack::push(new file_iterator(fp, symbol(path).contents()));
7223 a_delete path;
7224 tok.next();
7225 process_input_stack();
7227 mac_path = orig_mac_path;
7230 void macro_source()
7232 symbol nm = get_long_name(1);
7233 if (nm.is_null())
7234 skip_line();
7235 else {
7236 while (!tok.newline() && !tok.eof())
7237 tok.next();
7238 char *path;
7239 FILE *fp = mac_path->open_file(nm.contents(), &path);
7240 // .mso doesn't (and cannot) go through open_mac_file, so we
7241 // need to do it here manually: If we have tmac.FOOBAR, try
7242 // FOOBAR.tmac and vice versa
7243 if (!fp) {
7244 const char *fn = nm.contents();
7245 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
7246 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
7247 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
7248 strcat(s, MACRO_POSTFIX);
7249 fp = mac_path->open_file(s, &path);
7250 a_delete s;
7252 if (!fp) {
7253 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
7254 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
7255 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
7256 strcpy(s, MACRO_PREFIX);
7257 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
7258 fp = mac_path->open_file(s, &path);
7259 a_delete s;
7263 if (fp) {
7264 input_stack::push(new file_iterator(fp, symbol(path).contents()));
7265 a_delete path;
7267 else
7268 error("can't find macro file `%1'", nm.contents());
7269 tok.next();
7273 static void process_input_file(const char *name)
7275 FILE *fp;
7276 if (strcmp(name, "-") == 0) {
7277 clearerr(stdin);
7278 fp = stdin;
7280 else {
7281 errno = 0;
7282 fp = include_search_path.open_file_cautious(name);
7283 if (!fp)
7284 fatal("can't open `%1': %2", name, strerror(errno));
7286 input_stack::push(new file_iterator(fp, name));
7287 tok.next();
7288 process_input_stack();
7291 // make sure the_input is empty before calling this
7293 static int evaluate_expression(const char *expr, units *res)
7295 input_stack::push(make_temp_iterator(expr));
7296 tok.next();
7297 int success = get_number(res, 'u');
7298 while (input_stack::get(0) != EOF)
7300 return success;
7303 static void do_register_assignment(const char *s)
7305 const char *p = strchr(s, '=');
7306 if (!p) {
7307 char buf[2];
7308 buf[0] = s[0];
7309 buf[1] = 0;
7310 units n;
7311 if (evaluate_expression(s + 1, &n))
7312 set_number_reg(buf, n);
7314 else {
7315 char *buf = new char[p - s + 1];
7316 memcpy(buf, s, p - s);
7317 buf[p - s] = 0;
7318 units n;
7319 if (evaluate_expression(p + 1, &n))
7320 set_number_reg(buf, n);
7321 a_delete buf;
7325 static void set_string(const char *name, const char *value)
7327 macro *m = new macro;
7328 for (const char *p = value; *p; p++)
7329 if (!invalid_input_char((unsigned char)*p))
7330 m->append(*p);
7331 request_dictionary.define(name, m);
7334 static void do_string_assignment(const char *s)
7336 const char *p = strchr(s, '=');
7337 if (!p) {
7338 char buf[2];
7339 buf[0] = s[0];
7340 buf[1] = 0;
7341 set_string(buf, s + 1);
7343 else {
7344 char *buf = new char[p - s + 1];
7345 memcpy(buf, s, p - s);
7346 buf[p - s] = 0;
7347 set_string(buf, p + 1);
7348 a_delete buf;
7352 struct string_list {
7353 const char *s;
7354 string_list *next;
7355 string_list(const char *ss) : s(ss), next(0) {}
7358 #if 0
7359 static void prepend_string(const char *s, string_list **p)
7361 string_list *l = new string_list(s);
7362 l->next = *p;
7363 *p = l;
7365 #endif
7367 static void add_string(const char *s, string_list **p)
7369 while (*p)
7370 p = &((*p)->next);
7371 *p = new string_list(s);
7374 void usage(FILE *stream, const char *prog)
7376 fprintf(stream,
7377 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
7378 " -rcn -Tname -Fdir -Idir -Mdir [files...]\n",
7379 prog);
7382 int main(int argc, char **argv)
7384 program_name = argv[0];
7385 static char stderr_buf[BUFSIZ];
7386 setbuf(stderr, stderr_buf);
7387 int c;
7388 string_list *macros = 0;
7389 string_list *register_assignments = 0;
7390 string_list *string_assignments = 0;
7391 int iflag = 0;
7392 int tflag = 0;
7393 int fflag = 0;
7394 int nflag = 0;
7395 int no_rc = 0; // don't process troffrc and troffrc-end
7396 int next_page_number = 0; // pacify compiler
7397 opterr = 0;
7398 hresolution = vresolution = 1;
7399 // restore $PATH if called from groff
7400 char* groff_path = getenv("GROFF_PATH__");
7401 if (groff_path) {
7402 string e = "PATH";
7403 e += '=';
7404 if (*groff_path)
7405 e += groff_path;
7406 e += '\0';
7407 if (putenv(strsave(e.contents())))
7408 fatal("putenv failed");
7410 static const struct option long_options[] = {
7411 { "help", no_argument, 0, CHAR_MAX + 1 },
7412 { "version", no_argument, 0, 'v' },
7413 { 0, 0, 0, 0 }
7415 #if defined(DEBUGGING)
7416 #define DEBUG_OPTION "D"
7417 #endif
7418 while ((c = getopt_long(argc, argv,
7419 "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
7420 DEBUG_OPTION, long_options, 0))
7421 != EOF)
7422 switch(c) {
7423 case 'v':
7425 printf("GNU troff (groff) version %s\n", Version_string);
7426 exit(0);
7427 break;
7429 case 'I':
7430 // Search path for .psbb files
7431 // and most other non-system input files.
7432 include_search_path.command_line_dir(optarg);
7433 break;
7434 case 'T':
7435 device = optarg;
7436 tflag = 1;
7437 is_html = (strcmp(device, "html") == 0);
7438 break;
7439 case 'C':
7440 compatible_flag = 1;
7441 // fall through
7442 case 'c':
7443 color_flag = 0;
7444 break;
7445 case 'M':
7446 macro_path.command_line_dir(optarg);
7447 safer_macro_path.command_line_dir(optarg);
7448 config_macro_path.command_line_dir(optarg);
7449 break;
7450 case 'F':
7451 font::command_line_font_dir(optarg);
7452 break;
7453 case 'm':
7454 add_string(optarg, &macros);
7455 break;
7456 case 'E':
7457 inhibit_errors = 1;
7458 break;
7459 case 'R':
7460 no_rc = 1;
7461 break;
7462 case 'w':
7463 enable_warning(optarg);
7464 break;
7465 case 'W':
7466 disable_warning(optarg);
7467 break;
7468 case 'i':
7469 iflag = 1;
7470 break;
7471 case 'b':
7472 backtrace_flag = 1;
7473 break;
7474 case 'a':
7475 ascii_output_flag = 1;
7476 break;
7477 case 'z':
7478 suppress_output_flag = 1;
7479 break;
7480 case 'n':
7481 if (sscanf(optarg, "%d", &next_page_number) == 1)
7482 nflag++;
7483 else
7484 error("bad page number");
7485 break;
7486 case 'o':
7487 parse_output_page_list(optarg);
7488 break;
7489 case 'd':
7490 if (*optarg == '\0')
7491 error("`-d' requires non-empty argument");
7492 else
7493 add_string(optarg, &string_assignments);
7494 break;
7495 case 'r':
7496 if (*optarg == '\0')
7497 error("`-r' requires non-empty argument");
7498 else
7499 add_string(optarg, &register_assignments);
7500 break;
7501 case 'f':
7502 default_family = symbol(optarg);
7503 fflag = 1;
7504 break;
7505 case 'q':
7506 case 's':
7507 case 't':
7508 // silently ignore these
7509 break;
7510 case 'U':
7511 unsafe_flag = 1; // unsafe behaviour
7512 break;
7513 #if defined(DEBUGGING)
7514 case 'D':
7515 debug_state = 1;
7516 break;
7517 #endif
7518 case CHAR_MAX + 1: // --help
7519 usage(stdout, argv[0]);
7520 exit(0);
7521 break;
7522 case '?':
7523 usage(stderr, argv[0]);
7524 exit(1);
7525 break; // never reached
7526 default:
7527 assert(0);
7529 if (unsafe_flag)
7530 mac_path = &macro_path;
7531 set_string(".T", device);
7532 init_charset_table();
7533 init_hpf_code_table();
7534 if (!font::load_desc())
7535 fatal("sorry, I can't continue");
7536 units_per_inch = font::res;
7537 hresolution = font::hor;
7538 vresolution = font::vert;
7539 sizescale = font::sizescale;
7540 tcommand_flag = font::tcommand;
7541 warn_scale = (double)units_per_inch;
7542 warn_scaling_indicator = 'i';
7543 if (!fflag && font::family != 0 && *font::family != '\0')
7544 default_family = symbol(font::family);
7545 font_size::init_size_table(font::sizes);
7546 int i;
7547 int j = 1;
7548 if (font::style_table) {
7549 for (i = 0; font::style_table[i]; i++)
7550 mount_style(j++, symbol(font::style_table[i]));
7552 for (i = 0; font::font_name_table[i]; i++, j++)
7553 // In the DESC file a font name of 0 (zero) means leave this
7554 // position empty.
7555 if (strcmp(font::font_name_table[i], "0") != 0)
7556 mount_font(j, symbol(font::font_name_table[i]));
7557 curdiv = topdiv = new top_level_diversion;
7558 if (nflag)
7559 topdiv->set_next_page_number(next_page_number);
7560 init_input_requests();
7561 init_env_requests();
7562 init_div_requests();
7563 #ifdef COLUMN
7564 init_column_requests();
7565 #endif /* COLUMN */
7566 init_node_requests();
7567 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
7568 init_registers();
7569 init_reg_requests();
7570 init_hyphen_requests();
7571 init_environments();
7572 while (string_assignments) {
7573 do_string_assignment(string_assignments->s);
7574 string_list *tem = string_assignments;
7575 string_assignments = string_assignments->next;
7576 delete tem;
7578 while (register_assignments) {
7579 do_register_assignment(register_assignments->s);
7580 string_list *tem = register_assignments;
7581 register_assignments = register_assignments->next;
7582 delete tem;
7584 if (!no_rc)
7585 process_startup_file(INITIAL_STARTUP_FILE);
7586 while (macros) {
7587 process_macro_file(macros->s);
7588 string_list *tem = macros;
7589 macros = macros->next;
7590 delete tem;
7592 if (!no_rc)
7593 process_startup_file(FINAL_STARTUP_FILE);
7594 for (i = optind; i < argc; i++)
7595 process_input_file(argv[i]);
7596 if (optind >= argc || iflag)
7597 process_input_file("-");
7598 exit_troff();
7599 return 0; // not reached
7602 void warn_request()
7604 int n;
7605 if (has_arg() && get_integer(&n)) {
7606 if (n & ~WARN_TOTAL) {
7607 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
7608 n &= WARN_TOTAL;
7610 warning_mask = n;
7612 else
7613 warning_mask = WARN_TOTAL;
7614 skip_line();
7617 static void init_registers()
7619 #ifdef LONG_FOR_TIME_T
7620 long
7621 #else /* not LONG_FOR_TIME_T */
7622 time_t
7623 #endif /* not LONG_FOR_TIME_T */
7624 t = time(0);
7625 // Use struct here to work around misfeature in old versions of g++.
7626 struct tm *tt = localtime(&t);
7627 set_number_reg("seconds", int(tt->tm_sec));
7628 set_number_reg("minutes", int(tt->tm_min));
7629 set_number_reg("hours", int(tt->tm_hour));
7630 set_number_reg("dw", int(tt->tm_wday + 1));
7631 set_number_reg("dy", int(tt->tm_mday));
7632 set_number_reg("mo", int(tt->tm_mon + 1));
7633 set_number_reg("year", int(1900 + tt->tm_year));
7634 set_number_reg("yr", int(tt->tm_year));
7635 set_number_reg("$$", getpid());
7636 number_reg_dictionary.define(".A",
7637 new constant_reg(ascii_output_flag
7638 ? "1"
7639 : "0"));
7643 * registers associated with \O
7646 static int output_reg_minx_contents = -1;
7647 static int output_reg_miny_contents = -1;
7648 static int output_reg_maxx_contents = -1;
7649 static int output_reg_maxy_contents = -1;
7651 void check_output_limits(int x, int y)
7653 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
7654 output_reg_minx_contents = x;
7655 if (x > output_reg_maxx_contents)
7656 output_reg_maxx_contents = x;
7657 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
7658 output_reg_miny_contents = y;
7659 if (y > output_reg_maxy_contents)
7660 output_reg_maxy_contents = y;
7663 void reset_output_registers()
7665 output_reg_minx_contents = -1;
7666 output_reg_miny_contents = -1;
7667 output_reg_maxx_contents = -1;
7668 output_reg_maxy_contents = -1;
7671 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
7673 *minx = output_reg_minx_contents;
7674 *miny = output_reg_miny_contents;
7675 *maxx = output_reg_maxx_contents;
7676 *maxy = output_reg_maxy_contents;
7679 void init_input_requests()
7681 init_request("ab", abort_request);
7682 init_request("als", alias_macro);
7683 init_request("am", append_macro);
7684 init_request("am1", append_nocomp_macro);
7685 init_request("ami", append_indirect_macro);
7686 init_request("ami1", append_indirect_nocomp_macro);
7687 init_request("as", append_string);
7688 init_request("as1", append_nocomp_string);
7689 init_request("asciify", asciify_macro);
7690 init_request("backtrace", backtrace_request);
7691 init_request("blm", blank_line_macro);
7692 init_request("break", while_break_request);
7693 init_request("cf", copy_file);
7694 init_request("cflags", char_flags);
7695 init_request("char", define_character);
7696 init_request("chop", chop_macro);
7697 init_request("close", close_request);
7698 init_request("color", activate_color);
7699 init_request("composite", composite_request);
7700 init_request("continue", while_continue_request);
7701 init_request("cp", compatible);
7702 init_request("de", define_macro);
7703 init_request("de1", define_nocomp_macro);
7704 init_request("defcolor", define_color);
7705 init_request("dei", define_indirect_macro);
7706 init_request("dei1", define_indirect_nocomp_macro);
7707 init_request("device", device_request);
7708 init_request("devicem", device_macro_request);
7709 init_request("do", do_request);
7710 init_request("ds", define_string);
7711 init_request("ds1", define_nocomp_string);
7712 init_request("ec", set_escape_char);
7713 init_request("ecr", restore_escape_char);
7714 init_request("ecs", save_escape_char);
7715 init_request("el", else_request);
7716 init_request("em", end_macro);
7717 init_request("eo", escape_off);
7718 init_request("ex", exit_request);
7719 init_request("fchar", define_fallback_character);
7720 #ifdef WIDOW_CONTROL
7721 init_request("fpl", flush_pending_lines);
7722 #endif /* WIDOW_CONTROL */
7723 init_request("hcode", hyphenation_code);
7724 init_request("hpfcode", hyphenation_patterns_file_code);
7725 init_request("ie", if_else_request);
7726 init_request("if", if_request);
7727 init_request("ig", ignore);
7728 init_request("length", length_request);
7729 init_request("lf", line_file);
7730 init_request("mso", macro_source);
7731 init_request("nop", nop_request);
7732 init_request("nroff", nroff_request);
7733 init_request("nx", next_file);
7734 init_request("open", open_request);
7735 init_request("opena", opena_request);
7736 init_request("output", output_request);
7737 init_request("pc", set_page_character);
7738 init_request("pi", pipe_output);
7739 init_request("pm", print_macros);
7740 init_request("psbb", ps_bbox_request);
7741 #ifndef POPEN_MISSING
7742 init_request("pso", pipe_source);
7743 #endif /* not POPEN_MISSING */
7744 init_request("rchar", remove_character);
7745 init_request("rd", read_request);
7746 init_request("return", return_macro_request);
7747 init_request("rm", remove_macro);
7748 init_request("rn", rename_macro);
7749 init_request("schar", define_special_character);
7750 init_request("shift", shift);
7751 init_request("so", source);
7752 init_request("spreadwarn", spreadwarn_request);
7753 init_request("substring", substring_request);
7754 init_request("sy", system_request);
7755 init_request("tag", tag);
7756 init_request("taga", taga);
7757 init_request("tm", terminal);
7758 init_request("tm1", terminal1);
7759 init_request("tmc", terminal_continue);
7760 init_request("tr", translate);
7761 init_request("trf", transparent_file);
7762 init_request("trin", translate_input);
7763 init_request("trnt", translate_no_transparent);
7764 init_request("troff", troff_request);
7765 init_request("unformat", unformat_macro);
7766 #ifdef COLUMN
7767 init_request("vj", vjustify);
7768 #endif /* COLUMN */
7769 init_request("warn", warn_request);
7770 init_request("warnscale", warnscale_request);
7771 init_request("while", while_request);
7772 init_request("write", write_request);
7773 init_request("writec", write_request_continue);
7774 init_request("writem", write_macro_request);
7775 number_reg_dictionary.define(".$", new nargs_reg);
7776 number_reg_dictionary.define(".br", new break_flag_reg);
7777 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
7778 number_reg_dictionary.define(".O", new variable_reg(&begin_level));
7779 number_reg_dictionary.define(".c", new lineno_reg);
7780 number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
7781 number_reg_dictionary.define(".F", new filename_reg);
7782 number_reg_dictionary.define(".g", new constant_reg("1"));
7783 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
7784 number_reg_dictionary.define(".R", new constant_reg("10000"));
7785 number_reg_dictionary.define(".U", new constant_int_reg(&unsafe_flag));
7786 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
7787 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
7788 extern const char *major_version;
7789 number_reg_dictionary.define(".x", new constant_reg(major_version));
7790 extern const char *revision;
7791 number_reg_dictionary.define(".Y", new constant_reg(revision));
7792 extern const char *minor_version;
7793 number_reg_dictionary.define(".y", new constant_reg(minor_version));
7794 number_reg_dictionary.define("c.", new writable_lineno_reg);
7795 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
7796 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
7797 number_reg_dictionary.define("opmaxx",
7798 new variable_reg(&output_reg_maxx_contents));
7799 number_reg_dictionary.define("opmaxy",
7800 new variable_reg(&output_reg_maxy_contents));
7801 number_reg_dictionary.define("opminx",
7802 new variable_reg(&output_reg_minx_contents));
7803 number_reg_dictionary.define("opminy",
7804 new variable_reg(&output_reg_miny_contents));
7805 number_reg_dictionary.define("slimit",
7806 new variable_reg(&input_stack::limit));
7807 number_reg_dictionary.define("systat", new variable_reg(&system_status));
7808 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
7809 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
7812 object_dictionary request_dictionary(501);
7814 void init_request(const char *s, REQUEST_FUNCP f)
7816 request_dictionary.define(s, new request(f));
7819 static request_or_macro *lookup_request(symbol nm)
7821 assert(!nm.is_null());
7822 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
7823 if (p == 0) {
7824 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
7825 p = new macro;
7826 request_dictionary.define(nm, p);
7828 return p;
7831 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7833 // Don't interpret character definitions in compatible mode.
7834 int old_compatible_flag = compatible_flag;
7835 compatible_flag = 0;
7836 int old_escape_char = escape_char;
7837 escape_char = '\\';
7838 macro *mac = ci->set_macro(0);
7839 assert(mac != 0);
7840 environment *oldenv = curenv;
7841 environment env(envp);
7842 curenv = &env;
7843 curenv->set_composite();
7844 token old_tok = tok;
7845 input_stack::add_boundary();
7846 string_iterator *si =
7847 new string_iterator(*mac, "composite character", ci->nm);
7848 input_stack::push(si);
7849 // we don't use process_input_stack, because we don't want to recognise
7850 // requests
7851 for (;;) {
7852 tok.next();
7853 if (tok.eof())
7854 break;
7855 if (tok.newline()) {
7856 error("composite character mustn't contain newline");
7857 while (!tok.eof())
7858 tok.next();
7859 break;
7861 else
7862 tok.process();
7864 node *n = curenv->extract_output_line();
7865 input_stack::remove_boundary();
7866 ci->set_macro(mac);
7867 tok = old_tok;
7868 curenv = oldenv;
7869 compatible_flag = old_compatible_flag;
7870 escape_char = old_escape_char;
7871 have_input = 0;
7872 return n;
7875 static node *read_draw_node()
7877 token start;
7878 start.next();
7879 if (!start.delimiter(1)){
7880 do {
7881 tok.next();
7882 } while (tok != start && !tok.newline() && !tok.eof());
7884 else {
7885 tok.next();
7886 if (tok == start)
7887 error("missing argument");
7888 else {
7889 unsigned char type = tok.ch();
7890 if (type == 'F') {
7891 read_color_draw_node(start);
7892 return 0;
7894 tok.next();
7895 int maxpoints = 10;
7896 hvpair *point = new hvpair[maxpoints];
7897 int npoints = 0;
7898 int no_last_v = 0;
7899 int err = 0;
7900 int i;
7901 for (i = 0; tok != start; i++) {
7902 if (i == maxpoints) {
7903 hvpair *oldpoint = point;
7904 point = new hvpair[maxpoints*2];
7905 for (int j = 0; j < maxpoints; j++)
7906 point[j] = oldpoint[j];
7907 maxpoints *= 2;
7908 a_delete oldpoint;
7910 if (!get_hunits(&point[i].h,
7911 type == 'f' || type == 't' ? 'u' : 'm')) {
7912 err = 1;
7913 break;
7915 ++npoints;
7916 tok.skip();
7917 point[i].v = V0;
7918 if (tok == start) {
7919 no_last_v = 1;
7920 break;
7922 if (!get_vunits(&point[i].v, 'v')) {
7923 err = 1;
7924 break;
7926 tok.skip();
7928 while (tok != start && !tok.newline() && !tok.eof())
7929 tok.next();
7930 if (!err) {
7931 switch (type) {
7932 case 'l':
7933 if (npoints != 1 || no_last_v) {
7934 error("two arguments needed for line");
7935 npoints = 1;
7937 break;
7938 case 'c':
7939 if (npoints != 1 || !no_last_v) {
7940 error("one argument needed for circle");
7941 npoints = 1;
7942 point[0].v = V0;
7944 break;
7945 case 'e':
7946 if (npoints != 1 || no_last_v) {
7947 error("two arguments needed for ellipse");
7948 npoints = 1;
7950 break;
7951 case 'a':
7952 if (npoints != 2 || no_last_v) {
7953 error("four arguments needed for arc");
7954 npoints = 2;
7956 break;
7957 case '~':
7958 if (no_last_v)
7959 error("even number of arguments needed for spline");
7960 break;
7961 case 'f':
7962 if (npoints != 1 || !no_last_v) {
7963 error("one argument needed for gray shade");
7964 npoints = 1;
7965 point[0].v = V0;
7967 default:
7968 // silently pass it through
7969 break;
7971 draw_node *dn = new draw_node(type, point, npoints,
7972 curenv->get_font_size(),
7973 curenv->get_glyph_color(),
7974 curenv->get_fill_color());
7975 a_delete point;
7976 return dn;
7978 else {
7979 a_delete point;
7983 return 0;
7986 static void read_color_draw_node(token &start)
7988 tok.next();
7989 if (tok == start) {
7990 error("missing color scheme");
7991 return;
7993 unsigned char scheme = tok.ch();
7994 tok.next();
7995 color *col = 0;
7996 char end = start.ch();
7997 switch (scheme) {
7998 case 'c':
7999 col = read_cmy(end);
8000 break;
8001 case 'd':
8002 col = &default_color;
8003 break;
8004 case 'g':
8005 col = read_gray(end);
8006 break;
8007 case 'k':
8008 col = read_cmyk(end);
8009 break;
8010 case 'r':
8011 col = read_rgb(end);
8012 break;
8014 if (col)
8015 curenv->set_fill_color(col);
8016 while (tok != start) {
8017 if (tok.newline() || tok.eof()) {
8018 warning(WARN_DELIM, "missing closing delimiter");
8019 input_stack::push(make_temp_iterator("\n"));
8020 break;
8022 tok.next();
8024 have_input = 1;
8027 static struct {
8028 const char *name;
8029 int mask;
8030 } warning_table[] = {
8031 { "char", WARN_CHAR },
8032 { "range", WARN_RANGE },
8033 { "break", WARN_BREAK },
8034 { "delim", WARN_DELIM },
8035 { "el", WARN_EL },
8036 { "scale", WARN_SCALE },
8037 { "number", WARN_NUMBER },
8038 { "syntax", WARN_SYNTAX },
8039 { "tab", WARN_TAB },
8040 { "right-brace", WARN_RIGHT_BRACE },
8041 { "missing", WARN_MISSING },
8042 { "input", WARN_INPUT },
8043 { "escape", WARN_ESCAPE },
8044 { "space", WARN_SPACE },
8045 { "font", WARN_FONT },
8046 { "di", WARN_DI },
8047 { "mac", WARN_MAC },
8048 { "reg", WARN_REG },
8049 { "ig", WARN_IG },
8050 { "color", WARN_COLOR },
8051 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
8052 { "w", WARN_TOTAL },
8053 { "default", DEFAULT_WARNING_MASK },
8056 static int lookup_warning(const char *name)
8058 for (unsigned int i = 0;
8059 i < sizeof(warning_table)/sizeof(warning_table[0]);
8060 i++)
8061 if (strcmp(name, warning_table[i].name) == 0)
8062 return warning_table[i].mask;
8063 return 0;
8066 static void enable_warning(const char *name)
8068 int mask = lookup_warning(name);
8069 if (mask)
8070 warning_mask |= mask;
8071 else
8072 error("unknown warning `%1'", name);
8075 static void disable_warning(const char *name)
8077 int mask = lookup_warning(name);
8078 if (mask)
8079 warning_mask &= ~mask;
8080 else
8081 error("unknown warning `%1'", name);
8084 static void copy_mode_error(const char *format,
8085 const errarg &arg1,
8086 const errarg &arg2,
8087 const errarg &arg3)
8089 if (ignoring) {
8090 static const char prefix[] = "(in ignored input) ";
8091 char *s = new char[sizeof(prefix) + strlen(format)];
8092 strcpy(s, prefix);
8093 strcat(s, format);
8094 warning(WARN_IG, s, arg1, arg2, arg3);
8095 a_delete s;
8097 else
8098 error(format, arg1, arg2, arg3);
8101 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
8103 static void do_error(error_type type,
8104 const char *format,
8105 const errarg &arg1,
8106 const errarg &arg2,
8107 const errarg &arg3)
8109 const char *filename;
8110 int lineno;
8111 if (inhibit_errors && type < FATAL)
8112 return;
8113 if (backtrace_flag)
8114 input_stack::backtrace();
8115 if (!get_file_line(&filename, &lineno))
8116 filename = 0;
8117 if (filename)
8118 errprint("%1:%2: ", filename, lineno);
8119 else if (program_name)
8120 fprintf(stderr, "%s: ", program_name);
8121 switch (type) {
8122 case FATAL:
8123 fputs("fatal error: ", stderr);
8124 break;
8125 case ERROR:
8126 break;
8127 case WARNING:
8128 fputs("warning: ", stderr);
8129 break;
8130 case OUTPUT_WARNING:
8131 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
8132 fprintf(stderr, "warning [p %d, %.1f%c",
8133 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
8134 if (topdiv != curdiv) {
8135 double fromtop1 = curdiv->get_vertical_position().to_units()
8136 / warn_scale;
8137 fprintf(stderr, ", div `%s', %.1f%c",
8138 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
8140 fprintf(stderr, "]: ");
8141 break;
8143 errprint(format, arg1, arg2, arg3);
8144 fputc('\n', stderr);
8145 fflush(stderr);
8146 if (type == FATAL)
8147 cleanup_and_exit(1);
8150 int warning(warning_type t,
8151 const char *format,
8152 const errarg &arg1,
8153 const errarg &arg2,
8154 const errarg &arg3)
8156 if ((t & warning_mask) != 0) {
8157 do_error(WARNING, format, arg1, arg2, arg3);
8158 return 1;
8160 else
8161 return 0;
8164 int output_warning(warning_type t,
8165 const char *format,
8166 const errarg &arg1,
8167 const errarg &arg2,
8168 const errarg &arg3)
8170 if ((t & warning_mask) != 0) {
8171 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
8172 return 1;
8174 else
8175 return 0;
8178 void error(const char *format,
8179 const errarg &arg1,
8180 const errarg &arg2,
8181 const errarg &arg3)
8183 do_error(ERROR, format, arg1, arg2, arg3);
8186 void fatal(const char *format,
8187 const errarg &arg1,
8188 const errarg &arg2,
8189 const errarg &arg3)
8191 do_error(FATAL, format, arg1, arg2, arg3);
8194 void fatal_with_file_and_line(const char *filename, int lineno,
8195 const char *format,
8196 const errarg &arg1,
8197 const errarg &arg2,
8198 const errarg &arg3)
8200 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
8201 errprint(format, arg1, arg2, arg3);
8202 fputc('\n', stderr);
8203 fflush(stderr);
8204 cleanup_and_exit(1);
8207 void error_with_file_and_line(const char *filename, int lineno,
8208 const char *format,
8209 const errarg &arg1,
8210 const errarg &arg2,
8211 const errarg &arg3)
8213 fprintf(stderr, "%s:%d: error: ", filename, lineno);
8214 errprint(format, arg1, arg2, arg3);
8215 fputc('\n', stderr);
8216 fflush(stderr);
8219 dictionary charinfo_dictionary(501);
8221 charinfo *get_charinfo(symbol nm)
8223 void *p = charinfo_dictionary.lookup(nm);
8224 if (p != 0)
8225 return (charinfo *)p;
8226 charinfo *cp = new charinfo(nm);
8227 (void)charinfo_dictionary.lookup(nm, cp);
8228 return cp;
8231 int charinfo::next_index = 0;
8233 charinfo::charinfo(symbol s)
8234 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
8235 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
8236 not_found(0), transparent_translate(1), translate_input(0),
8237 mode(CHAR_NORMAL), nm(s)
8239 index = next_index++;
8240 number = -1;
8243 void charinfo::set_hyphenation_code(unsigned char c)
8245 hyphenation_code = c;
8248 void charinfo::set_translation(charinfo *ci, int tt, int ti)
8250 translation = ci;
8251 if (ci && ti) {
8252 if (hyphenation_code != 0)
8253 ci->set_hyphenation_code(hyphenation_code);
8254 if (asciify_code != 0)
8255 ci->set_asciify_code(asciify_code);
8256 else if (ascii_code != 0)
8257 ci->set_asciify_code(ascii_code);
8258 ci->set_translation_input();
8260 special_translation = TRANSLATE_NONE;
8261 transparent_translate = tt;
8264 void charinfo::set_special_translation(int c, int tt)
8266 special_translation = c;
8267 translation = 0;
8268 transparent_translate = tt;
8271 void charinfo::set_ascii_code(unsigned char c)
8273 ascii_code = c;
8276 void charinfo::set_asciify_code(unsigned char c)
8278 asciify_code = c;
8281 macro *charinfo::set_macro(macro *m)
8283 macro *tem = mac;
8284 mac = m;
8285 return tem;
8288 macro *charinfo::setx_macro(macro *m, char_mode cm)
8290 macro *tem = mac;
8291 mac = m;
8292 mode = cm;
8293 return tem;
8296 void charinfo::set_number(int n)
8298 assert(n >= 0);
8299 number = n;
8302 int charinfo::get_number()
8304 assert(number >= 0);
8305 return number;
8308 symbol UNNAMED_SYMBOL("---");
8310 // For numbered characters not between 0 and 255, we make a symbol out
8311 // of the number and store them in this dictionary.
8313 dictionary numbered_charinfo_dictionary(11);
8315 charinfo *get_charinfo_by_number(int n)
8317 static charinfo *number_table[256];
8319 if (n >= 0 && n < 256) {
8320 charinfo *ci = number_table[n];
8321 if (!ci) {
8322 ci = new charinfo(UNNAMED_SYMBOL);
8323 ci->set_number(n);
8324 number_table[n] = ci;
8326 return ci;
8328 else {
8329 symbol ns(i_to_a(n));
8330 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
8331 if (!ci) {
8332 ci = new charinfo(UNNAMED_SYMBOL);
8333 ci->set_number(n);
8334 (void)numbered_charinfo_dictionary.lookup(ns, ci);
8336 return ci;
8340 // This overrides the same function from libgroff; while reading font
8341 // definition files it puts single-letter glyph names into `charset_table'
8342 // and converts glyph names of the form `\x' (`x' a single letter) into `x'.
8343 // Consequently, symbol("x") refers to glyph name `\x', not `x'.
8345 glyph *name_to_glyph(const char *nm)
8347 charinfo *ci;
8348 if (nm[1] == 0)
8349 ci = charset_table[nm[0] & 0xff];
8350 else if (nm[0] == '\\' && nm[2] == 0)
8351 ci = get_charinfo(symbol(nm + 1));
8352 else
8353 ci = get_charinfo(symbol(nm));
8354 return ci->as_glyph();
8357 glyph *number_to_glyph(int n)
8359 return get_charinfo_by_number(n)->as_glyph();
8362 const char *glyph_to_name(glyph *g)
8364 charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
8365 return (ci->nm != UNNAMED_SYMBOL ? ci->nm.contents() : NULL);