* src/roff/troff/input.cc (charinfo_to_node_list): Reset
[s-roff.git] / src / roff / troff / input.cc
blob4d5b30d86e582d248154fdffc239d2254be99e80
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 for more details.
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING. If not, write to the Free Software
20 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
22 #include "troff.h"
23 #include "symbol.h"
24 #include "dictionary.h"
25 #include "hvunits.h"
26 #include "env.h"
27 #include "request.h"
28 #include "node.h"
29 #include "reg.h"
30 #include "token.h"
31 #include "div.h"
32 #include "charinfo.h"
33 #include "stringclass.h"
34 #include "font.h"
35 #include "macropath.h"
36 #include "defs.h"
37 #include "input.h"
38 #include "unicode.h"
40 // Needed for getpid() and isatty()
41 #include "posix.h"
43 #include "nonposix.h"
45 #ifdef NEED_DECLARATION_PUTENV
46 extern "C" {
47 int putenv(const char *);
49 #endif /* NEED_DECLARATION_PUTENV */
51 #define MACRO_PREFIX "tmac."
52 #define MACRO_POSTFIX ".tmac"
53 #define INITIAL_STARTUP_FILE "troffrc"
54 #define FINAL_STARTUP_FILE "troffrc-end"
55 #define DEFAULT_INPUT_STACK_LIMIT 1000
57 #ifndef DEFAULT_WARNING_MASK
58 // warnings that are enabled by default
59 #define DEFAULT_WARNING_MASK \
60 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
61 #endif
63 // initial size of buffer for reading names; expanded as necessary
64 #define ABUF_SIZE 16
66 extern "C" const char *Version_string;
68 #ifdef COLUMN
69 void init_column_requests();
70 #endif /* COLUMN */
72 static node *read_draw_node();
73 static void read_color_draw_node(token &);
74 void handle_first_page_transition();
75 static void push_token(const token &);
76 void copy_file();
77 #ifdef COLUMN
78 void vjustify();
79 #endif /* COLUMN */
80 void transparent_file();
81 void process_input_stack();
83 const char *program_name = 0;
84 token tok;
85 int break_flag = 0;
86 int color_flag = 1; // colors are on by default
87 static int backtrace_flag = 0;
88 #ifndef POPEN_MISSING
89 char *pipe_command = 0;
90 #endif
91 charinfo *charset_table[256];
92 unsigned char hpf_code_table[256];
94 static int warning_mask = DEFAULT_WARNING_MASK;
95 static int inhibit_errors = 0;
96 static int ignoring = 0;
98 static void enable_warning(const char *);
99 static void disable_warning(const char *);
101 static int escape_char = '\\';
102 static symbol end_macro_name;
103 static symbol blank_line_macro_name;
104 static int compatible_flag = 0;
105 int ascii_output_flag = 0;
106 int suppress_output_flag = 0;
107 int is_html = 0;
108 int begin_level = 0; // number of nested .begin requests
110 int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
111 // \R, \s, or \S has been processed in
112 // token::next()
113 int tcommand_flag = 0;
114 int safer_flag = 1; // safer by default
116 int have_string_arg = 0; // whether we have \*[foo bar...]
118 double spread_limit = -3.0 - 1.0; // negative means deactivated
120 double warn_scale;
121 char warn_scaling_indicator;
123 search_path *mac_path = &safer_macro_path;
125 static int get_copy(node**, int = 0);
126 static void copy_mode_error(const char *,
127 const errarg & = empty_errarg,
128 const errarg & = empty_errarg,
129 const errarg & = empty_errarg);
131 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
132 static symbol read_escape_name(read_mode mode = NO_ARGS);
133 static symbol read_long_escape_name(read_mode mode = NO_ARGS);
134 static void interpolate_string(symbol);
135 static void interpolate_string_with_args(symbol);
136 static void interpolate_macro(symbol);
137 static void interpolate_number_format(symbol);
138 static void interpolate_environment_variable(symbol);
140 static symbol composite_glyph_name(symbol);
141 static void interpolate_arg(symbol);
142 static request_or_macro *lookup_request(symbol);
143 static int get_delim_number(units *, int);
144 static int get_delim_number(units *, int, units);
145 static symbol do_get_long_name(int, char);
146 static int get_line_arg(units *res, int si, charinfo **cp);
147 static int read_size(int *);
148 static symbol get_delim_name();
149 static void init_registers();
150 static void trapping_blank_line();
152 struct input_iterator;
153 input_iterator *make_temp_iterator(const char *);
154 const char *input_char_description(int);
157 void set_escape_char()
159 if (has_arg()) {
160 if (tok.ch() == 0) {
161 error("bad escape character");
162 escape_char = '\\';
164 else
165 escape_char = tok.ch();
167 else
168 escape_char = '\\';
169 skip_line();
172 void escape_off()
174 escape_char = 0;
175 skip_line();
178 static int saved_escape_char = '\\';
180 void save_escape_char()
182 saved_escape_char = escape_char;
183 skip_line();
186 void restore_escape_char()
188 escape_char = saved_escape_char;
189 skip_line();
192 class input_iterator {
193 public:
194 input_iterator();
195 virtual ~input_iterator() {}
196 int get(node **);
197 friend class input_stack;
198 protected:
199 const unsigned char *ptr;
200 const unsigned char *eptr;
201 input_iterator *next;
202 private:
203 virtual int fill(node **);
204 virtual int peek();
205 virtual int has_args() { return 0; }
206 virtual int nargs() { return 0; }
207 virtual input_iterator *get_arg(int) { return 0; }
208 virtual int get_location(int, const char **, int *) { return 0; }
209 virtual void backtrace() {}
210 virtual int set_location(const char *, int) { return 0; }
211 virtual int next_file(FILE *, const char *) { return 0; }
212 virtual void shift(int) {}
213 virtual int is_boundary() {return 0; }
214 virtual int internal_level() { return 0; }
215 virtual int is_file() { return 0; }
216 virtual int is_macro() { return 0; }
217 virtual void save_compatible_flag(int) {}
218 virtual int get_compatible_flag() { return 0; }
221 input_iterator::input_iterator()
222 : ptr(0), eptr(0)
226 int input_iterator::fill(node **)
228 return EOF;
231 int input_iterator::peek()
233 return EOF;
236 inline int input_iterator::get(node **p)
238 return ptr < eptr ? *ptr++ : fill(p);
241 class input_boundary : public input_iterator {
242 public:
243 int is_boundary() { return 1; }
246 class input_return_boundary : public input_iterator {
247 public:
248 int is_boundary() { return 2; }
251 class file_iterator : public input_iterator {
252 FILE *fp;
253 int lineno;
254 const char *filename;
255 int popened;
256 int newline_flag;
257 int seen_escape;
258 enum { BUF_SIZE = 512 };
259 unsigned char buf[BUF_SIZE];
260 void close();
261 public:
262 file_iterator(FILE *, const char *, int = 0);
263 ~file_iterator();
264 int fill(node **);
265 int peek();
266 int get_location(int, const char **, int *);
267 void backtrace();
268 int set_location(const char *, int);
269 int next_file(FILE *, const char *);
270 int is_file();
273 file_iterator::file_iterator(FILE *f, const char *fn, int po)
274 : fp(f), lineno(1), filename(fn), popened(po),
275 newline_flag(0), seen_escape(0)
277 if ((font::use_charnames_in_special) && (fn != 0)) {
278 if (!the_output)
279 init_output();
280 the_output->put_filename(fn);
284 file_iterator::~file_iterator()
286 close();
289 void file_iterator::close()
291 if (fp == stdin)
292 clearerr(stdin);
293 #ifndef POPEN_MISSING
294 else if (popened)
295 pclose(fp);
296 #endif /* not POPEN_MISSING */
297 else
298 fclose(fp);
301 int file_iterator::is_file()
303 return 1;
306 int file_iterator::next_file(FILE *f, const char *s)
308 close();
309 filename = s;
310 fp = f;
311 lineno = 1;
312 newline_flag = 0;
313 seen_escape = 0;
314 popened = 0;
315 ptr = 0;
316 eptr = 0;
317 return 1;
320 int file_iterator::fill(node **)
322 if (newline_flag)
323 lineno++;
324 newline_flag = 0;
325 unsigned char *p = buf;
326 ptr = p;
327 unsigned char *e = p + BUF_SIZE;
328 while (p < e) {
329 int c = getc(fp);
330 if (c == EOF)
331 break;
332 if (invalid_input_char(c))
333 warning(WARN_INPUT, "invalid input character code %1", int(c));
334 else {
335 *p++ = c;
336 if (c == '\n') {
337 seen_escape = 0;
338 newline_flag = 1;
339 break;
341 seen_escape = (c == '\\');
344 if (p > buf) {
345 eptr = p;
346 return *ptr++;
348 else {
349 eptr = p;
350 return EOF;
354 int file_iterator::peek()
356 int c = getc(fp);
357 while (invalid_input_char(c)) {
358 warning(WARN_INPUT, "invalid input character code %1", int(c));
359 c = getc(fp);
361 if (c != EOF)
362 ungetc(c, fp);
363 return c;
366 int file_iterator::get_location(int /*allow_macro*/,
367 const char **filenamep, int *linenop)
369 *linenop = lineno;
370 if (filename != 0 && strcmp(filename, "-") == 0)
371 *filenamep = "<standard input>";
372 else
373 *filenamep = filename;
374 return 1;
377 void file_iterator::backtrace()
379 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
380 popened ? "process" : "file");
383 int file_iterator::set_location(const char *f, int ln)
385 if (f) {
386 filename = f;
387 if (!the_output)
388 init_output();
389 the_output->put_filename(f);
391 lineno = ln;
392 return 1;
395 input_iterator nil_iterator;
397 class input_stack {
398 public:
399 static int get(node **);
400 static int peek();
401 static void push(input_iterator *);
402 static input_iterator *get_arg(int);
403 static int nargs();
404 static int get_location(int, const char **, int *);
405 static int set_location(const char *, int);
406 static void backtrace();
407 static void backtrace_all();
408 static void next_file(FILE *, const char *);
409 static void end_file();
410 static void shift(int n);
411 static void add_boundary();
412 static void add_return_boundary();
413 static int is_return_boundary();
414 static void remove_boundary();
415 static int get_level();
416 static void clear();
417 static void pop_macro();
418 static void save_compatible_flag(int);
419 static int get_compatible_flag();
421 static int limit;
422 private:
423 static input_iterator *top;
424 static int level;
426 static int finish_get(node **);
427 static int finish_peek();
430 input_iterator *input_stack::top = &nil_iterator;
431 int input_stack::level = 0;
432 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
434 inline int input_stack::get_level()
436 return level + top->internal_level();
439 inline int input_stack::get(node **np)
441 int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
442 if (res == '\n')
443 have_input = 0;
444 return res;
447 int input_stack::finish_get(node **np)
449 for (;;) {
450 int c = top->fill(np);
451 if (c != EOF || top->is_boundary())
452 return c;
453 if (top == &nil_iterator)
454 break;
455 input_iterator *tem = top;
456 top = top->next;
457 level--;
458 delete tem;
459 if (top->ptr < top->eptr)
460 return *top->ptr++;
462 assert(level == 0);
463 return EOF;
466 inline int input_stack::peek()
468 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
471 int input_stack::finish_peek()
473 for (;;) {
474 int c = top->peek();
475 if (c != EOF || top->is_boundary())
476 return c;
477 if (top == &nil_iterator)
478 break;
479 input_iterator *tem = top;
480 top = top->next;
481 level--;
482 delete tem;
483 if (top->ptr < top->eptr)
484 return *top->ptr;
486 assert(level == 0);
487 return EOF;
490 void input_stack::add_boundary()
492 push(new input_boundary);
495 void input_stack::add_return_boundary()
497 push(new input_return_boundary);
500 int input_stack::is_return_boundary()
502 return top->is_boundary() == 2;
505 void input_stack::remove_boundary()
507 assert(top->is_boundary());
508 input_iterator *temp = top->next;
509 delete top;
510 top = temp;
511 level--;
514 void input_stack::push(input_iterator *in)
516 if (in == 0)
517 return;
518 if (++level > limit && limit > 0)
519 fatal("input stack limit exceeded (probable infinite loop)");
520 in->next = top;
521 top = in;
524 input_iterator *input_stack::get_arg(int i)
526 input_iterator *p;
527 for (p = top; p != 0; p = p->next)
528 if (p->has_args())
529 return p->get_arg(i);
530 return 0;
533 void input_stack::shift(int n)
535 for (input_iterator *p = top; p; p = p->next)
536 if (p->has_args()) {
537 p->shift(n);
538 return;
542 int input_stack::nargs()
544 for (input_iterator *p =top; p != 0; p = p->next)
545 if (p->has_args())
546 return p->nargs();
547 return 0;
550 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
552 for (input_iterator *p = top; p; p = p->next)
553 if (p->get_location(allow_macro, filenamep, linenop))
554 return 1;
555 return 0;
558 void input_stack::backtrace()
560 const char *f;
561 int n;
562 // only backtrace down to (not including) the topmost file
563 for (input_iterator *p = top;
564 p && !p->get_location(0, &f, &n);
565 p = p->next)
566 p->backtrace();
569 void input_stack::backtrace_all()
571 for (input_iterator *p = top; p; p = p->next)
572 p->backtrace();
575 int input_stack::set_location(const char *filename, int lineno)
577 for (input_iterator *p = top; p; p = p->next)
578 if (p->set_location(filename, lineno))
579 return 1;
580 return 0;
583 void input_stack::next_file(FILE *fp, const char *s)
585 input_iterator **pp;
586 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
587 if ((*pp)->next_file(fp, s))
588 return;
589 if (++level > limit && limit > 0)
590 fatal("input stack limit exceeded");
591 *pp = new file_iterator(fp, s);
592 (*pp)->next = &nil_iterator;
595 void input_stack::end_file()
597 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
598 if ((*pp)->is_file()) {
599 input_iterator *tem = *pp;
600 *pp = (*pp)->next;
601 delete tem;
602 level--;
603 return;
607 void input_stack::clear()
609 int nboundaries = 0;
610 while (top != &nil_iterator) {
611 if (top->is_boundary())
612 nboundaries++;
613 input_iterator *tem = top;
614 top = top->next;
615 level--;
616 delete tem;
618 // Keep while_request happy.
619 for (; nboundaries > 0; --nboundaries)
620 add_return_boundary();
623 void input_stack::pop_macro()
625 int nboundaries = 0;
626 int is_macro = 0;
627 do {
628 if (top->next == &nil_iterator)
629 break;
630 if (top->is_boundary())
631 nboundaries++;
632 is_macro = top->is_macro();
633 input_iterator *tem = top;
634 top = top->next;
635 level--;
636 delete tem;
637 } while (!is_macro);
638 // Keep while_request happy.
639 for (; nboundaries > 0; --nboundaries)
640 add_return_boundary();
643 inline void input_stack::save_compatible_flag(int f)
645 top->save_compatible_flag(f);
648 inline int input_stack::get_compatible_flag()
650 return top->get_compatible_flag();
653 void backtrace_request()
655 input_stack::backtrace_all();
656 fflush(stderr);
657 skip_line();
660 void next_file()
662 symbol nm = get_long_name();
663 while (!tok.newline() && !tok.eof())
664 tok.next();
665 if (nm.is_null())
666 input_stack::end_file();
667 else {
668 errno = 0;
669 FILE *fp = fopen(nm.contents(), "r");
670 if (!fp)
671 error("can't open `%1': %2", nm.contents(), strerror(errno));
672 else
673 input_stack::next_file(fp, nm.contents());
675 tok.next();
678 void shift()
680 int n;
681 if (!has_arg() || !get_integer(&n))
682 n = 1;
683 input_stack::shift(n);
684 skip_line();
687 static int get_char_for_escape_name(int allow_space = 0)
689 int c = get_copy(0);
690 switch (c) {
691 case EOF:
692 copy_mode_error("end of input in escape name");
693 return '\0';
694 default:
695 if (!invalid_input_char(c))
696 break;
697 // fall through
698 case '\n':
699 if (c == '\n')
700 input_stack::push(make_temp_iterator("\n"));
701 // fall through
702 case ' ':
703 if (c == ' ' && allow_space)
704 break;
705 // fall through
706 case '\t':
707 case '\001':
708 case '\b':
709 copy_mode_error("%1 is not allowed in an escape name",
710 input_char_description(c));
711 return '\0';
713 return c;
716 static symbol read_two_char_escape_name()
718 char buf[3];
719 buf[0] = get_char_for_escape_name();
720 if (buf[0] != '\0') {
721 buf[1] = get_char_for_escape_name();
722 if (buf[1] == '\0')
723 buf[0] = 0;
724 else
725 buf[2] = 0;
727 return symbol(buf);
730 static symbol read_long_escape_name(read_mode mode)
732 int start_level = input_stack::get_level();
733 char abuf[ABUF_SIZE];
734 char *buf = abuf;
735 int buf_size = ABUF_SIZE;
736 int i = 0;
737 int c;
738 int have_char = 0;
739 for (;;) {
740 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
741 if (c == 0) {
742 if (buf != abuf)
743 a_delete buf;
744 return NULL_SYMBOL;
746 have_char = 1;
747 if (mode == WITH_ARGS && c == ' ')
748 break;
749 if (i + 2 > buf_size) {
750 if (buf == abuf) {
751 buf = new char[ABUF_SIZE*2];
752 memcpy(buf, abuf, buf_size);
753 buf_size = ABUF_SIZE*2;
755 else {
756 char *old_buf = buf;
757 buf = new char[buf_size*2];
758 memcpy(buf, old_buf, buf_size);
759 buf_size *= 2;
760 a_delete old_buf;
763 if (c == ']' && input_stack::get_level() == start_level)
764 break;
765 buf[i++] = c;
767 buf[i] = 0;
768 if (c == ' ')
769 have_string_arg = 1;
770 if (buf == abuf) {
771 if (i == 0) {
772 if (mode != ALLOW_EMPTY)
773 copy_mode_error("empty escape name");
774 return EMPTY_SYMBOL;
776 return symbol(abuf);
778 else {
779 symbol s(buf);
780 a_delete buf;
781 return s;
785 static symbol read_escape_name(read_mode mode)
787 int c = get_char_for_escape_name();
788 if (c == 0)
789 return NULL_SYMBOL;
790 if (c == '(')
791 return read_two_char_escape_name();
792 if (c == '[' && !compatible_flag)
793 return read_long_escape_name(mode);
794 char buf[2];
795 buf[0] = c;
796 buf[1] = '\0';
797 return symbol(buf);
800 static symbol read_increment_and_escape_name(int *incp)
802 int c = get_char_for_escape_name();
803 switch (c) {
804 case 0:
805 *incp = 0;
806 return NULL_SYMBOL;
807 case '(':
808 *incp = 0;
809 return read_two_char_escape_name();
810 case '+':
811 *incp = 1;
812 return read_escape_name();
813 case '-':
814 *incp = -1;
815 return read_escape_name();
816 case '[':
817 if (!compatible_flag) {
818 *incp = 0;
819 return read_long_escape_name();
821 break;
823 *incp = 0;
824 char buf[2];
825 buf[0] = c;
826 buf[1] = '\0';
827 return symbol(buf);
830 static int get_copy(node **nd, int defining)
832 for (;;) {
833 int c = input_stack::get(nd);
834 if (c == ESCAPE_NEWLINE) {
835 if (defining)
836 return c;
837 do {
838 c = input_stack::get(nd);
839 } while (c == ESCAPE_NEWLINE);
841 if (c != escape_char || escape_char <= 0)
842 return c;
843 c = input_stack::peek();
844 switch(c) {
845 case 0:
846 return escape_char;
847 case '"':
848 (void)input_stack::get(0);
849 while ((c = input_stack::get(0)) != '\n' && c != EOF)
851 return c;
852 case '#': // Like \" but newline is ignored.
853 (void)input_stack::get(0);
854 while ((c = input_stack::get(0)) != '\n')
855 if (c == EOF)
856 return EOF;
857 break;
858 case '$':
860 (void)input_stack::get(0);
861 symbol s = read_escape_name();
862 if (!(s.is_null() || s.is_empty()))
863 interpolate_arg(s);
864 break;
866 case '*':
868 (void)input_stack::get(0);
869 symbol s = read_escape_name(WITH_ARGS);
870 if (!(s.is_null() || s.is_empty())) {
871 if (have_string_arg) {
872 have_string_arg = 0;
873 interpolate_string_with_args(s);
875 else
876 interpolate_string(s);
878 break;
880 case 'a':
881 (void)input_stack::get(0);
882 return '\001';
883 case 'e':
884 (void)input_stack::get(0);
885 return ESCAPE_e;
886 case 'E':
887 (void)input_stack::get(0);
888 return ESCAPE_E;
889 case 'n':
891 (void)input_stack::get(0);
892 int inc;
893 symbol s = read_increment_and_escape_name(&inc);
894 if (!(s.is_null() || s.is_empty()))
895 interpolate_number_reg(s, inc);
896 break;
898 case 'g':
900 (void)input_stack::get(0);
901 symbol s = read_escape_name();
902 if (!(s.is_null() || s.is_empty()))
903 interpolate_number_format(s);
904 break;
906 case 't':
907 (void)input_stack::get(0);
908 return '\t';
909 case 'V':
911 (void)input_stack::get(0);
912 symbol s = read_escape_name();
913 if (!(s.is_null() || s.is_empty()))
914 interpolate_environment_variable(s);
915 break;
917 case '\n':
918 (void)input_stack::get(0);
919 if (defining)
920 return ESCAPE_NEWLINE;
921 break;
922 case ' ':
923 (void)input_stack::get(0);
924 return ESCAPE_SPACE;
925 case '~':
926 (void)input_stack::get(0);
927 return ESCAPE_TILDE;
928 case ':':
929 (void)input_stack::get(0);
930 return ESCAPE_COLON;
931 case '|':
932 (void)input_stack::get(0);
933 return ESCAPE_BAR;
934 case '^':
935 (void)input_stack::get(0);
936 return ESCAPE_CIRCUMFLEX;
937 case '{':
938 (void)input_stack::get(0);
939 return ESCAPE_LEFT_BRACE;
940 case '}':
941 (void)input_stack::get(0);
942 return ESCAPE_RIGHT_BRACE;
943 case '`':
944 (void)input_stack::get(0);
945 return ESCAPE_LEFT_QUOTE;
946 case '\'':
947 (void)input_stack::get(0);
948 return ESCAPE_RIGHT_QUOTE;
949 case '-':
950 (void)input_stack::get(0);
951 return ESCAPE_HYPHEN;
952 case '_':
953 (void)input_stack::get(0);
954 return ESCAPE_UNDERSCORE;
955 case 'c':
956 (void)input_stack::get(0);
957 return ESCAPE_c;
958 case '!':
959 (void)input_stack::get(0);
960 return ESCAPE_BANG;
961 case '?':
962 (void)input_stack::get(0);
963 return ESCAPE_QUESTION;
964 case '&':
965 (void)input_stack::get(0);
966 return ESCAPE_AMPERSAND;
967 case ')':
968 (void)input_stack::get(0);
969 return ESCAPE_RIGHT_PARENTHESIS;
970 case '.':
971 (void)input_stack::get(0);
972 return c;
973 case '%':
974 (void)input_stack::get(0);
975 return ESCAPE_PERCENT;
976 default:
977 if (c == escape_char) {
978 (void)input_stack::get(0);
979 return c;
981 else
982 return escape_char;
987 class non_interpreted_char_node : public node {
988 unsigned char c;
989 public:
990 non_interpreted_char_node(unsigned char);
991 node *copy();
992 int interpret(macro *);
993 int same(node *);
994 const char *type();
995 int force_tprint();
998 int non_interpreted_char_node::same(node *nd)
1000 return c == ((non_interpreted_char_node *)nd)->c;
1003 const char *non_interpreted_char_node::type()
1005 return "non_interpreted_char_node";
1008 int non_interpreted_char_node::force_tprint()
1010 return 0;
1013 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1015 assert(n != 0);
1018 node *non_interpreted_char_node::copy()
1020 return new non_interpreted_char_node(c);
1023 int non_interpreted_char_node::interpret(macro *mac)
1025 mac->append(c);
1026 return 1;
1029 static void do_width();
1030 static node *do_non_interpreted();
1031 static node *do_special();
1032 static node *do_suppress(symbol nm);
1033 static void do_register();
1035 dictionary color_dictionary(501);
1036 static symbol default_symbol("default");
1038 static color *lookup_color(symbol nm)
1040 assert(!nm.is_null());
1041 if (nm == default_symbol)
1042 return &default_color;
1043 color *c = (color *)color_dictionary.lookup(nm);
1044 if (c == 0)
1045 warning(WARN_COLOR, "color `%1' not defined", nm.contents());
1046 return c;
1049 void do_glyph_color(symbol nm)
1051 if (nm.is_null())
1052 return;
1053 if (nm.is_empty())
1054 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1055 else {
1056 color *tem = lookup_color(nm);
1057 if (tem)
1058 curenv->set_glyph_color(tem);
1059 else
1060 (void)color_dictionary.lookup(nm, new color);
1064 void do_fill_color(symbol nm)
1066 if (nm.is_null())
1067 return;
1068 if (nm.is_empty())
1069 curenv->set_fill_color(curenv->get_prev_fill_color());
1070 else {
1071 color *tem = lookup_color(nm);
1072 if (tem)
1073 curenv->set_fill_color(tem);
1074 else
1075 (void)color_dictionary.lookup(nm, new color);
1079 static unsigned int get_color_element(const char *scheme, const char *col)
1081 units val;
1082 if (!get_number(&val, 'f')) {
1083 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1084 tok.next();
1085 return 0;
1087 if (val < 0) {
1088 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1089 return 0;
1091 if (val > color::MAX_COLOR_VAL+1) {
1092 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1093 // we change 0x10000 to 0xffff
1094 return color::MAX_COLOR_VAL;
1096 return (unsigned int)val;
1099 static color *read_rgb(char end = 0)
1101 symbol component = do_get_long_name(0, end);
1102 if (component.is_null()) {
1103 warning(WARN_COLOR, "missing rgb color values");
1104 return 0;
1106 const char *s = component.contents();
1107 color *col = new color;
1108 if (*s == '#') {
1109 if (!col->read_rgb(s)) {
1110 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1111 delete col;
1112 return 0;
1115 else {
1116 if (!end)
1117 input_stack::push(make_temp_iterator(" "));
1118 input_stack::push(make_temp_iterator(s));
1119 tok.next();
1120 unsigned int r = get_color_element("rgb color", "red component");
1121 unsigned int g = get_color_element("rgb color", "green component");
1122 unsigned int b = get_color_element("rgb color", "blue component");
1123 col->set_rgb(r, g, b);
1125 return col;
1128 static color *read_cmy(char end = 0)
1130 symbol component = do_get_long_name(0, end);
1131 if (component.is_null()) {
1132 warning(WARN_COLOR, "missing cmy color values");
1133 return 0;
1135 const char *s = component.contents();
1136 color *col = new color;
1137 if (*s == '#') {
1138 if (!col->read_cmy(s)) {
1139 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1140 delete col;
1141 return 0;
1144 else {
1145 if (!end)
1146 input_stack::push(make_temp_iterator(" "));
1147 input_stack::push(make_temp_iterator(s));
1148 tok.next();
1149 unsigned int c = get_color_element("cmy color", "cyan component");
1150 unsigned int m = get_color_element("cmy color", "magenta component");
1151 unsigned int y = get_color_element("cmy color", "yellow component");
1152 col->set_cmy(c, m, y);
1154 return col;
1157 static color *read_cmyk(char end = 0)
1159 symbol component = do_get_long_name(0, end);
1160 if (component.is_null()) {
1161 warning(WARN_COLOR, "missing cmyk color values");
1162 return 0;
1164 const char *s = component.contents();
1165 color *col = new color;
1166 if (*s == '#') {
1167 if (!col->read_cmyk(s)) {
1168 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1169 delete col;
1170 return 0;
1173 else {
1174 if (!end)
1175 input_stack::push(make_temp_iterator(" "));
1176 input_stack::push(make_temp_iterator(s));
1177 tok.next();
1178 unsigned int c = get_color_element("cmyk color", "cyan component");
1179 unsigned int m = get_color_element("cmyk color", "magenta component");
1180 unsigned int y = get_color_element("cmyk color", "yellow component");
1181 unsigned int k = get_color_element("cmyk color", "black component");
1182 col->set_cmyk(c, m, y, k);
1184 return col;
1187 static color *read_gray(char end = 0)
1189 symbol component = do_get_long_name(0, end);
1190 if (component.is_null()) {
1191 warning(WARN_COLOR, "missing gray values");
1192 return 0;
1194 const char *s = component.contents();
1195 color *col = new color;
1196 if (*s == '#') {
1197 if (!col->read_gray(s)) {
1198 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1199 delete col;
1200 return 0;
1203 else {
1204 if (!end)
1205 input_stack::push(make_temp_iterator("\n"));
1206 input_stack::push(make_temp_iterator(s));
1207 tok.next();
1208 unsigned int g = get_color_element("gray", "gray value");
1209 col->set_gray(g);
1211 return col;
1214 static void activate_color()
1216 int n;
1217 if (has_arg() && get_integer(&n))
1218 color_flag = n != 0;
1219 else
1220 color_flag = 1;
1221 skip_line();
1224 static void define_color()
1226 symbol color_name = get_long_name(1);
1227 if (color_name.is_null()) {
1228 skip_line();
1229 return;
1231 if (color_name == default_symbol) {
1232 warning(WARN_COLOR, "default color can't be redefined");
1233 skip_line();
1234 return;
1236 symbol style = get_long_name(1);
1237 if (style.is_null()) {
1238 skip_line();
1239 return;
1241 color *col;
1242 if (strcmp(style.contents(), "rgb") == 0)
1243 col = read_rgb();
1244 else if (strcmp(style.contents(), "cmyk") == 0)
1245 col = read_cmyk();
1246 else if (strcmp(style.contents(), "gray") == 0)
1247 col = read_gray();
1248 else if (strcmp(style.contents(), "grey") == 0)
1249 col = read_gray();
1250 else if (strcmp(style.contents(), "cmy") == 0)
1251 col = read_cmy();
1252 else {
1253 warning(WARN_COLOR,
1254 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1255 style.contents());
1256 skip_line();
1257 return;
1259 if (col)
1260 (void)color_dictionary.lookup(color_name, col);
1261 skip_line();
1264 static node *do_overstrike()
1266 token start;
1267 overstrike_node *on = new overstrike_node;
1268 int start_level = input_stack::get_level();
1269 start.next();
1270 for (;;) {
1271 tok.next();
1272 if (tok.newline() || tok.eof()) {
1273 warning(WARN_DELIM, "missing closing delimiter");
1274 input_stack::push(make_temp_iterator("\n"));
1275 break;
1277 if (tok == start
1278 && (compatible_flag || input_stack::get_level() == start_level))
1279 break;
1280 charinfo *ci = tok.get_char(1);
1281 if (ci) {
1282 node *n = curenv->make_char_node(ci);
1283 if (n)
1284 on->overstrike(n);
1287 return on;
1290 static node *do_bracket()
1292 token start;
1293 bracket_node *bn = new bracket_node;
1294 start.next();
1295 int start_level = input_stack::get_level();
1296 for (;;) {
1297 tok.next();
1298 if (tok.eof()) {
1299 warning(WARN_DELIM, "missing closing delimiter");
1300 break;
1302 if (tok.newline()) {
1303 warning(WARN_DELIM, "missing closing delimiter");
1304 input_stack::push(make_temp_iterator("\n"));
1305 break;
1307 if (tok == start
1308 && (compatible_flag || input_stack::get_level() == start_level))
1309 break;
1310 charinfo *ci = tok.get_char(1);
1311 if (ci) {
1312 node *n = curenv->make_char_node(ci);
1313 if (n)
1314 bn->bracket(n);
1317 return bn;
1320 static int do_name_test()
1322 token start;
1323 start.next();
1324 int start_level = input_stack::get_level();
1325 int bad_char = 0;
1326 int some_char = 0;
1327 for (;;) {
1328 tok.next();
1329 if (tok.newline() || tok.eof()) {
1330 warning(WARN_DELIM, "missing closing delimiter");
1331 input_stack::push(make_temp_iterator("\n"));
1332 break;
1334 if (tok == start
1335 && (compatible_flag || input_stack::get_level() == start_level))
1336 break;
1337 if (!tok.ch())
1338 bad_char = 1;
1339 some_char = 1;
1341 return some_char && !bad_char;
1344 static int do_expr_test()
1346 token start;
1347 start.next();
1348 int start_level = input_stack::get_level();
1349 if (!start.delimiter(1))
1350 return 0;
1351 tok.next();
1352 // disable all warning and error messages temporarily
1353 int saved_warning_mask = warning_mask;
1354 int saved_inhibit_errors = inhibit_errors;
1355 warning_mask = 0;
1356 inhibit_errors = 1;
1357 int dummy;
1358 int result = get_number_rigidly(&dummy, 'u');
1359 warning_mask = saved_warning_mask;
1360 inhibit_errors = saved_inhibit_errors;
1361 if (tok == start && input_stack::get_level() == start_level)
1362 return result;
1363 // ignore everything up to the delimiter in case we aren't right there
1364 for (;;) {
1365 tok.next();
1366 if (tok.newline() || tok.eof()) {
1367 warning(WARN_DELIM, "missing closing delimiter");
1368 input_stack::push(make_temp_iterator("\n"));
1369 break;
1371 if (tok == start && input_stack::get_level() == start_level)
1372 break;
1374 return 0;
1377 #if 0
1378 static node *do_zero_width()
1380 token start;
1381 start.next();
1382 int start_level = input_stack::get_level();
1383 environment env(curenv);
1384 environment *oldenv = curenv;
1385 curenv = &env;
1386 for (;;) {
1387 tok.next();
1388 if (tok.newline() || tok.eof()) {
1389 error("missing closing delimiter");
1390 break;
1392 if (tok == start
1393 && (compatible_flag || input_stack::get_level() == start_level))
1394 break;
1395 tok.process();
1397 curenv = oldenv;
1398 node *rev = env.extract_output_line();
1399 node *n = 0;
1400 while (rev) {
1401 node *tem = rev;
1402 rev = rev->next;
1403 tem->next = n;
1404 n = tem;
1406 return new zero_width_node(n);
1409 #else
1411 // It's undesirable for \Z to change environments, because then
1412 // \n(.w won't work as expected.
1414 static node *do_zero_width()
1416 node *rev = new dummy_node;
1417 token start;
1418 start.next();
1419 int start_level = input_stack::get_level();
1420 for (;;) {
1421 tok.next();
1422 if (tok.newline() || tok.eof()) {
1423 warning(WARN_DELIM, "missing closing delimiter");
1424 input_stack::push(make_temp_iterator("\n"));
1425 break;
1427 if (tok == start
1428 && (compatible_flag || input_stack::get_level() == start_level))
1429 break;
1430 if (!tok.add_to_node_list(&rev))
1431 error("invalid token in argument to \\Z");
1433 node *n = 0;
1434 while (rev) {
1435 node *tem = rev;
1436 rev = rev->next;
1437 tem->next = n;
1438 n = tem;
1440 return new zero_width_node(n);
1443 #endif
1445 token_node *node::get_token_node()
1447 return 0;
1450 class token_node : public node {
1451 public:
1452 token tk;
1453 token_node(const token &t);
1454 node *copy();
1455 token_node *get_token_node();
1456 int same(node *);
1457 const char *type();
1458 int force_tprint();
1461 token_node::token_node(const token &t) : tk(t)
1465 node *token_node::copy()
1467 return new token_node(tk);
1470 token_node *token_node::get_token_node()
1472 return this;
1475 int token_node::same(node *nd)
1477 return tk == ((token_node *)nd)->tk;
1480 const char *token_node::type()
1482 return "token_node";
1485 int token_node::force_tprint()
1487 return 0;
1490 token::token() : nd(0), type(TOKEN_EMPTY)
1494 token::~token()
1496 delete nd;
1499 token::token(const token &t)
1500 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1502 // Use two statements to work around bug in SGI C++.
1503 node *tem = t.nd;
1504 nd = tem ? tem->copy() : 0;
1507 void token::operator=(const token &t)
1509 delete nd;
1510 nm = t.nm;
1511 // Use two statements to work around bug in SGI C++.
1512 node *tem = t.nd;
1513 nd = tem ? tem->copy() : 0;
1514 c = t.c;
1515 val = t.val;
1516 dim = t.dim;
1517 type = t.type;
1520 void token::skip()
1522 while (space())
1523 next();
1526 int has_arg()
1528 while (tok.space())
1529 tok.next();
1530 return !tok.newline();
1533 void token::make_space()
1535 type = TOKEN_SPACE;
1538 void token::make_newline()
1540 type = TOKEN_NEWLINE;
1543 void token::next()
1545 if (nd) {
1546 delete nd;
1547 nd = 0;
1549 units x;
1550 for (;;) {
1551 node *n;
1552 int cc = input_stack::get(&n);
1553 if (cc != escape_char || escape_char == 0) {
1554 handle_normal_char:
1555 switch(cc) {
1556 case COMPATIBLE_SAVE:
1557 input_stack::save_compatible_flag(compatible_flag);
1558 compatible_flag = 0;
1559 continue;
1560 case COMPATIBLE_RESTORE:
1561 compatible_flag = input_stack::get_compatible_flag();
1562 continue;
1563 case EOF:
1564 type = TOKEN_EOF;
1565 return;
1566 case TRANSPARENT_FILE_REQUEST:
1567 case TITLE_REQUEST:
1568 case COPY_FILE_REQUEST:
1569 #ifdef COLUMN
1570 case VJUSTIFY_REQUEST:
1571 #endif /* COLUMN */
1572 type = TOKEN_REQUEST;
1573 c = cc;
1574 return;
1575 case BEGIN_TRAP:
1576 type = TOKEN_BEGIN_TRAP;
1577 return;
1578 case END_TRAP:
1579 type = TOKEN_END_TRAP;
1580 return;
1581 case LAST_PAGE_EJECTOR:
1582 seen_last_page_ejector = 1;
1583 // fall through
1584 case PAGE_EJECTOR:
1585 type = TOKEN_PAGE_EJECTOR;
1586 return;
1587 case ESCAPE_PERCENT:
1588 ESCAPE_PERCENT:
1589 type = TOKEN_HYPHEN_INDICATOR;
1590 return;
1591 case ESCAPE_SPACE:
1592 ESCAPE_SPACE:
1593 type = TOKEN_UNSTRETCHABLE_SPACE;
1594 return;
1595 case ESCAPE_TILDE:
1596 ESCAPE_TILDE:
1597 type = TOKEN_STRETCHABLE_SPACE;
1598 return;
1599 case ESCAPE_COLON:
1600 ESCAPE_COLON:
1601 type = TOKEN_ZERO_WIDTH_BREAK;
1602 return;
1603 case ESCAPE_e:
1604 ESCAPE_e:
1605 type = TOKEN_ESCAPE;
1606 return;
1607 case ESCAPE_E:
1608 goto handle_escape_char;
1609 case ESCAPE_BAR:
1610 ESCAPE_BAR:
1611 type = TOKEN_NODE;
1612 nd = new hmotion_node(curenv->get_narrow_space_width(),
1613 curenv->get_fill_color());
1614 return;
1615 case ESCAPE_CIRCUMFLEX:
1616 ESCAPE_CIRCUMFLEX:
1617 type = TOKEN_NODE;
1618 nd = new hmotion_node(curenv->get_half_narrow_space_width(),
1619 curenv->get_fill_color());
1620 return;
1621 case ESCAPE_NEWLINE:
1622 have_input = 0;
1623 break;
1624 case ESCAPE_LEFT_BRACE:
1625 ESCAPE_LEFT_BRACE:
1626 type = TOKEN_LEFT_BRACE;
1627 return;
1628 case ESCAPE_RIGHT_BRACE:
1629 ESCAPE_RIGHT_BRACE:
1630 type = TOKEN_RIGHT_BRACE;
1631 return;
1632 case ESCAPE_LEFT_QUOTE:
1633 ESCAPE_LEFT_QUOTE:
1634 type = TOKEN_SPECIAL;
1635 nm = symbol("ga");
1636 return;
1637 case ESCAPE_RIGHT_QUOTE:
1638 ESCAPE_RIGHT_QUOTE:
1639 type = TOKEN_SPECIAL;
1640 nm = symbol("aa");
1641 return;
1642 case ESCAPE_HYPHEN:
1643 ESCAPE_HYPHEN:
1644 type = TOKEN_SPECIAL;
1645 nm = symbol("-");
1646 return;
1647 case ESCAPE_UNDERSCORE:
1648 ESCAPE_UNDERSCORE:
1649 type = TOKEN_SPECIAL;
1650 nm = symbol("ul");
1651 return;
1652 case ESCAPE_c:
1653 ESCAPE_c:
1654 type = TOKEN_INTERRUPT;
1655 return;
1656 case ESCAPE_BANG:
1657 ESCAPE_BANG:
1658 type = TOKEN_TRANSPARENT;
1659 return;
1660 case ESCAPE_QUESTION:
1661 ESCAPE_QUESTION:
1662 nd = do_non_interpreted();
1663 if (nd) {
1664 type = TOKEN_NODE;
1665 return;
1667 break;
1668 case ESCAPE_AMPERSAND:
1669 ESCAPE_AMPERSAND:
1670 type = TOKEN_DUMMY;
1671 return;
1672 case ESCAPE_RIGHT_PARENTHESIS:
1673 ESCAPE_RIGHT_PARENTHESIS:
1674 type = TOKEN_TRANSPARENT_DUMMY;
1675 return;
1676 case '\b':
1677 type = TOKEN_BACKSPACE;
1678 return;
1679 case ' ':
1680 type = TOKEN_SPACE;
1681 return;
1682 case '\t':
1683 type = TOKEN_TAB;
1684 return;
1685 case '\n':
1686 type = TOKEN_NEWLINE;
1687 return;
1688 case '\001':
1689 type = TOKEN_LEADER;
1690 return;
1691 case 0:
1693 assert(n != 0);
1694 token_node *tn = n->get_token_node();
1695 if (tn) {
1696 *this = tn->tk;
1697 delete tn;
1699 else {
1700 nd = n;
1701 type = TOKEN_NODE;
1704 return;
1705 default:
1706 type = TOKEN_CHAR;
1707 c = cc;
1708 return;
1711 else {
1712 handle_escape_char:
1713 cc = input_stack::get(0);
1714 switch(cc) {
1715 case '(':
1716 nm = read_two_char_escape_name();
1717 type = TOKEN_SPECIAL;
1718 return;
1719 case EOF:
1720 type = TOKEN_EOF;
1721 error("end of input after escape character");
1722 return;
1723 case '`':
1724 goto ESCAPE_LEFT_QUOTE;
1725 case '\'':
1726 goto ESCAPE_RIGHT_QUOTE;
1727 case '-':
1728 goto ESCAPE_HYPHEN;
1729 case '_':
1730 goto ESCAPE_UNDERSCORE;
1731 case '%':
1732 goto ESCAPE_PERCENT;
1733 case ' ':
1734 goto ESCAPE_SPACE;
1735 case '0':
1736 nd = new hmotion_node(curenv->get_digit_width(),
1737 curenv->get_fill_color());
1738 type = TOKEN_NODE;
1739 return;
1740 case '|':
1741 goto ESCAPE_BAR;
1742 case '^':
1743 goto ESCAPE_CIRCUMFLEX;
1744 case '/':
1745 type = TOKEN_ITALIC_CORRECTION;
1746 return;
1747 case ',':
1748 type = TOKEN_NODE;
1749 nd = new left_italic_corrected_node;
1750 return;
1751 case '&':
1752 goto ESCAPE_AMPERSAND;
1753 case ')':
1754 goto ESCAPE_RIGHT_PARENTHESIS;
1755 case '!':
1756 goto ESCAPE_BANG;
1757 case '?':
1758 goto ESCAPE_QUESTION;
1759 case '~':
1760 goto ESCAPE_TILDE;
1761 case ':':
1762 goto ESCAPE_COLON;
1763 case '"':
1764 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1766 if (cc == '\n')
1767 type = TOKEN_NEWLINE;
1768 else
1769 type = TOKEN_EOF;
1770 return;
1771 case '#': // Like \" but newline is ignored.
1772 while ((cc = input_stack::get(0)) != '\n')
1773 if (cc == EOF) {
1774 type = TOKEN_EOF;
1775 return;
1777 break;
1778 case '$':
1780 symbol s = read_escape_name();
1781 if (!(s.is_null() || s.is_empty()))
1782 interpolate_arg(s);
1783 break;
1785 case '*':
1787 symbol s = read_escape_name(WITH_ARGS);
1788 if (!(s.is_null() || s.is_empty())) {
1789 if (have_string_arg) {
1790 have_string_arg = 0;
1791 interpolate_string_with_args(s);
1793 else
1794 interpolate_string(s);
1796 break;
1798 case 'a':
1799 nd = new non_interpreted_char_node('\001');
1800 type = TOKEN_NODE;
1801 return;
1802 case 'A':
1803 c = '0' + do_name_test();
1804 type = TOKEN_CHAR;
1805 return;
1806 case 'b':
1807 nd = do_bracket();
1808 type = TOKEN_NODE;
1809 return;
1810 case 'B':
1811 c = '0' + do_expr_test();
1812 type = TOKEN_CHAR;
1813 return;
1814 case 'c':
1815 goto ESCAPE_c;
1816 case 'C':
1817 nm = get_delim_name();
1818 if (nm.is_null())
1819 break;
1820 type = TOKEN_SPECIAL;
1821 return;
1822 case 'd':
1823 type = TOKEN_NODE;
1824 nd = new vmotion_node(curenv->get_size() / 2,
1825 curenv->get_fill_color());
1826 return;
1827 case 'D':
1828 nd = read_draw_node();
1829 if (!nd)
1830 break;
1831 type = TOKEN_NODE;
1832 return;
1833 case 'e':
1834 goto ESCAPE_e;
1835 case 'E':
1836 goto handle_escape_char;
1837 case 'f':
1839 symbol s = read_escape_name(ALLOW_EMPTY);
1840 if (s.is_null())
1841 break;
1842 const char *p;
1843 for (p = s.contents(); *p != '\0'; p++)
1844 if (!csdigit(*p))
1845 break;
1846 if (*p || s.is_empty())
1847 curenv->set_font(s);
1848 else
1849 curenv->set_font(atoi(s.contents()));
1850 if (!compatible_flag)
1851 have_input = 1;
1852 break;
1854 case 'F':
1856 symbol s = read_escape_name(ALLOW_EMPTY);
1857 if (s.is_null())
1858 break;
1859 curenv->set_family(s);
1860 have_input = 1;
1861 break;
1863 case 'g':
1865 symbol s = read_escape_name();
1866 if (!(s.is_null() || s.is_empty()))
1867 interpolate_number_format(s);
1868 break;
1870 case 'h':
1871 if (!get_delim_number(&x, 'm'))
1872 break;
1873 type = TOKEN_NODE;
1874 nd = new hmotion_node(x, curenv->get_fill_color());
1875 return;
1876 case 'H':
1877 // don't take height increments relative to previous height if
1878 // in compatibility mode
1879 if (!compatible_flag && curenv->get_char_height())
1881 if (get_delim_number(&x, 'z', curenv->get_char_height()))
1882 curenv->set_char_height(x);
1884 else
1886 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1887 curenv->set_char_height(x);
1889 if (!compatible_flag)
1890 have_input = 1;
1891 break;
1892 case 'k':
1893 nm = read_escape_name();
1894 if (nm.is_null() || nm.is_empty())
1895 break;
1896 type = TOKEN_MARK_INPUT;
1897 return;
1898 case 'l':
1899 case 'L':
1901 charinfo *s = 0;
1902 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1903 break;
1904 if (s == 0)
1905 s = get_charinfo(cc == 'l' ? "ru" : "br");
1906 type = TOKEN_NODE;
1907 node *n = curenv->make_char_node(s);
1908 if (cc == 'l')
1909 nd = new hline_node(x, n);
1910 else
1911 nd = new vline_node(x, n);
1912 return;
1914 case 'm':
1915 do_glyph_color(read_escape_name(ALLOW_EMPTY));
1916 if (!compatible_flag)
1917 have_input = 1;
1918 break;
1919 case 'M':
1920 do_fill_color(read_escape_name(ALLOW_EMPTY));
1921 if (!compatible_flag)
1922 have_input = 1;
1923 break;
1924 case 'n':
1926 int inc;
1927 symbol s = read_increment_and_escape_name(&inc);
1928 if (!(s.is_null() || s.is_empty()))
1929 interpolate_number_reg(s, inc);
1930 break;
1932 case 'N':
1933 if (!get_delim_number(&val, 0))
1934 break;
1935 type = TOKEN_NUMBERED_CHAR;
1936 return;
1937 case 'o':
1938 nd = do_overstrike();
1939 type = TOKEN_NODE;
1940 return;
1941 case 'O':
1942 nd = do_suppress(read_escape_name());
1943 if (!nd)
1944 break;
1945 type = TOKEN_NODE;
1946 return;
1947 case 'p':
1948 type = TOKEN_SPREAD;
1949 return;
1950 case 'r':
1951 type = TOKEN_NODE;
1952 nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
1953 return;
1954 case 'R':
1955 do_register();
1956 if (!compatible_flag)
1957 have_input = 1;
1958 break;
1959 case 's':
1960 if (read_size(&x))
1961 curenv->set_size(x);
1962 if (!compatible_flag)
1963 have_input = 1;
1964 break;
1965 case 'S':
1966 if (get_delim_number(&x, 0))
1967 curenv->set_char_slant(x);
1968 if (!compatible_flag)
1969 have_input = 1;
1970 break;
1971 case 't':
1972 type = TOKEN_NODE;
1973 nd = new non_interpreted_char_node('\t');
1974 return;
1975 case 'u':
1976 type = TOKEN_NODE;
1977 nd = new vmotion_node(-curenv->get_size() / 2,
1978 curenv->get_fill_color());
1979 return;
1980 case 'v':
1981 if (!get_delim_number(&x, 'v'))
1982 break;
1983 type = TOKEN_NODE;
1984 nd = new vmotion_node(x, curenv->get_fill_color());
1985 return;
1986 case 'V':
1988 symbol s = read_escape_name();
1989 if (!(s.is_null() || s.is_empty()))
1990 interpolate_environment_variable(s);
1991 break;
1993 case 'w':
1994 do_width();
1995 break;
1996 case 'x':
1997 if (!get_delim_number(&x, 'v'))
1998 break;
1999 type = TOKEN_NODE;
2000 nd = new extra_size_node(x);
2001 return;
2002 case 'X':
2003 nd = do_special();
2004 if (!nd)
2005 break;
2006 type = TOKEN_NODE;
2007 return;
2008 case 'Y':
2010 symbol s = read_escape_name();
2011 if (s.is_null() || s.is_empty())
2012 break;
2013 request_or_macro *p = lookup_request(s);
2014 macro *m = p->to_macro();
2015 if (!m) {
2016 error("can't transparently throughput a request");
2017 break;
2019 nd = new special_node(*m);
2020 type = TOKEN_NODE;
2021 return;
2023 case 'z':
2025 next();
2026 if (type == TOKEN_NODE)
2027 nd = new zero_width_node(nd);
2028 else {
2029 charinfo *ci = get_char(1);
2030 if (ci == 0)
2031 break;
2032 node *gn = curenv->make_char_node(ci);
2033 if (gn == 0)
2034 break;
2035 nd = new zero_width_node(gn);
2036 type = TOKEN_NODE;
2038 return;
2040 case 'Z':
2041 nd = do_zero_width();
2042 if (nd == 0)
2043 break;
2044 type = TOKEN_NODE;
2045 return;
2046 case '{':
2047 goto ESCAPE_LEFT_BRACE;
2048 case '}':
2049 goto ESCAPE_RIGHT_BRACE;
2050 case '\n':
2051 break;
2052 case '[':
2053 if (!compatible_flag) {
2054 symbol s = read_long_escape_name(WITH_ARGS);
2055 if (s.is_null() || s.is_empty())
2056 break;
2057 if (have_string_arg) {
2058 have_string_arg = 0;
2059 nm = composite_glyph_name(s);
2061 else {
2062 const char *gn = check_unicode_name(s.contents());
2063 if (gn) {
2064 const char *gn_decomposed = decompose_unicode(gn);
2065 if (gn_decomposed)
2066 gn = &gn_decomposed[1];
2067 const char *groff_gn = unicode_to_glyph_name(gn);
2068 if (groff_gn)
2069 nm = symbol(groff_gn);
2070 else {
2071 char *buf = new char[strlen(gn) + 1 + 1];
2072 strcpy(buf, "u");
2073 strcat(buf, gn);
2074 nm = symbol(buf);
2075 a_delete buf;
2078 else
2079 nm = symbol(s.contents());
2081 type = TOKEN_SPECIAL;
2082 return;
2084 goto handle_normal_char;
2085 default:
2086 if (cc != escape_char && cc != '.')
2087 warning(WARN_ESCAPE, "escape character ignored before %1",
2088 input_char_description(cc));
2089 goto handle_normal_char;
2095 int token::operator==(const token &t)
2097 if (type != t.type)
2098 return 0;
2099 switch(type) {
2100 case TOKEN_CHAR:
2101 return c == t.c;
2102 case TOKEN_SPECIAL:
2103 return nm == t.nm;
2104 case TOKEN_NUMBERED_CHAR:
2105 return val == t.val;
2106 default:
2107 return 1;
2111 int token::operator!=(const token &t)
2113 return !(*this == t);
2116 // is token a suitable delimiter (like ')?
2118 int token::delimiter(int err)
2120 switch(type) {
2121 case TOKEN_CHAR:
2122 switch(c) {
2123 case '0':
2124 case '1':
2125 case '2':
2126 case '3':
2127 case '4':
2128 case '5':
2129 case '6':
2130 case '7':
2131 case '8':
2132 case '9':
2133 case '+':
2134 case '-':
2135 case '/':
2136 case '*':
2137 case '%':
2138 case '<':
2139 case '>':
2140 case '=':
2141 case '&':
2142 case ':':
2143 case '(':
2144 case ')':
2145 case '.':
2146 if (err)
2147 error("cannot use character `%1' as a starting delimiter", char(c));
2148 return 0;
2149 default:
2150 return 1;
2152 case TOKEN_NODE:
2153 case TOKEN_SPACE:
2154 case TOKEN_STRETCHABLE_SPACE:
2155 case TOKEN_UNSTRETCHABLE_SPACE:
2156 case TOKEN_TAB:
2157 case TOKEN_NEWLINE:
2158 if (err)
2159 error("cannot use %1 as a starting delimiter", description());
2160 return 0;
2161 default:
2162 return 1;
2166 const char *token::description()
2168 static char buf[4];
2169 switch (type) {
2170 case TOKEN_BACKSPACE:
2171 return "a backspace character";
2172 case TOKEN_CHAR:
2173 buf[0] = '`';
2174 buf[1] = c;
2175 buf[2] = '\'';
2176 buf[3] = '\0';
2177 return buf;
2178 case TOKEN_DUMMY:
2179 return "`\\&'";
2180 case TOKEN_ESCAPE:
2181 return "`\\e'";
2182 case TOKEN_HYPHEN_INDICATOR:
2183 return "`\\%'";
2184 case TOKEN_INTERRUPT:
2185 return "`\\c'";
2186 case TOKEN_ITALIC_CORRECTION:
2187 return "`\\/'";
2188 case TOKEN_LEADER:
2189 return "a leader character";
2190 case TOKEN_LEFT_BRACE:
2191 return "`\\{'";
2192 case TOKEN_MARK_INPUT:
2193 return "`\\k'";
2194 case TOKEN_NEWLINE:
2195 return "newline";
2196 case TOKEN_NODE:
2197 return "a node";
2198 case TOKEN_NUMBERED_CHAR:
2199 return "`\\N'";
2200 case TOKEN_RIGHT_BRACE:
2201 return "`\\}'";
2202 case TOKEN_SPACE:
2203 return "a space";
2204 case TOKEN_SPECIAL:
2205 return "a special character";
2206 case TOKEN_SPREAD:
2207 return "`\\p'";
2208 case TOKEN_STRETCHABLE_SPACE:
2209 return "`\\~'";
2210 case TOKEN_UNSTRETCHABLE_SPACE:
2211 return "`\\ '";
2212 case TOKEN_TAB:
2213 return "a tab character";
2214 case TOKEN_TRANSPARENT:
2215 return "`\\!'";
2216 case TOKEN_TRANSPARENT_DUMMY:
2217 return "`\\)'";
2218 case TOKEN_ZERO_WIDTH_BREAK:
2219 return "`\\:'";
2220 case TOKEN_EOF:
2221 return "end of input";
2222 default:
2223 break;
2225 return "a magic token";
2228 void skip_line()
2230 while (!tok.newline())
2231 if (tok.eof())
2232 return;
2233 else
2234 tok.next();
2235 tok.next();
2238 void compatible()
2240 int n;
2241 if (has_arg() && get_integer(&n))
2242 compatible_flag = n != 0;
2243 else
2244 compatible_flag = 1;
2245 skip_line();
2248 static void empty_name_warning(int required)
2250 if (tok.newline() || tok.eof()) {
2251 if (required)
2252 warning(WARN_MISSING, "missing name");
2254 else if (tok.right_brace() || tok.tab()) {
2255 const char *start = tok.description();
2256 do {
2257 tok.next();
2258 } while (tok.space() || tok.right_brace() || tok.tab());
2259 if (!tok.newline() && !tok.eof())
2260 error("%1 is not allowed before an argument", start);
2261 else if (required)
2262 warning(WARN_MISSING, "missing name");
2264 else if (required)
2265 error("name expected (got %1)", tok.description());
2266 else
2267 error("name expected (got %1): treated as missing", tok.description());
2270 static void non_empty_name_warning()
2272 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2273 && !tok.right_brace()
2274 // We don't want to give a warning for .el\{
2275 && !tok.left_brace())
2276 error("%1 is not allowed in a name", tok.description());
2279 symbol get_name(int required)
2281 if (compatible_flag) {
2282 char buf[3];
2283 tok.skip();
2284 if ((buf[0] = tok.ch()) != 0) {
2285 tok.next();
2286 if ((buf[1] = tok.ch()) != 0) {
2287 buf[2] = 0;
2288 tok.make_space();
2290 else
2291 non_empty_name_warning();
2292 return symbol(buf);
2294 else {
2295 empty_name_warning(required);
2296 return NULL_SYMBOL;
2299 else
2300 return get_long_name(required);
2303 symbol get_long_name(int required)
2305 return do_get_long_name(required, 0);
2308 static symbol do_get_long_name(int required, char end)
2310 while (tok.space())
2311 tok.next();
2312 char abuf[ABUF_SIZE];
2313 char *buf = abuf;
2314 int buf_size = ABUF_SIZE;
2315 int i = 0;
2316 for (;;) {
2317 // If end != 0 we normally have to append a null byte
2318 if (i + 2 > buf_size) {
2319 if (buf == abuf) {
2320 buf = new char[ABUF_SIZE*2];
2321 memcpy(buf, abuf, buf_size);
2322 buf_size = ABUF_SIZE*2;
2324 else {
2325 char *old_buf = buf;
2326 buf = new char[buf_size*2];
2327 memcpy(buf, old_buf, buf_size);
2328 buf_size *= 2;
2329 a_delete old_buf;
2332 if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
2333 break;
2334 i++;
2335 tok.next();
2337 if (i == 0) {
2338 empty_name_warning(required);
2339 return NULL_SYMBOL;
2341 if (end && buf[i] == end)
2342 buf[i+1] = '\0';
2343 else
2344 non_empty_name_warning();
2345 if (buf == abuf)
2346 return symbol(buf);
2347 else {
2348 symbol s(buf);
2349 a_delete buf;
2350 return s;
2354 void exit_troff()
2356 exit_started = 1;
2357 topdiv->set_last_page();
2358 if (!end_macro_name.is_null()) {
2359 spring_trap(end_macro_name);
2360 tok.next();
2361 process_input_stack();
2363 curenv->final_break();
2364 tok.next();
2365 process_input_stack();
2366 end_diversions();
2367 if (topdiv->get_page_length() > 0) {
2368 done_end_macro = 1;
2369 topdiv->set_ejecting();
2370 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2371 input_stack::push(make_temp_iterator((char *)buf));
2372 topdiv->space(topdiv->get_page_length(), 1);
2373 tok.next();
2374 process_input_stack();
2375 seen_last_page_ejector = 1; // should be set already
2376 topdiv->set_ejecting();
2377 push_page_ejector();
2378 topdiv->space(topdiv->get_page_length(), 1);
2379 tok.next();
2380 process_input_stack();
2382 // This will only happen if a trap-invoked macro starts a diversion,
2383 // or if vertical position traps have been disabled.
2384 cleanup_and_exit(0);
2387 // This implements .ex. The input stack must be cleared before calling
2388 // exit_troff().
2390 void exit_request()
2392 input_stack::clear();
2393 if (exit_started)
2394 tok.next();
2395 else
2396 exit_troff();
2399 void return_macro_request()
2401 input_stack::pop_macro();
2402 tok.next();
2405 void end_macro()
2407 end_macro_name = get_name();
2408 skip_line();
2411 void blank_line_macro()
2413 blank_line_macro_name = get_name();
2414 skip_line();
2417 static void trapping_blank_line()
2419 if (!blank_line_macro_name.is_null())
2420 spring_trap(blank_line_macro_name);
2421 else
2422 blank_line();
2425 void do_request()
2427 int old_compatible_flag = compatible_flag;
2428 compatible_flag = 0;
2429 symbol nm = get_name();
2430 if (nm.is_null())
2431 skip_line();
2432 else
2433 interpolate_macro(nm);
2434 compatible_flag = old_compatible_flag;
2437 inline int possibly_handle_first_page_transition()
2439 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2440 handle_first_page_transition();
2441 return 1;
2443 else
2444 return 0;
2447 static int transparent_translate(int cc)
2449 if (!invalid_input_char(cc)) {
2450 charinfo *ci = charset_table[cc];
2451 switch (ci->get_special_translation(1)) {
2452 case charinfo::TRANSLATE_SPACE:
2453 return ' ';
2454 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2455 return ESCAPE_TILDE;
2456 case charinfo::TRANSLATE_DUMMY:
2457 return ESCAPE_AMPERSAND;
2458 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2459 return ESCAPE_PERCENT;
2461 // This is really ugly.
2462 ci = ci->get_translation(1);
2463 if (ci) {
2464 int c = ci->get_ascii_code();
2465 if (c != '\0')
2466 return c;
2467 error("can't translate %1 to special character `%2'"
2468 " in transparent throughput",
2469 input_char_description(cc),
2470 ci->nm.contents());
2473 return cc;
2476 class int_stack {
2477 struct int_stack_element {
2478 int n;
2479 int_stack_element *next;
2480 } *top;
2481 public:
2482 int_stack();
2483 ~int_stack();
2484 void push(int);
2485 int is_empty();
2486 int pop();
2489 int_stack::int_stack()
2491 top = 0;
2494 int_stack::~int_stack()
2496 while (top != 0) {
2497 int_stack_element *temp = top;
2498 top = top->next;
2499 delete temp;
2503 int int_stack::is_empty()
2505 return top == 0;
2508 void int_stack::push(int n)
2510 int_stack_element *p = new int_stack_element;
2511 p->next = top;
2512 p->n = n;
2513 top = p;
2516 int int_stack::pop()
2518 assert(top != 0);
2519 int_stack_element *p = top;
2520 top = top->next;
2521 int n = p->n;
2522 delete p;
2523 return n;
2526 int node::reread(int *)
2528 return 0;
2531 int diverted_space_node::reread(int *bolp)
2533 if (curenv->get_fill())
2534 trapping_blank_line();
2535 else
2536 curdiv->space(n);
2537 *bolp = 1;
2538 return 1;
2541 int diverted_copy_file_node::reread(int *bolp)
2543 curdiv->copy_file(filename.contents());
2544 *bolp = 1;
2545 return 1;
2548 int word_space_node::reread(int *)
2550 if (unformat) {
2551 for (width_list *w = orig_width; w; w = w->next)
2552 curenv->space(w->width, w->sentence_width);
2553 unformat = 0;
2554 return 1;
2556 return 0;
2559 int unbreakable_space_node::reread(int *)
2561 return 0;
2564 int hmotion_node::reread(int *)
2566 if (unformat && was_tab) {
2567 curenv->handle_tab(0);
2568 unformat = 0;
2569 return 1;
2571 return 0;
2574 void process_input_stack()
2576 int_stack trap_bol_stack;
2577 int bol = 1;
2578 for (;;) {
2579 int suppress_next = 0;
2580 switch (tok.type) {
2581 case token::TOKEN_CHAR:
2583 unsigned char ch = tok.c;
2584 if (bol && !have_input
2585 && (ch == curenv->control_char
2586 || ch == curenv->no_break_control_char)) {
2587 break_flag = ch == curenv->control_char;
2588 // skip tabs as well as spaces here
2589 do {
2590 tok.next();
2591 } while (tok.white_space());
2592 symbol nm = get_name();
2593 if (nm.is_null())
2594 skip_line();
2595 else
2596 interpolate_macro(nm);
2597 suppress_next = 1;
2599 else {
2600 if (possibly_handle_first_page_transition())
2602 else {
2603 for (;;) {
2604 curenv->add_char(charset_table[ch]);
2605 tok.next();
2606 if (tok.type != token::TOKEN_CHAR)
2607 break;
2608 ch = tok.c;
2610 suppress_next = 1;
2611 bol = 0;
2614 break;
2616 case token::TOKEN_TRANSPARENT:
2618 if (bol) {
2619 if (possibly_handle_first_page_transition())
2621 else {
2622 int cc;
2623 do {
2624 node *n;
2625 cc = get_copy(&n);
2626 if (cc != EOF)
2627 if (cc != '\0')
2628 curdiv->transparent_output(transparent_translate(cc));
2629 else
2630 curdiv->transparent_output(n);
2631 } while (cc != '\n' && cc != EOF);
2632 if (cc == EOF)
2633 curdiv->transparent_output('\n');
2636 break;
2638 case token::TOKEN_NEWLINE:
2640 if (bol && !have_input
2641 && !curenv->get_prev_line_interrupted())
2642 trapping_blank_line();
2643 else {
2644 curenv->newline();
2645 bol = 1;
2647 break;
2649 case token::TOKEN_REQUEST:
2651 int request_code = tok.c;
2652 tok.next();
2653 switch (request_code) {
2654 case TITLE_REQUEST:
2655 title();
2656 break;
2657 case COPY_FILE_REQUEST:
2658 copy_file();
2659 break;
2660 case TRANSPARENT_FILE_REQUEST:
2661 transparent_file();
2662 break;
2663 #ifdef COLUMN
2664 case VJUSTIFY_REQUEST:
2665 vjustify();
2666 break;
2667 #endif /* COLUMN */
2668 default:
2669 assert(0);
2670 break;
2672 suppress_next = 1;
2673 break;
2675 case token::TOKEN_SPACE:
2677 if (possibly_handle_first_page_transition())
2679 else if (bol && !curenv->get_prev_line_interrupted()) {
2680 int nspaces = 0;
2681 // save space_width now so that it isn't changed by \f or \s
2682 // which we wouldn't notice here
2683 hunits space_width = curenv->get_space_width();
2684 do {
2685 nspaces += tok.nspaces();
2686 tok.next();
2687 } while (tok.space());
2688 if (tok.newline())
2689 trapping_blank_line();
2690 else {
2691 push_token(tok);
2692 curenv->do_break();
2693 curenv->add_node(new hmotion_node(space_width * nspaces,
2694 curenv->get_fill_color()));
2695 bol = 0;
2698 else {
2699 curenv->space();
2700 bol = 0;
2702 break;
2704 case token::TOKEN_EOF:
2705 return;
2706 case token::TOKEN_NODE:
2708 if (possibly_handle_first_page_transition())
2710 else if (tok.nd->reread(&bol)) {
2711 delete tok.nd;
2712 tok.nd = 0;
2714 else {
2715 curenv->add_node(tok.nd);
2716 tok.nd = 0;
2717 bol = 0;
2718 curenv->possibly_break_line(1);
2720 break;
2722 case token::TOKEN_PAGE_EJECTOR:
2724 continue_page_eject();
2725 // I think we just want to preserve bol.
2726 // bol = 1;
2727 break;
2729 case token::TOKEN_BEGIN_TRAP:
2731 trap_bol_stack.push(bol);
2732 bol = 1;
2733 break;
2735 case token::TOKEN_END_TRAP:
2737 if (trap_bol_stack.is_empty())
2738 error("spurious end trap token detected!");
2739 else
2740 bol = trap_bol_stack.pop();
2742 /* I'm not totally happy about this. But I can't think of any other
2743 way to do it. Doing an output_pending_lines() whenever a
2744 TOKEN_END_TRAP is detected doesn't work: for example,
2746 .wh -1i x
2747 .de x
2750 .wh -.5i y
2751 .de y
2752 .tl ''-%-''
2755 .ll .5i
2756 .sp |\n(.pu-1i-.5v
2757 a\%very\%very\%long\%word
2759 will print all but the first lines from the word immediately
2760 after the footer, rather than on the next page. */
2762 if (trap_bol_stack.is_empty())
2763 curenv->output_pending_lines();
2764 break;
2766 default:
2768 bol = 0;
2769 tok.process();
2770 break;
2773 if (!suppress_next)
2774 tok.next();
2775 trap_sprung_flag = 0;
2779 #ifdef WIDOW_CONTROL
2781 void flush_pending_lines()
2783 while (!tok.newline() && !tok.eof())
2784 tok.next();
2785 curenv->output_pending_lines();
2786 tok.next();
2789 #endif /* WIDOW_CONTROL */
2791 request_or_macro::request_or_macro()
2795 macro *request_or_macro::to_macro()
2797 return 0;
2800 request::request(REQUEST_FUNCP pp) : p(pp)
2804 void request::invoke(symbol)
2806 (*p)();
2809 struct char_block {
2810 enum { SIZE = 128 };
2811 unsigned char s[SIZE];
2812 char_block *next;
2813 char_block();
2816 char_block::char_block()
2817 : next(0)
2821 class char_list {
2822 public:
2823 char_list();
2824 ~char_list();
2825 void append(unsigned char);
2826 void set(unsigned char, int);
2827 unsigned char get(int);
2828 int length();
2829 private:
2830 unsigned char *ptr;
2831 int len;
2832 char_block *head;
2833 char_block *tail;
2834 friend class macro_header;
2835 friend class string_iterator;
2838 char_list::char_list()
2839 : ptr(0), len(0), head(0), tail(0)
2843 char_list::~char_list()
2845 while (head != 0) {
2846 char_block *tem = head;
2847 head = head->next;
2848 delete tem;
2852 int char_list::length()
2854 return len;
2857 void char_list::append(unsigned char c)
2859 if (tail == 0) {
2860 head = tail = new char_block;
2861 ptr = tail->s;
2863 else {
2864 if (ptr >= tail->s + char_block::SIZE) {
2865 tail->next = new char_block;
2866 tail = tail->next;
2867 ptr = tail->s;
2870 *ptr++ = c;
2871 len++;
2874 void char_list::set(unsigned char c, int offset)
2876 assert(len > offset);
2877 // optimization for access at the end
2878 int boundary = len - len % char_block::SIZE;
2879 if (offset >= boundary) {
2880 *(tail->s + offset - boundary) = c;
2881 return;
2883 char_block *tem = head;
2884 int l = 0;
2885 for (;;) {
2886 l += char_block::SIZE;
2887 if (l > offset) {
2888 *(tem->s + offset % char_block::SIZE) = c;
2889 return;
2891 tem = tem->next;
2895 unsigned char char_list::get(int offset)
2897 assert(len > offset);
2898 // optimization for access at the end
2899 int boundary = len - len % char_block::SIZE;
2900 if (offset >= boundary)
2901 return *(tail->s + offset - boundary);
2902 char_block *tem = head;
2903 int l = 0;
2904 for (;;) {
2905 l += char_block::SIZE;
2906 if (l > offset)
2907 return *(tem->s + offset % char_block::SIZE);
2908 tem = tem->next;
2912 class node_list {
2913 node *head;
2914 node *tail;
2915 public:
2916 node_list();
2917 ~node_list();
2918 void append(node *);
2919 int length();
2920 node *extract();
2922 friend class macro_header;
2923 friend class string_iterator;
2926 void node_list::append(node *n)
2928 if (head == 0) {
2929 n->next = 0;
2930 head = tail = n;
2932 else {
2933 n->next = 0;
2934 tail = tail->next = n;
2938 int node_list::length()
2940 int total = 0;
2941 for (node *n = head; n != 0; n = n->next)
2942 ++total;
2943 return total;
2946 node_list::node_list()
2948 head = tail = 0;
2951 node *node_list::extract()
2953 node *temp = head;
2954 head = tail = 0;
2955 return temp;
2958 node_list::~node_list()
2960 delete_node_list(head);
2963 struct macro_header {
2964 public:
2965 int count;
2966 char_list cl;
2967 node_list nl;
2968 macro_header() { count = 1; }
2969 macro_header *copy(int);
2972 macro::~macro()
2974 if (p != 0 && --(p->count) <= 0)
2975 delete p;
2978 macro::macro()
2980 if (!input_stack::get_location(1, &filename, &lineno)) {
2981 filename = 0;
2982 lineno = 0;
2984 len = 0;
2985 empty_macro = 1;
2986 p = 0;
2989 macro::macro(const macro &m)
2990 : p(m.p), filename(m.filename), lineno(m.lineno), len(m.len),
2991 empty_macro(m.empty_macro)
2993 if (p != 0)
2994 p->count++;
2997 macro &macro::operator=(const macro &m)
2999 // don't assign object
3000 if (m.p != 0)
3001 m.p->count++;
3002 if (p != 0 && --(p->count) <= 0)
3003 delete p;
3004 p = m.p;
3005 filename = m.filename;
3006 lineno = m.lineno;
3007 len = m.len;
3008 empty_macro = m.empty_macro;
3009 return *this;
3012 void macro::append(unsigned char c)
3014 assert(c != 0);
3015 if (p == 0)
3016 p = new macro_header;
3017 if (p->cl.length() != len) {
3018 macro_header *tem = p->copy(len);
3019 if (--(p->count) <= 0)
3020 delete p;
3021 p = tem;
3023 p->cl.append(c);
3024 ++len;
3025 if (c != COMPATIBLE_SAVE && c != COMPATIBLE_RESTORE)
3026 empty_macro = 0;
3029 void macro::set(unsigned char c, int offset)
3031 assert(p != 0);
3032 assert(c != 0);
3033 p->cl.set(c, offset);
3036 unsigned char macro::get(int offset)
3038 assert(p != 0);
3039 return p->cl.get(offset);
3042 int macro::length()
3044 return len;
3047 void macro::append_str(const char *s)
3049 int i = 0;
3051 if (s) {
3052 while (s[i] != (char)0) {
3053 append(s[i]);
3054 i++;
3059 void macro::append(node *n)
3061 assert(n != 0);
3062 if (p == 0)
3063 p = new macro_header;
3064 if (p->cl.length() != len) {
3065 macro_header *tem = p->copy(len);
3066 if (--(p->count) <= 0)
3067 delete p;
3068 p = tem;
3070 p->cl.append(0);
3071 p->nl.append(n);
3072 ++len;
3073 empty_macro = 0;
3076 void macro::append_unsigned(unsigned int i)
3078 unsigned int j = i / 10;
3079 if (j != 0)
3080 append_unsigned(j);
3081 append(((unsigned char)(((int)'0') + i % 10)));
3084 void macro::append_int(int i)
3086 if (i < 0) {
3087 append('-');
3088 i = -i;
3090 append_unsigned((unsigned int)i);
3093 void macro::print_size()
3095 errprint("%1", len);
3098 // make a copy of the first n bytes
3100 macro_header *macro_header::copy(int n)
3102 macro_header *p = new macro_header;
3103 char_block *bp = cl.head;
3104 unsigned char *ptr = bp->s;
3105 node *nd = nl.head;
3106 while (--n >= 0) {
3107 if (ptr >= bp->s + char_block::SIZE) {
3108 bp = bp->next;
3109 ptr = bp->s;
3111 int c = *ptr++;
3112 p->cl.append(c);
3113 if (c == 0) {
3114 p->nl.append(nd->copy());
3115 nd = nd->next;
3118 return p;
3121 void print_macros()
3123 object_dictionary_iterator iter(request_dictionary);
3124 request_or_macro *rm;
3125 symbol s;
3126 while (iter.get(&s, (object **)&rm)) {
3127 assert(!s.is_null());
3128 macro *m = rm->to_macro();
3129 if (m) {
3130 errprint("%1\t", s.contents());
3131 m->print_size();
3132 errprint("\n");
3135 fflush(stderr);
3136 skip_line();
3139 class string_iterator : public input_iterator {
3140 macro mac;
3141 const char *how_invoked;
3142 int newline_flag;
3143 int lineno;
3144 char_block *bp;
3145 int count; // of characters remaining
3146 node *nd;
3147 int saved_compatible_flag;
3148 protected:
3149 symbol nm;
3150 string_iterator();
3151 public:
3152 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
3153 int fill(node **);
3154 int peek();
3155 int get_location(int, const char **, int *);
3156 void backtrace();
3157 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3158 int get_compatible_flag() { return saved_compatible_flag; }
3161 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3162 : mac(m), how_invoked(p),
3163 newline_flag(0), lineno(1), nm(s)
3165 count = mac.len;
3166 if (count != 0) {
3167 bp = mac.p->cl.head;
3168 nd = mac.p->nl.head;
3169 ptr = eptr = bp->s;
3171 else {
3172 bp = 0;
3173 nd = 0;
3174 ptr = eptr = 0;
3178 string_iterator::string_iterator()
3180 bp = 0;
3181 nd = 0;
3182 ptr = eptr = 0;
3183 newline_flag = 0;
3184 how_invoked = 0;
3185 lineno = 1;
3186 count = 0;
3189 int string_iterator::fill(node **np)
3191 if (newline_flag)
3192 lineno++;
3193 newline_flag = 0;
3194 if (count <= 0)
3195 return EOF;
3196 const unsigned char *p = eptr;
3197 if (p >= bp->s + char_block::SIZE) {
3198 bp = bp->next;
3199 p = bp->s;
3201 if (*p == '\0') {
3202 if (np)
3203 *np = nd->copy();
3204 nd = nd->next;
3205 eptr = ptr = p + 1;
3206 count--;
3207 return 0;
3209 const unsigned char *e = bp->s + char_block::SIZE;
3210 if (e - p > count)
3211 e = p + count;
3212 ptr = p;
3213 while (p < e) {
3214 unsigned char c = *p;
3215 if (c == '\n' || c == ESCAPE_NEWLINE) {
3216 newline_flag = 1;
3217 p++;
3218 break;
3220 if (c == '\0')
3221 break;
3222 p++;
3224 eptr = p;
3225 count -= p - ptr;
3226 return *ptr++;
3229 int string_iterator::peek()
3231 if (count <= 0)
3232 return EOF;
3233 const unsigned char *p = eptr;
3234 if (p >= bp->s + char_block::SIZE) {
3235 p = bp->next->s;
3237 return *p;
3240 int string_iterator::get_location(int allow_macro,
3241 const char **filep, int *linep)
3243 if (!allow_macro)
3244 return 0;
3245 if (mac.filename == 0)
3246 return 0;
3247 *filep = mac.filename;
3248 *linep = mac.lineno + lineno - 1;
3249 return 1;
3252 void string_iterator::backtrace()
3254 if (mac.filename) {
3255 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3256 if (how_invoked) {
3257 if (!nm.is_null())
3258 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3259 else
3260 errprint(": %1\n", how_invoked);
3262 else
3263 errprint("\n");
3267 class temp_iterator : public input_iterator {
3268 unsigned char *base;
3269 temp_iterator(const char *, int len);
3270 public:
3271 ~temp_iterator();
3272 friend input_iterator *make_temp_iterator(const char *);
3275 #ifdef __GNUG__
3276 inline
3277 #endif
3278 temp_iterator::temp_iterator(const char *s, int len)
3280 base = new unsigned char[len];
3281 memcpy(base, s, len);
3282 ptr = base;
3283 eptr = base + len;
3286 temp_iterator::~temp_iterator()
3288 a_delete base;
3291 class small_temp_iterator : public input_iterator {
3292 private:
3293 small_temp_iterator(const char *, int);
3294 ~small_temp_iterator();
3295 enum { BLOCK = 16 };
3296 static small_temp_iterator *free_list;
3297 void *operator new(size_t);
3298 void operator delete(void *);
3299 enum { SIZE = 12 };
3300 unsigned char buf[SIZE];
3301 friend input_iterator *make_temp_iterator(const char *);
3304 small_temp_iterator *small_temp_iterator::free_list = 0;
3306 void *small_temp_iterator::operator new(size_t n)
3308 assert(n == sizeof(small_temp_iterator));
3309 if (!free_list) {
3310 free_list =
3311 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3312 for (int i = 0; i < BLOCK - 1; i++)
3313 free_list[i].next = free_list + i + 1;
3314 free_list[BLOCK-1].next = 0;
3316 small_temp_iterator *p = free_list;
3317 free_list = (small_temp_iterator *)(free_list->next);
3318 p->next = 0;
3319 return p;
3322 #ifdef __GNUG__
3323 inline
3324 #endif
3325 void small_temp_iterator::operator delete(void *p)
3327 if (p) {
3328 ((small_temp_iterator *)p)->next = free_list;
3329 free_list = (small_temp_iterator *)p;
3333 small_temp_iterator::~small_temp_iterator()
3337 #ifdef __GNUG__
3338 inline
3339 #endif
3340 small_temp_iterator::small_temp_iterator(const char *s, int len)
3342 for (int i = 0; i < len; i++)
3343 buf[i] = s[i];
3344 ptr = buf;
3345 eptr = buf + len;
3348 input_iterator *make_temp_iterator(const char *s)
3350 if (s == 0)
3351 return new small_temp_iterator(s, 0);
3352 else {
3353 int n = strlen(s);
3354 if (n <= small_temp_iterator::SIZE)
3355 return new small_temp_iterator(s, n);
3356 else
3357 return new temp_iterator(s, n);
3361 // this is used when macros with arguments are interpolated
3363 struct arg_list {
3364 macro mac;
3365 arg_list *next;
3366 arg_list(const macro &);
3367 ~arg_list();
3370 arg_list::arg_list(const macro &m) : mac(m), next(0)
3374 arg_list::~arg_list()
3378 class macro_iterator : public string_iterator {
3379 arg_list *args;
3380 int argc;
3381 public:
3382 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
3383 macro_iterator();
3384 ~macro_iterator();
3385 int has_args() { return 1; }
3386 input_iterator *get_arg(int i);
3387 int nargs() { return argc; }
3388 void add_arg(const macro &m);
3389 void shift(int n);
3390 int is_macro() { return 1; }
3393 input_iterator *macro_iterator::get_arg(int i)
3395 if (i == 0)
3396 return make_temp_iterator(nm.contents());
3397 if (i > 0 && i <= argc) {
3398 arg_list *p = args;
3399 for (int j = 1; j < i; j++) {
3400 assert(p != 0);
3401 p = p->next;
3403 return new string_iterator(p->mac);
3405 else
3406 return 0;
3409 void macro_iterator::add_arg(const macro &m)
3411 arg_list **p;
3412 for (p = &args; *p; p = &((*p)->next))
3414 *p = new arg_list(m);
3415 ++argc;
3418 void macro_iterator::shift(int n)
3420 while (n > 0 && argc > 0) {
3421 arg_list *tem = args;
3422 args = args->next;
3423 delete tem;
3424 --argc;
3425 --n;
3429 // This gets used by eg .if '\?xxx\?''.
3431 int operator==(const macro &m1, const macro &m2)
3433 if (m1.len != m2.len)
3434 return 0;
3435 string_iterator iter1(m1);
3436 string_iterator iter2(m2);
3437 int n = m1.len;
3438 while (--n >= 0) {
3439 node *nd1 = 0;
3440 int c1 = iter1.get(&nd1);
3441 assert(c1 != EOF);
3442 node *nd2 = 0;
3443 int c2 = iter2.get(&nd2);
3444 assert(c2 != EOF);
3445 if (c1 != c2) {
3446 if (c1 == 0)
3447 delete nd1;
3448 else if (c2 == 0)
3449 delete nd2;
3450 return 0;
3452 if (c1 == 0) {
3453 assert(nd1 != 0);
3454 assert(nd2 != 0);
3455 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3456 delete nd1;
3457 delete nd2;
3458 if (!are_same)
3459 return 0;
3462 return 1;
3465 static void interpolate_macro(symbol nm)
3467 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3468 if (p == 0) {
3469 int warned = 0;
3470 const char *s = nm.contents();
3471 if (strlen(s) > 2) {
3472 request_or_macro *r;
3473 char buf[3];
3474 buf[0] = s[0];
3475 buf[1] = s[1];
3476 buf[2] = '\0';
3477 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3478 if (r) {
3479 macro *m = r->to_macro();
3480 if (!m || !m->empty())
3481 warned = warning(WARN_SPACE,
3482 "macro `%1' not defined "
3483 "(probably missing space after `%2')",
3484 nm.contents(), buf);
3487 if (!warned) {
3488 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
3489 p = new macro;
3490 request_dictionary.define(nm, p);
3493 if (p)
3494 p->invoke(nm);
3495 else {
3496 skip_line();
3497 return;
3501 static void decode_args(macro_iterator *mi)
3503 if (!tok.newline() && !tok.eof()) {
3504 node *n;
3505 int c = get_copy(&n);
3506 for (;;) {
3507 while (c == ' ')
3508 c = get_copy(&n);
3509 if (c == '\n' || c == EOF)
3510 break;
3511 macro arg;
3512 int quote_input_level = 0;
3513 int done_tab_warning = 0;
3514 if (c == '\"') {
3515 quote_input_level = input_stack::get_level();
3516 c = get_copy(&n);
3518 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3519 if (quote_input_level > 0 && c == '\"'
3520 && (compatible_flag
3521 || input_stack::get_level() == quote_input_level)) {
3522 c = get_copy(&n);
3523 if (c == '"') {
3524 arg.append(c);
3525 c = get_copy(&n);
3527 else
3528 break;
3530 else {
3531 if (c == 0)
3532 arg.append(n);
3533 else {
3534 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3535 warning(WARN_TAB, "tab character in unquoted macro argument");
3536 done_tab_warning = 1;
3538 arg.append(c);
3540 c = get_copy(&n);
3543 mi->add_arg(arg);
3548 static void decode_string_args(macro_iterator *mi)
3550 node *n;
3551 int c = get_copy(&n);
3552 for (;;) {
3553 while (c == ' ')
3554 c = get_copy(&n);
3555 if (c == '\n' || c == EOF) {
3556 error("missing `]'");
3557 break;
3559 if (c == ']')
3560 break;
3561 macro arg;
3562 int quote_input_level = 0;
3563 int done_tab_warning = 0;
3564 if (c == '\"') {
3565 quote_input_level = input_stack::get_level();
3566 c = get_copy(&n);
3568 while (c != EOF && c != '\n'
3569 && !(c == ']' && quote_input_level == 0)
3570 && !(c == ' ' && quote_input_level == 0)) {
3571 if (quote_input_level > 0 && c == '\"'
3572 && input_stack::get_level() == quote_input_level) {
3573 c = get_copy(&n);
3574 if (c == '"') {
3575 arg.append(c);
3576 c = get_copy(&n);
3578 else
3579 break;
3581 else {
3582 if (c == 0)
3583 arg.append(n);
3584 else {
3585 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3586 warning(WARN_TAB, "tab character in unquoted string argument");
3587 done_tab_warning = 1;
3589 arg.append(c);
3591 c = get_copy(&n);
3594 mi->add_arg(arg);
3598 void macro::invoke(symbol nm)
3600 macro_iterator *mi = new macro_iterator(nm, *this);
3601 decode_args(mi);
3602 input_stack::push(mi);
3603 tok.next();
3606 macro *macro::to_macro()
3608 return this;
3611 int macro::empty()
3613 return empty_macro == 1;
3616 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
3617 : string_iterator(m, how_invoked, s), args(0), argc(0)
3621 macro_iterator::macro_iterator() : args(0), argc(0)
3625 macro_iterator::~macro_iterator()
3627 while (args != 0) {
3628 arg_list *tem = args;
3629 args = args->next;
3630 delete tem;
3634 dictionary composite_dictionary(17);
3636 void composite_request()
3638 symbol from = get_name(1);
3639 if (!from.is_null()) {
3640 const char *from_gn = glyph_name_to_unicode(from.contents());
3641 if (!from_gn) {
3642 from_gn = check_unicode_name(from.contents());
3643 if (!from_gn) {
3644 error("invalid composite glyph name `%1'", from.contents());
3645 skip_line();
3646 return;
3649 const char *from_decomposed = decompose_unicode(from_gn);
3650 if (from_decomposed)
3651 from_gn = &from_decomposed[1];
3652 symbol to = get_name(1);
3653 if (to.is_null())
3654 composite_dictionary.remove(symbol(from_gn));
3655 else {
3656 const char *to_gn = glyph_name_to_unicode(to.contents());
3657 if (!to_gn) {
3658 to_gn = check_unicode_name(to.contents());
3659 if (!to_gn) {
3660 error("invalid composite glyph name `%1'", to.contents());
3661 skip_line();
3662 return;
3665 const char *to_decomposed = decompose_unicode(to_gn);
3666 if (to_decomposed)
3667 to_gn = &to_decomposed[1];
3668 if (strcmp(from_gn, to_gn) == 0)
3669 composite_dictionary.remove(symbol(from_gn));
3670 else
3671 (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
3674 skip_line();
3677 static symbol composite_glyph_name(symbol nm)
3679 macro_iterator *mi = new macro_iterator();
3680 decode_string_args(mi);
3681 input_stack::push(mi);
3682 const char *gn = glyph_name_to_unicode(nm.contents());
3683 if (!gn) {
3684 gn = check_unicode_name(nm.contents());
3685 if (!gn) {
3686 error("invalid base glyph `%1' in composite glyph name", nm.contents());
3687 return EMPTY_SYMBOL;
3690 const char *gn_decomposed = decompose_unicode(gn);
3691 string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
3692 string gl;
3693 int n = input_stack::nargs();
3694 for (int i = 1; i <= n; i++) {
3695 glyph_name += '_';
3696 input_iterator *p = input_stack::get_arg(i);
3697 gl.clear();
3698 int c;
3699 while ((c = p->get(0)) != EOF)
3700 gl += c;
3701 gl += '\0';
3702 const char *u = glyph_name_to_unicode(gl.contents());
3703 if (!u) {
3704 u = check_unicode_name(gl.contents());
3705 if (!u) {
3706 error("invalid component `%1' in composite glyph name",
3707 gl.contents());
3708 return EMPTY_SYMBOL;
3711 const char *decomposed = decompose_unicode(u);
3712 if (decomposed)
3713 u = &decomposed[1];
3714 void *mapped_composite = composite_dictionary.lookup(symbol(u));
3715 if (mapped_composite)
3716 u = (const char *)mapped_composite;
3717 glyph_name += u;
3719 glyph_name += '\0';
3720 const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
3721 if (groff_gn)
3722 return symbol(groff_gn);
3723 gl.clear();
3724 gl += 'u';
3725 gl += glyph_name;
3726 return symbol(gl.contents());
3729 int trap_sprung_flag = 0;
3730 int postpone_traps_flag = 0;
3731 symbol postponed_trap;
3733 void spring_trap(symbol nm)
3735 assert(!nm.is_null());
3736 trap_sprung_flag = 1;
3737 if (postpone_traps_flag) {
3738 postponed_trap = nm;
3739 return;
3741 static char buf[2] = { BEGIN_TRAP, 0 };
3742 static char buf2[2] = { END_TRAP, '\0' };
3743 input_stack::push(make_temp_iterator(buf2));
3744 request_or_macro *p = lookup_request(nm);
3745 macro *m = p->to_macro();
3746 if (m)
3747 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
3748 else
3749 error("you can't invoke a request with a trap");
3750 input_stack::push(make_temp_iterator(buf));
3753 void postpone_traps()
3755 postpone_traps_flag = 1;
3758 int unpostpone_traps()
3760 postpone_traps_flag = 0;
3761 if (!postponed_trap.is_null()) {
3762 spring_trap(postponed_trap);
3763 postponed_trap = NULL_SYMBOL;
3764 return 1;
3766 else
3767 return 0;
3770 void read_request()
3772 macro_iterator *mi = new macro_iterator;
3773 int reading_from_terminal = isatty(fileno(stdin));
3774 int had_prompt = 0;
3775 if (!tok.newline() && !tok.eof()) {
3776 int c = get_copy(0);
3777 while (c == ' ')
3778 c = get_copy(0);
3779 while (c != EOF && c != '\n' && c != ' ') {
3780 if (!invalid_input_char(c)) {
3781 if (reading_from_terminal)
3782 fputc(c, stderr);
3783 had_prompt = 1;
3785 c = get_copy(0);
3787 if (c == ' ') {
3788 tok.make_space();
3789 decode_args(mi);
3792 if (reading_from_terminal) {
3793 fputc(had_prompt ? ':' : '\a', stderr);
3794 fflush(stderr);
3796 input_stack::push(mi);
3797 macro mac;
3798 int nl = 0;
3799 int c;
3800 while ((c = getchar()) != EOF) {
3801 if (invalid_input_char(c))
3802 warning(WARN_INPUT, "invalid input character code %1", int(c));
3803 else {
3804 if (c == '\n') {
3805 if (nl)
3806 break;
3807 else
3808 nl = 1;
3810 else
3811 nl = 0;
3812 mac.append(c);
3815 if (reading_from_terminal)
3816 clearerr(stdin);
3817 input_stack::push(new string_iterator(mac));
3818 tok.next();
3821 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3822 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT, CALLING_DISABLE_COMP };
3824 void do_define_string(define_mode mode, calling_mode calling)
3826 symbol nm;
3827 node *n;
3828 int c;
3829 nm = get_name(1);
3830 if (nm.is_null()) {
3831 skip_line();
3832 return;
3834 if (tok.newline())
3835 c = '\n';
3836 else if (tok.tab())
3837 c = '\t';
3838 else if (!tok.space()) {
3839 error("bad string definition");
3840 skip_line();
3841 return;
3843 else
3844 c = get_copy(&n);
3845 while (c == ' ')
3846 c = get_copy(&n);
3847 if (c == '"')
3848 c = get_copy(&n);
3849 macro mac;
3850 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3851 macro *mm = rm ? rm->to_macro() : 0;
3852 if (mode == DEFINE_APPEND && mm)
3853 mac = *mm;
3854 if (calling == CALLING_DISABLE_COMP)
3855 mac.append(COMPATIBLE_SAVE);
3856 while (c != '\n' && c != EOF) {
3857 if (c == 0)
3858 mac.append(n);
3859 else
3860 mac.append((unsigned char)c);
3861 c = get_copy(&n);
3863 if (!mm) {
3864 mm = new macro;
3865 request_dictionary.define(nm, mm);
3867 if (calling == CALLING_DISABLE_COMP)
3868 mac.append(COMPATIBLE_RESTORE);
3869 *mm = mac;
3870 tok.next();
3873 void define_string()
3875 do_define_string(DEFINE_NORMAL, CALLING_NORMAL);
3878 void define_nocomp_string()
3880 do_define_string(DEFINE_NORMAL, CALLING_DISABLE_COMP);
3883 void append_string()
3885 do_define_string(DEFINE_APPEND, CALLING_NORMAL);
3888 void append_nocomp_string()
3890 do_define_string(DEFINE_APPEND, CALLING_DISABLE_COMP);
3893 void do_define_character(char_mode mode, const char *font_name)
3895 node *n;
3896 int c;
3897 tok.skip();
3898 charinfo *ci = tok.get_char(1);
3899 if (ci == 0) {
3900 skip_line();
3901 return;
3903 if (font_name) {
3904 string s(font_name);
3905 s += ' ';
3906 s += ci->nm.contents();
3907 s += '\0';
3908 ci = get_charinfo(symbol(s.contents()));
3910 tok.next();
3911 if (tok.newline())
3912 c = '\n';
3913 else if (tok.tab())
3914 c = '\t';
3915 else if (!tok.space()) {
3916 error("bad character definition");
3917 skip_line();
3918 return;
3920 else
3921 c = get_copy(&n);
3922 while (c == ' ' || c == '\t')
3923 c = get_copy(&n);
3924 if (c == '"')
3925 c = get_copy(&n);
3926 macro *m = new macro;
3927 while (c != '\n' && c != EOF) {
3928 if (c == 0)
3929 m->append(n);
3930 else
3931 m->append((unsigned char)c);
3932 c = get_copy(&n);
3934 m = ci->setx_macro(m, mode);
3935 if (m)
3936 delete m;
3937 tok.next();
3940 void define_character()
3942 do_define_character(CHAR_NORMAL);
3945 void define_fallback_character()
3947 do_define_character(CHAR_FALLBACK);
3950 void define_special_character()
3952 do_define_character(CHAR_SPECIAL);
3955 static void remove_character()
3957 tok.skip();
3958 while (!tok.newline() && !tok.eof()) {
3959 if (!tok.space() && !tok.tab()) {
3960 charinfo *ci = tok.get_char(1);
3961 if (!ci)
3962 break;
3963 macro *m = ci->set_macro(0);
3964 if (m)
3965 delete m;
3967 tok.next();
3969 skip_line();
3972 static void interpolate_string(symbol nm)
3974 request_or_macro *p = lookup_request(nm);
3975 macro *m = p->to_macro();
3976 if (!m)
3977 error("you can only invoke a string or macro using \\*");
3978 else {
3979 string_iterator *si = new string_iterator(*m, "string", nm);
3980 input_stack::push(si);
3984 static void interpolate_string_with_args(symbol s)
3986 request_or_macro *p = lookup_request(s);
3987 macro *m = p->to_macro();
3988 if (!m)
3989 error("you can only invoke a string or macro using \\*");
3990 else {
3991 macro_iterator *mi = new macro_iterator(s, *m);
3992 decode_string_args(mi);
3993 input_stack::push(mi);
3997 /* This class is used for the implementation of \$@. It is used for
3998 each of the closing double quotes. It artificially increases the
3999 input level by 2, so that the closing double quote will appear to have
4000 the same input level as the opening quote. */
4002 class end_quote_iterator : public input_iterator {
4003 unsigned char buf[1];
4004 public:
4005 end_quote_iterator();
4006 ~end_quote_iterator() { }
4007 int internal_level() { return 2; }
4010 end_quote_iterator::end_quote_iterator()
4012 buf[0] = '"';
4013 ptr = buf;
4014 eptr = buf + 1;
4017 static void interpolate_arg(symbol nm)
4019 const char *s = nm.contents();
4020 if (!s || *s == '\0')
4021 copy_mode_error("missing argument name");
4022 else if (s[1] == 0 && csdigit(s[0]))
4023 input_stack::push(input_stack::get_arg(s[0] - '0'));
4024 else if (s[0] == '*' && s[1] == '\0') {
4025 for (int i = input_stack::nargs(); i > 0; i--) {
4026 input_stack::push(input_stack::get_arg(i));
4027 if (i != 1)
4028 input_stack::push(make_temp_iterator(" "));
4031 else if (s[0] == '@' && s[1] == '\0') {
4032 for (int i = input_stack::nargs(); i > 0; i--) {
4033 input_stack::push(new end_quote_iterator);
4034 input_stack::push(input_stack::get_arg(i));
4035 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
4038 else {
4039 const char *p;
4040 for (p = s; *p && csdigit(*p); p++)
4042 if (*p)
4043 copy_mode_error("bad argument name `%1'", s);
4044 else
4045 input_stack::push(input_stack::get_arg(atoi(s)));
4049 void handle_first_page_transition()
4051 push_token(tok);
4052 topdiv->begin_page();
4055 // We push back a token by wrapping it up in a token_node, and
4056 // wrapping that up in a string_iterator.
4058 static void push_token(const token &t)
4060 macro m;
4061 m.append(new token_node(t));
4062 input_stack::push(new string_iterator(m));
4065 void push_page_ejector()
4067 static char buf[2] = { PAGE_EJECTOR, '\0' };
4068 input_stack::push(make_temp_iterator(buf));
4071 void handle_initial_request(unsigned char code)
4073 char buf[2];
4074 buf[0] = code;
4075 buf[1] = '\0';
4076 macro mac;
4077 mac.append(new token_node(tok));
4078 input_stack::push(new string_iterator(mac));
4079 input_stack::push(make_temp_iterator(buf));
4080 topdiv->begin_page();
4081 tok.next();
4084 void handle_initial_title()
4086 handle_initial_request(TITLE_REQUEST);
4089 // this should be local to define_macro, but cfront 1.2 doesn't support that
4090 static symbol dot_symbol(".");
4092 void do_define_macro(define_mode mode, calling_mode calling)
4094 symbol nm, term;
4095 if (calling == CALLING_INDIRECT) {
4096 symbol temp1 = get_name(1);
4097 if (temp1.is_null()) {
4098 skip_line();
4099 return;
4101 symbol temp2 = get_name();
4102 input_stack::push(make_temp_iterator("\n"));
4103 if (!temp2.is_null()) {
4104 interpolate_string(temp2);
4105 input_stack::push(make_temp_iterator(" "));
4107 interpolate_string(temp1);
4108 input_stack::push(make_temp_iterator(" "));
4109 tok.next();
4111 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4112 nm = get_name(1);
4113 if (nm.is_null()) {
4114 skip_line();
4115 return;
4118 term = get_name(); // the request that terminates the definition
4119 if (term.is_null())
4120 term = dot_symbol;
4121 while (!tok.newline() && !tok.eof())
4122 tok.next();
4123 const char *start_filename;
4124 int start_lineno;
4125 int have_start_location = input_stack::get_location(0, &start_filename,
4126 &start_lineno);
4127 node *n;
4128 // doing this here makes the line numbers come out right
4129 int c = get_copy(&n, 1);
4130 macro mac;
4131 macro *mm = 0;
4132 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4133 request_or_macro *rm =
4134 (request_or_macro *)request_dictionary.lookup(nm);
4135 if (rm)
4136 mm = rm->to_macro();
4137 if (mm && mode == DEFINE_APPEND)
4138 mac = *mm;
4140 int bol = 1;
4141 if (calling == CALLING_DISABLE_COMP)
4142 mac.append(COMPATIBLE_SAVE);
4143 for (;;) {
4144 while (c == ESCAPE_NEWLINE) {
4145 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
4146 mac.append(c);
4147 c = get_copy(&n, 1);
4149 if (bol && c == '.') {
4150 const char *s = term.contents();
4151 int d = 0;
4152 // see if it matches term
4153 int i = 0;
4154 if (s[0] != 0) {
4155 while ((d = get_copy(&n)) == ' ' || d == '\t')
4157 if ((unsigned char)s[0] == d) {
4158 for (i = 1; s[i] != 0; i++) {
4159 d = get_copy(&n);
4160 if ((unsigned char)s[i] != d)
4161 break;
4165 if (s[i] == 0
4166 && ((i == 2 && compatible_flag)
4167 || (d = get_copy(&n)) == ' '
4168 || d == '\n')) { // we found it
4169 if (d == '\n')
4170 tok.make_newline();
4171 else
4172 tok.make_space();
4173 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4174 if (!mm) {
4175 mm = new macro;
4176 request_dictionary.define(nm, mm);
4178 if (calling == CALLING_DISABLE_COMP)
4179 mac.append(COMPATIBLE_RESTORE);
4180 *mm = mac;
4182 if (term != dot_symbol) {
4183 ignoring = 0;
4184 interpolate_macro(term);
4186 else
4187 skip_line();
4188 return;
4190 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4191 mac.append(c);
4192 for (int j = 0; j < i; j++)
4193 mac.append(s[j]);
4195 c = d;
4197 if (c == EOF) {
4198 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4199 if (have_start_location)
4200 error_with_file_and_line(start_filename, start_lineno,
4201 "end of file while defining macro `%1'",
4202 nm.contents());
4203 else
4204 error("end of file while defining macro `%1'", nm.contents());
4206 else {
4207 if (have_start_location)
4208 error_with_file_and_line(start_filename, start_lineno,
4209 "end of file while ignoring input lines");
4210 else
4211 error("end of file while ignoring input lines");
4213 tok.next();
4214 return;
4216 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4217 if (c == 0)
4218 mac.append(n);
4219 else
4220 mac.append(c);
4222 bol = (c == '\n');
4223 c = get_copy(&n, 1);
4227 void define_macro()
4229 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL);
4232 void define_nocomp_macro()
4234 do_define_macro(DEFINE_NORMAL, CALLING_DISABLE_COMP);
4237 void define_indirect_macro()
4239 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT);
4242 void append_macro()
4244 do_define_macro(DEFINE_APPEND, CALLING_NORMAL);
4247 void append_indirect_macro()
4249 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT);
4252 void append_nocomp_macro()
4254 do_define_macro(DEFINE_APPEND, CALLING_DISABLE_COMP);
4257 void ignore()
4259 ignoring = 1;
4260 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL);
4261 ignoring = 0;
4264 void remove_macro()
4266 for (;;) {
4267 symbol s = get_name();
4268 if (s.is_null())
4269 break;
4270 request_dictionary.remove(s);
4272 skip_line();
4275 void rename_macro()
4277 symbol s1 = get_name(1);
4278 if (!s1.is_null()) {
4279 symbol s2 = get_name(1);
4280 if (!s2.is_null())
4281 request_dictionary.rename(s1, s2);
4283 skip_line();
4286 void alias_macro()
4288 symbol s1 = get_name(1);
4289 if (!s1.is_null()) {
4290 symbol s2 = get_name(1);
4291 if (!s2.is_null()) {
4292 if (!request_dictionary.alias(s1, s2))
4293 warning(WARN_MAC, "macro `%1' not defined", s2.contents());
4296 skip_line();
4299 void chop_macro()
4301 symbol s = get_name(1);
4302 if (!s.is_null()) {
4303 request_or_macro *p = lookup_request(s);
4304 macro *m = p->to_macro();
4305 if (!m)
4306 error("cannot chop request");
4307 else if (m->empty())
4308 error("cannot chop empty macro");
4309 else {
4310 int have_restore = 0;
4311 // we have to check for additional save/restore pairs which could be
4312 // there due to empty am1 requests.
4313 for (;;) {
4314 if (m->get(m->len - 1) != COMPATIBLE_RESTORE)
4315 break;
4316 have_restore = 1;
4317 m->len -= 1;
4318 if (m->get(m->len - 1) != COMPATIBLE_SAVE)
4319 break;
4320 have_restore = 0;
4321 m->len -= 1;
4322 if (m->len == 0)
4323 break;
4325 if (m->len == 0)
4326 error("cannot chop empty macro");
4327 else {
4328 if (have_restore)
4329 m->set(COMPATIBLE_RESTORE, m->len - 1);
4330 else
4331 m->len -= 1;
4335 skip_line();
4338 void substring_request()
4340 int start; // 0, 1, ..., n-1 or -1, -2, ...
4341 symbol s = get_name(1);
4342 if (!s.is_null() && get_integer(&start)) {
4343 request_or_macro *p = lookup_request(s);
4344 macro *m = p->to_macro();
4345 if (!m)
4346 error("cannot apply `substring' on a request");
4347 else {
4348 int end = -1;
4349 if (!has_arg() || get_integer(&end)) {
4350 int real_length = 0; // 1, 2, ..., n
4351 string_iterator iter1(*m);
4352 for (int l = 0; l < m->len; l++) {
4353 int c = iter1.get(0);
4354 if (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4355 continue;
4356 if (c == EOF)
4357 break;
4358 real_length++;
4360 if (start < 0)
4361 start += real_length;
4362 if (end < 0)
4363 end += real_length;
4364 if (start > end) {
4365 int tem = start;
4366 start = end;
4367 end = tem;
4369 if (start >= real_length || end < 0) {
4370 warning(WARN_RANGE,
4371 "start and end index of substring out of range");
4372 m->len = 0;
4373 if (m->p) {
4374 if (--(m->p->count) <= 0)
4375 delete m->p;
4376 m->p = 0;
4378 skip_line();
4379 return;
4381 if (start < 0) {
4382 warning(WARN_RANGE,
4383 "start index of substring out of range, set to 0");
4384 start = 0;
4386 if (end >= real_length) {
4387 warning(WARN_RANGE,
4388 "end index of substring out of range, set to string length");
4389 end = real_length - 1;
4391 // now extract the substring
4392 string_iterator iter(*m);
4393 int i;
4394 for (i = 0; i < start; i++) {
4395 int c = iter.get(0);
4396 while (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4397 c = iter.get(0);
4398 if (c == EOF)
4399 break;
4401 macro mac;
4402 for (; i <= end; i++) {
4403 node *nd;
4404 int c = iter.get(&nd);
4405 while (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4406 c = iter.get(0);
4407 if (c == EOF)
4408 break;
4409 if (c == 0)
4410 mac.append(nd);
4411 else
4412 mac.append((unsigned char)c);
4414 *m = mac;
4418 skip_line();
4421 void length_request()
4423 symbol ret;
4424 ret = get_name(1);
4425 if (ret.is_null()) {
4426 skip_line();
4427 return;
4429 int c;
4430 node *n;
4431 if (tok.newline())
4432 c = '\n';
4433 else if (tok.tab())
4434 c = '\t';
4435 else if (!tok.space()) {
4436 error("bad string definition");
4437 skip_line();
4438 return;
4440 else
4441 c = get_copy(&n);
4442 while (c == ' ')
4443 c = get_copy(&n);
4444 if (c == '"')
4445 c = get_copy(&n);
4446 int len = 0;
4447 while (c != '\n' && c != EOF) {
4448 ++len;
4449 c = get_copy(&n);
4451 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4452 if (r)
4453 r->set_value(len);
4454 else
4455 set_number_reg(ret, len);
4456 tok.next();
4459 void asciify_macro()
4461 symbol s = get_name(1);
4462 if (!s.is_null()) {
4463 request_or_macro *p = lookup_request(s);
4464 macro *m = p->to_macro();
4465 if (!m)
4466 error("cannot asciify request");
4467 else {
4468 macro am;
4469 string_iterator iter(*m);
4470 for (;;) {
4471 node *nd;
4472 int c = iter.get(&nd);
4473 if (c == EOF)
4474 break;
4475 if (c != 0)
4476 am.append(c);
4477 else
4478 nd->asciify(&am);
4480 *m = am;
4483 skip_line();
4486 void unformat_macro()
4488 symbol s = get_name(1);
4489 if (!s.is_null()) {
4490 request_or_macro *p = lookup_request(s);
4491 macro *m = p->to_macro();
4492 if (!m)
4493 error("cannot unformat request");
4494 else {
4495 macro am;
4496 string_iterator iter(*m);
4497 for (;;) {
4498 node *nd;
4499 int c = iter.get(&nd);
4500 if (c == EOF)
4501 break;
4502 if (c != 0)
4503 am.append(c);
4504 else {
4505 if (nd->set_unformat_flag())
4506 am.append(nd);
4509 *m = am;
4512 skip_line();
4515 static void interpolate_environment_variable(symbol nm)
4517 const char *s = getenv(nm.contents());
4518 if (s && *s)
4519 input_stack::push(make_temp_iterator(s));
4522 void interpolate_number_reg(symbol nm, int inc)
4524 reg *r = lookup_number_reg(nm);
4525 if (inc < 0)
4526 r->decrement();
4527 else if (inc > 0)
4528 r->increment();
4529 input_stack::push(make_temp_iterator(r->get_string()));
4532 static void interpolate_number_format(symbol nm)
4534 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4535 if (r)
4536 input_stack::push(make_temp_iterator(r->get_format()));
4539 static int get_delim_number(units *n, int si, int prev_value)
4541 token start;
4542 start.next();
4543 if (start.delimiter(1)) {
4544 tok.next();
4545 if (get_number(n, si, prev_value)) {
4546 if (start != tok)
4547 warning(WARN_DELIM, "closing delimiter does not match");
4548 return 1;
4551 return 0;
4554 static int get_delim_number(units *n, int si)
4556 token start;
4557 start.next();
4558 if (start.delimiter(1)) {
4559 tok.next();
4560 if (get_number(n, si)) {
4561 if (start != tok)
4562 warning(WARN_DELIM, "closing delimiter does not match");
4563 return 1;
4566 return 0;
4569 static int get_line_arg(units *n, int si, charinfo **cp)
4571 token start;
4572 start.next();
4573 int start_level = input_stack::get_level();
4574 if (!start.delimiter(1))
4575 return 0;
4576 tok.next();
4577 if (get_number(n, si)) {
4578 if (tok.dummy() || tok.transparent_dummy())
4579 tok.next();
4580 if (!(start == tok && input_stack::get_level() == start_level)) {
4581 *cp = tok.get_char(1);
4582 tok.next();
4584 if (!(start == tok && input_stack::get_level() == start_level))
4585 warning(WARN_DELIM, "closing delimiter does not match");
4586 return 1;
4588 return 0;
4591 static int read_size(int *x)
4593 tok.next();
4594 int c = tok.ch();
4595 int inc = 0;
4596 if (c == '-') {
4597 inc = -1;
4598 tok.next();
4599 c = tok.ch();
4601 else if (c == '+') {
4602 inc = 1;
4603 tok.next();
4604 c = tok.ch();
4606 int val;
4607 int bad = 0;
4608 if (c == '(') {
4609 tok.next();
4610 c = tok.ch();
4611 if (!inc) {
4612 // allow an increment either before or after the left parenthesis
4613 if (c == '-') {
4614 inc = -1;
4615 tok.next();
4616 c = tok.ch();
4618 else if (c == '+') {
4619 inc = 1;
4620 tok.next();
4621 c = tok.ch();
4624 if (!csdigit(c))
4625 bad = 1;
4626 else {
4627 val = c - '0';
4628 tok.next();
4629 c = tok.ch();
4630 if (!csdigit(c))
4631 bad = 1;
4632 else {
4633 val = val*10 + (c - '0');
4634 val *= sizescale;
4638 else if (csdigit(c)) {
4639 val = c - '0';
4640 if (!inc && c != '0' && c < '4') {
4641 tok.next();
4642 c = tok.ch();
4643 if (!csdigit(c))
4644 bad = 1;
4645 else
4646 val = val*10 + (c - '0');
4648 val *= sizescale;
4650 else if (!tok.delimiter(1))
4651 return 0;
4652 else {
4653 token start(tok);
4654 tok.next();
4655 if (!(inc
4656 ? get_number(&val, 'z')
4657 : get_number(&val, 'z', curenv->get_requested_point_size())))
4658 return 0;
4659 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
4660 if (start.ch() == '[')
4661 error("missing `]'");
4662 else
4663 error("missing closing delimiter");
4664 return 0;
4667 if (!bad) {
4668 switch (inc) {
4669 case 0:
4670 if (val == 0) {
4671 // special case -- \s[0] and \s0 means to revert to previous size
4672 *x = 0;
4673 return 1;
4675 *x = val;
4676 break;
4677 case 1:
4678 *x = curenv->get_requested_point_size() + val;
4679 break;
4680 case -1:
4681 *x = curenv->get_requested_point_size() - val;
4682 break;
4683 default:
4684 assert(0);
4686 if (*x <= 0) {
4687 warning(WARN_RANGE,
4688 "\\s request results in non-positive point size; set to 1");
4689 *x = 1;
4691 return 1;
4693 else {
4694 error("bad digit in point size");
4695 return 0;
4699 static symbol get_delim_name()
4701 token start;
4702 start.next();
4703 if (start.eof()) {
4704 error("end of input at start of delimited name");
4705 return NULL_SYMBOL;
4707 if (start.newline()) {
4708 error("can't delimit name with a newline");
4709 return NULL_SYMBOL;
4711 int start_level = input_stack::get_level();
4712 char abuf[ABUF_SIZE];
4713 char *buf = abuf;
4714 int buf_size = ABUF_SIZE;
4715 int i = 0;
4716 for (;;) {
4717 if (i + 1 > buf_size) {
4718 if (buf == abuf) {
4719 buf = new char[ABUF_SIZE*2];
4720 memcpy(buf, abuf, buf_size);
4721 buf_size = ABUF_SIZE*2;
4723 else {
4724 char *old_buf = buf;
4725 buf = new char[buf_size*2];
4726 memcpy(buf, old_buf, buf_size);
4727 buf_size *= 2;
4728 a_delete old_buf;
4731 tok.next();
4732 if (tok == start
4733 && (compatible_flag || input_stack::get_level() == start_level))
4734 break;
4735 if ((buf[i] = tok.ch()) == 0) {
4736 error("missing delimiter (got %1)", tok.description());
4737 if (buf != abuf)
4738 a_delete buf;
4739 return NULL_SYMBOL;
4741 i++;
4743 buf[i] = '\0';
4744 if (buf == abuf) {
4745 if (i == 0) {
4746 error("empty delimited name");
4747 return NULL_SYMBOL;
4749 else
4750 return symbol(buf);
4752 else {
4753 symbol s(buf);
4754 a_delete buf;
4755 return s;
4759 // Implement \R
4761 static void do_register()
4763 token start;
4764 start.next();
4765 if (!start.delimiter(1))
4766 return;
4767 tok.next();
4768 symbol nm = get_long_name(1);
4769 if (nm.is_null())
4770 return;
4771 while (tok.space())
4772 tok.next();
4773 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4774 int prev_value;
4775 if (!r || !r->get_value(&prev_value))
4776 prev_value = 0;
4777 int val;
4778 if (!get_number(&val, 'u', prev_value))
4779 return;
4780 if (start != tok)
4781 warning(WARN_DELIM, "closing delimiter does not match");
4782 if (r)
4783 r->set_value(val);
4784 else
4785 set_number_reg(nm, val);
4788 // this implements the \w escape sequence
4790 static void do_width()
4792 token start;
4793 start.next();
4794 int start_level = input_stack::get_level();
4795 environment env(curenv);
4796 environment *oldenv = curenv;
4797 curenv = &env;
4798 for (;;) {
4799 tok.next();
4800 if (tok.eof()) {
4801 warning(WARN_DELIM, "missing closing delimiter");
4802 break;
4804 if (tok.newline()) {
4805 warning(WARN_DELIM, "missing closing delimiter");
4806 input_stack::push(make_temp_iterator("\n"));
4807 break;
4809 if (tok == start
4810 && (compatible_flag || input_stack::get_level() == start_level))
4811 break;
4812 tok.process();
4814 env.wrap_up_tab();
4815 units x = env.get_input_line_position().to_units();
4816 input_stack::push(make_temp_iterator(i_to_a(x)));
4817 env.width_registers();
4818 curenv = oldenv;
4819 have_input = 0;
4822 charinfo *page_character;
4824 void set_page_character()
4826 page_character = get_optional_char();
4827 skip_line();
4830 static const symbol percent_symbol("%");
4832 void read_title_parts(node **part, hunits *part_width)
4834 tok.skip();
4835 if (tok.newline() || tok.eof())
4836 return;
4837 token start(tok);
4838 int start_level = input_stack::get_level();
4839 tok.next();
4840 for (int i = 0; i < 3; i++) {
4841 while (!tok.newline() && !tok.eof()) {
4842 if (tok == start
4843 && (compatible_flag || input_stack::get_level() == start_level)) {
4844 tok.next();
4845 break;
4847 if (page_character != 0 && tok.get_char() == page_character)
4848 interpolate_number_reg(percent_symbol, 0);
4849 else
4850 tok.process();
4851 tok.next();
4853 curenv->wrap_up_tab();
4854 part_width[i] = curenv->get_input_line_position();
4855 part[i] = curenv->extract_output_line();
4857 while (!tok.newline() && !tok.eof())
4858 tok.next();
4861 class non_interpreted_node : public node {
4862 macro mac;
4863 public:
4864 non_interpreted_node(const macro &);
4865 int interpret(macro *);
4866 node *copy();
4867 int same(node *);
4868 const char *type();
4869 int force_tprint();
4872 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
4876 int non_interpreted_node::same(node *nd)
4878 return mac == ((non_interpreted_node *)nd)->mac;
4881 const char *non_interpreted_node::type()
4883 return "non_interpreted_node";
4886 int non_interpreted_node::force_tprint()
4888 return 0;
4891 node *non_interpreted_node::copy()
4893 return new non_interpreted_node(mac);
4896 int non_interpreted_node::interpret(macro *m)
4898 string_iterator si(mac);
4899 node *n;
4900 for (;;) {
4901 int c = si.get(&n);
4902 if (c == EOF)
4903 break;
4904 if (c == 0)
4905 m->append(n);
4906 else
4907 m->append(c);
4909 return 1;
4912 static node *do_non_interpreted()
4914 node *n;
4915 int c;
4916 macro mac;
4917 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
4918 if (c == 0)
4919 mac.append(n);
4920 else
4921 mac.append(c);
4922 if (c == EOF || c == '\n') {
4923 error("missing \\?");
4924 return 0;
4926 return new non_interpreted_node(mac);
4929 static void encode_char(macro *mac, char c)
4931 if (c == '\0') {
4932 if ((font::use_charnames_in_special) && tok.special()) {
4933 charinfo *ci = tok.get_char(1);
4934 const char *s = ci->get_symbol()->contents();
4935 if (s[0] != (char)0) {
4936 mac->append('\\');
4937 mac->append('(');
4938 int i = 0;
4939 while (s[i] != (char)0) {
4940 mac->append(s[i]);
4941 i++;
4943 mac->append('\\');
4944 mac->append(')');
4947 else if (tok.stretchable_space()
4948 || tok.unstretchable_space())
4949 mac->append(' ');
4950 else if (!(tok.hyphen_indicator()
4951 || tok.dummy()
4952 || tok.transparent_dummy()
4953 || tok.zero_width_break()))
4954 error("%1 is invalid within \\X", tok.description());
4956 else {
4957 if ((font::use_charnames_in_special) && (c == '\\')) {
4959 * add escape escape sequence
4961 mac->append(c);
4963 mac->append(c);
4967 node *do_special()
4969 token start;
4970 start.next();
4971 int start_level = input_stack::get_level();
4972 macro mac;
4973 for (tok.next();
4974 tok != start || input_stack::get_level() != start_level;
4975 tok.next()) {
4976 if (tok.eof()) {
4977 warning(WARN_DELIM, "missing closing delimiter");
4978 return 0;
4980 if (tok.newline()) {
4981 input_stack::push(make_temp_iterator("\n"));
4982 warning(WARN_DELIM, "missing closing delimiter");
4983 break;
4985 unsigned char c;
4986 if (tok.space())
4987 c = ' ';
4988 else if (tok.tab())
4989 c = '\t';
4990 else if (tok.leader())
4991 c = '\001';
4992 else if (tok.backspace())
4993 c = '\b';
4994 else
4995 c = tok.ch();
4996 encode_char(&mac, c);
4998 return new special_node(mac);
5001 void output_request()
5003 if (!tok.newline() && !tok.eof()) {
5004 int c;
5005 for (;;) {
5006 c = get_copy(0);
5007 if (c == '"') {
5008 c = get_copy(0);
5009 break;
5011 if (c != ' ' && c != '\t')
5012 break;
5014 for (; c != '\n' && c != EOF; c = get_copy(0))
5015 topdiv->transparent_output(c);
5016 topdiv->transparent_output('\n');
5018 tok.next();
5021 extern int image_no; // from node.cc
5023 static node *do_suppress(symbol nm)
5025 if (nm.is_null() || nm.is_empty()) {
5026 error("expecting an argument to escape \\O");
5027 return 0;
5029 const char *s = nm.contents();
5030 switch (*s) {
5031 case '0':
5032 if (begin_level == 0)
5033 // suppress generation of glyphs
5034 return new suppress_node(0, 0);
5035 break;
5036 case '1':
5037 if (begin_level == 0)
5038 // enable generation of glyphs
5039 return new suppress_node(1, 0);
5040 break;
5041 case '2':
5042 if (begin_level == 0)
5043 return new suppress_node(1, 1);
5044 break;
5045 case '3':
5046 begin_level++;
5047 break;
5048 case '4':
5049 begin_level--;
5050 break;
5051 case '5':
5053 s++; // move over '5'
5054 char position = *s;
5055 if (*s == (char)0) {
5056 error("missing position and filename in \\O");
5057 return 0;
5059 if (!(position == 'l'
5060 || position == 'r'
5061 || position == 'c'
5062 || position == 'i')) {
5063 error("l, r, c, or i position expected (got %1 in \\O)", position);
5064 return 0;
5066 s++; // onto image name
5067 if (s == (char *)0) {
5068 error("missing image name for \\O");
5069 return 0;
5071 image_no++;
5072 if (begin_level == 0)
5073 return new suppress_node(symbol(s), position, image_no);
5075 break;
5076 default:
5077 error("`%1' is an invalid argument to \\O", *s);
5079 return 0;
5082 void special_node::tprint(troff_output_file *out)
5084 tprint_start(out);
5085 string_iterator iter(mac);
5086 for (;;) {
5087 int c = iter.get(0);
5088 if (c == EOF)
5089 break;
5090 for (const char *s = ::asciify(c); *s; s++)
5091 tprint_char(out, *s);
5093 tprint_end(out);
5096 int get_file_line(const char **filename, int *lineno)
5098 return input_stack::get_location(0, filename, lineno);
5101 void line_file()
5103 int n;
5104 if (get_integer(&n)) {
5105 const char *filename = 0;
5106 if (has_arg()) {
5107 symbol s = get_long_name();
5108 filename = s.contents();
5110 (void)input_stack::set_location(filename, n-1);
5112 skip_line();
5115 static int nroff_mode = 0;
5117 static void nroff_request()
5119 nroff_mode = 1;
5120 skip_line();
5123 static void troff_request()
5125 nroff_mode = 0;
5126 skip_line();
5129 static void skip_alternative()
5131 int level = 0;
5132 // ensure that ``.if 0\{'' works as expected
5133 if (tok.left_brace())
5134 level++;
5135 int c;
5136 for (;;) {
5137 c = input_stack::get(0);
5138 if (c == EOF)
5139 break;
5140 if (c == ESCAPE_LEFT_BRACE)
5141 ++level;
5142 else if (c == ESCAPE_RIGHT_BRACE)
5143 --level;
5144 else if (c == escape_char && escape_char > 0)
5145 switch(input_stack::get(0)) {
5146 case '{':
5147 ++level;
5148 break;
5149 case '}':
5150 --level;
5151 break;
5152 case '"':
5153 while ((c = input_stack::get(0)) != '\n' && c != EOF)
5157 Note that the level can properly be < 0, eg
5159 .if 1 \{\
5160 .if 0 \{\
5161 .\}\}
5163 So don't give an error message in this case.
5165 if (level <= 0 && c == '\n')
5166 break;
5168 tok.next();
5171 static void begin_alternative()
5173 while (tok.space() || tok.left_brace())
5174 tok.next();
5177 void nop_request()
5179 while (tok.space())
5180 tok.next();
5183 static int_stack if_else_stack;
5185 int do_if_request()
5187 int invert = 0;
5188 while (tok.space())
5189 tok.next();
5190 while (tok.ch() == '!') {
5191 tok.next();
5192 invert = !invert;
5194 int result;
5195 unsigned char c = tok.ch();
5196 if (c == 't') {
5197 tok.next();
5198 result = !nroff_mode;
5200 else if (c == 'n') {
5201 tok.next();
5202 result = nroff_mode;
5204 else if (c == 'v') {
5205 tok.next();
5206 result = 0;
5208 else if (c == 'o') {
5209 result = (topdiv->get_page_number() & 1);
5210 tok.next();
5212 else if (c == 'e') {
5213 result = !(topdiv->get_page_number() & 1);
5214 tok.next();
5216 else if (c == 'd' || c == 'r') {
5217 tok.next();
5218 symbol nm = get_name(1);
5219 if (nm.is_null()) {
5220 skip_alternative();
5221 return 0;
5223 result = (c == 'd'
5224 ? request_dictionary.lookup(nm) != 0
5225 : number_reg_dictionary.lookup(nm) != 0);
5227 else if (c == 'm') {
5228 tok.next();
5229 symbol nm = get_long_name(1);
5230 if (nm.is_null()) {
5231 skip_alternative();
5232 return 0;
5234 result = (nm == default_symbol
5235 || color_dictionary.lookup(nm) != 0);
5237 else if (c == 'c') {
5238 tok.next();
5239 tok.skip();
5240 charinfo *ci = tok.get_char(1);
5241 if (ci == 0) {
5242 skip_alternative();
5243 return 0;
5245 result = character_exists(ci, curenv);
5246 tok.next();
5248 else if (tok.space())
5249 result = 0;
5250 else if (tok.delimiter()) {
5251 token delim = tok;
5252 int delim_level = input_stack::get_level();
5253 environment env1(curenv);
5254 environment env2(curenv);
5255 environment *oldenv = curenv;
5256 curenv = &env1;
5257 for (int i = 0; i < 2; i++) {
5258 for (;;) {
5259 tok.next();
5260 if (tok.newline() || tok.eof()) {
5261 warning(WARN_DELIM, "missing closing delimiter");
5262 tok.next();
5263 curenv = oldenv;
5264 return 0;
5266 if (tok == delim
5267 && (compatible_flag || input_stack::get_level() == delim_level))
5268 break;
5269 tok.process();
5271 curenv = &env2;
5273 node *n1 = env1.extract_output_line();
5274 node *n2 = env2.extract_output_line();
5275 result = same_node_list(n1, n2);
5276 delete_node_list(n1);
5277 delete_node_list(n2);
5278 curenv = oldenv;
5279 have_input = 0;
5280 tok.next();
5282 else {
5283 units n;
5284 if (!get_number(&n, 'u')) {
5285 skip_alternative();
5286 return 0;
5288 else
5289 result = n > 0;
5291 if (invert)
5292 result = !result;
5293 if (result)
5294 begin_alternative();
5295 else
5296 skip_alternative();
5297 return result;
5300 void if_else_request()
5302 if_else_stack.push(do_if_request());
5305 void if_request()
5307 do_if_request();
5310 void else_request()
5312 if (if_else_stack.is_empty()) {
5313 warning(WARN_EL, "unbalanced .el request");
5314 skip_alternative();
5316 else {
5317 if (if_else_stack.pop())
5318 skip_alternative();
5319 else
5320 begin_alternative();
5324 static int while_depth = 0;
5325 static int while_break_flag = 0;
5327 void while_request()
5329 macro mac;
5330 int escaped = 0;
5331 int level = 0;
5332 mac.append(new token_node(tok));
5333 for (;;) {
5334 node *n;
5335 int c = input_stack::get(&n);
5336 if (c == EOF)
5337 break;
5338 if (c == 0) {
5339 escaped = 0;
5340 mac.append(n);
5342 else if (escaped) {
5343 if (c == '{')
5344 level += 1;
5345 else if (c == '}')
5346 level -= 1;
5347 escaped = 0;
5348 mac.append(c);
5350 else {
5351 if (c == ESCAPE_LEFT_BRACE)
5352 level += 1;
5353 else if (c == ESCAPE_RIGHT_BRACE)
5354 level -= 1;
5355 else if (c == escape_char)
5356 escaped = 1;
5357 mac.append(c);
5358 if (c == '\n' && level <= 0)
5359 break;
5362 if (level != 0)
5363 error("unbalanced \\{ \\}");
5364 else {
5365 while_depth++;
5366 input_stack::add_boundary();
5367 for (;;) {
5368 input_stack::push(new string_iterator(mac, "while loop"));
5369 tok.next();
5370 if (!do_if_request()) {
5371 while (input_stack::get(0) != EOF)
5373 break;
5375 process_input_stack();
5376 if (while_break_flag || input_stack::is_return_boundary()) {
5377 while_break_flag = 0;
5378 break;
5381 input_stack::remove_boundary();
5382 while_depth--;
5384 tok.next();
5387 void while_break_request()
5389 if (!while_depth) {
5390 error("no while loop");
5391 skip_line();
5393 else {
5394 while_break_flag = 1;
5395 while (input_stack::get(0) != EOF)
5397 tok.next();
5401 void while_continue_request()
5403 if (!while_depth) {
5404 error("no while loop");
5405 skip_line();
5407 else {
5408 while (input_stack::get(0) != EOF)
5410 tok.next();
5414 // .so
5416 void source()
5418 symbol nm = get_long_name(1);
5419 if (nm.is_null())
5420 skip_line();
5421 else {
5422 while (!tok.newline() && !tok.eof())
5423 tok.next();
5424 errno = 0;
5425 FILE *fp = fopen(nm.contents(), "r");
5426 if (fp)
5427 input_stack::push(new file_iterator(fp, nm.contents()));
5428 else
5429 error("can't open `%1': %2", nm.contents(), strerror(errno));
5430 tok.next();
5434 // like .so but use popen()
5436 void pipe_source()
5438 if (safer_flag) {
5439 error(".pso request not allowed in safer mode");
5440 skip_line();
5442 else {
5443 #ifdef POPEN_MISSING
5444 error("pipes not available on this system");
5445 skip_line();
5446 #else /* not POPEN_MISSING */
5447 if (tok.newline() || tok.eof())
5448 error("missing command");
5449 else {
5450 int c;
5451 while ((c = get_copy(0)) == ' ' || c == '\t')
5453 int buf_size = 24;
5454 char *buf = new char[buf_size];
5455 int buf_used = 0;
5456 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5457 const char *s = asciify(c);
5458 int slen = strlen(s);
5459 if (buf_used + slen + 1> buf_size) {
5460 char *old_buf = buf;
5461 int old_buf_size = buf_size;
5462 buf_size *= 2;
5463 buf = new char[buf_size];
5464 memcpy(buf, old_buf, old_buf_size);
5465 a_delete old_buf;
5467 strcpy(buf + buf_used, s);
5468 buf_used += slen;
5470 buf[buf_used] = '\0';
5471 errno = 0;
5472 FILE *fp = popen(buf, POPEN_RT);
5473 if (fp)
5474 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5475 else
5476 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5477 a_delete buf;
5479 tok.next();
5480 #endif /* not POPEN_MISSING */
5484 // .psbb
5486 static int llx_reg_contents = 0;
5487 static int lly_reg_contents = 0;
5488 static int urx_reg_contents = 0;
5489 static int ury_reg_contents = 0;
5491 struct bounding_box {
5492 int llx, lly, urx, ury;
5495 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5496 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5498 int parse_bounding_box(char *p, bounding_box *bb)
5500 if (sscanf(p, "%d %d %d %d",
5501 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5502 return 1;
5503 else {
5504 /* The Document Structuring Conventions say that the numbers
5505 should be integers. Unfortunately some broken applications
5506 get this wrong. */
5507 double x1, x2, x3, x4;
5508 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5509 bb->llx = (int)x1;
5510 bb->lly = (int)x2;
5511 bb->urx = (int)x3;
5512 bb->ury = (int)x4;
5513 return 1;
5515 else {
5516 for (; *p == ' ' || *p == '\t'; p++)
5518 if (strncmp(p, "(atend)", 7) == 0) {
5519 return 2;
5523 bb->llx = bb->lly = bb->urx = bb->ury = 0;
5524 return 0;
5527 // This version is taken from psrm.cc
5529 #define PS_LINE_MAX 255
5530 cset white_space("\n\r \t");
5532 int ps_get_line(char *buf, FILE *fp, const char* filename)
5534 int c = getc(fp);
5535 if (c == EOF) {
5536 buf[0] = '\0';
5537 return 0;
5539 int i = 0;
5540 int err = 0;
5541 while (c != '\r' && c != '\n' && c != EOF) {
5542 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
5543 error("invalid input character code %1 in `%2'", int(c), filename);
5544 else if (i < PS_LINE_MAX)
5545 buf[i++] = c;
5546 else if (!err) {
5547 err = 1;
5548 error("PostScript file `%1' is non-conforming "
5549 "because length of line exceeds 255", filename);
5551 c = getc(fp);
5553 buf[i++] = '\n';
5554 buf[i] = '\0';
5555 if (c == '\r') {
5556 c = getc(fp);
5557 if (c != EOF && c != '\n')
5558 ungetc(c, fp);
5560 return 1;
5563 inline void assign_registers(int llx, int lly, int urx, int ury)
5565 llx_reg_contents = llx;
5566 lly_reg_contents = lly;
5567 urx_reg_contents = urx;
5568 ury_reg_contents = ury;
5571 void do_ps_file(FILE *fp, const char* filename)
5573 bounding_box bb;
5574 int bb_at_end = 0;
5575 char buf[PS_LINE_MAX];
5576 llx_reg_contents = lly_reg_contents =
5577 urx_reg_contents = ury_reg_contents = 0;
5578 if (!ps_get_line(buf, fp, filename)) {
5579 error("`%1' is empty", filename);
5580 return;
5582 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
5583 error("`%1' is not conforming to the Document Structuring Conventions",
5584 filename);
5585 return;
5587 while (ps_get_line(buf, fp, filename) != 0) {
5588 if (buf[0] != '%' || buf[1] != '%'
5589 || strncmp(buf + 2, "EndComments", 11) == 0)
5590 break;
5591 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5592 int res = parse_bounding_box(buf + 14, &bb);
5593 if (res == 1) {
5594 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5595 return;
5597 else if (res == 2) {
5598 bb_at_end = 1;
5599 break;
5601 else {
5602 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5603 filename);
5604 return;
5608 if (bb_at_end) {
5609 long offset;
5610 int last_try = 0;
5611 /* in the trailer, the last BoundingBox comment is significant */
5612 for (offset = 512; !last_try; offset *= 2) {
5613 int had_trailer = 0;
5614 int got_bb = 0;
5615 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
5616 last_try = 1;
5617 if (fseek(fp, 0L, 0) == -1)
5618 break;
5620 while (ps_get_line(buf, fp, filename) != 0) {
5621 if (buf[0] == '%' && buf[1] == '%') {
5622 if (!had_trailer) {
5623 if (strncmp(buf + 2, "Trailer", 7) == 0)
5624 had_trailer = 1;
5626 else {
5627 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5628 int res = parse_bounding_box(buf + 14, &bb);
5629 if (res == 1)
5630 got_bb = 1;
5631 else if (res == 2) {
5632 error("`(atend)' not allowed in trailer of `%1'", filename);
5633 return;
5635 else {
5636 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5637 filename);
5638 return;
5644 if (got_bb) {
5645 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5646 return;
5650 error("%%%%BoundingBox comment not found in `%1'", filename);
5653 void ps_bbox_request()
5655 symbol nm = get_long_name(1);
5656 if (nm.is_null())
5657 skip_line();
5658 else {
5659 while (!tok.newline() && !tok.eof())
5660 tok.next();
5661 errno = 0;
5662 // PS files might contain non-printable characters, such as ^Z
5663 // and CRs not followed by an LF, so open them in binary mode.
5664 FILE *fp = fopen(nm.contents(), FOPEN_RB);
5665 if (fp) {
5666 do_ps_file(fp, nm.contents());
5667 fclose(fp);
5669 else
5670 error("can't open `%1': %2", nm.contents(), strerror(errno));
5671 tok.next();
5675 const char *asciify(int c)
5677 static char buf[3];
5678 buf[0] = escape_char == '\0' ? '\\' : escape_char;
5679 buf[1] = buf[2] = '\0';
5680 switch (c) {
5681 case ESCAPE_QUESTION:
5682 buf[1] = '?';
5683 break;
5684 case ESCAPE_AMPERSAND:
5685 buf[1] = '&';
5686 break;
5687 case ESCAPE_RIGHT_PARENTHESIS:
5688 buf[1] = ')';
5689 break;
5690 case ESCAPE_UNDERSCORE:
5691 buf[1] = '_';
5692 break;
5693 case ESCAPE_BAR:
5694 buf[1] = '|';
5695 break;
5696 case ESCAPE_CIRCUMFLEX:
5697 buf[1] = '^';
5698 break;
5699 case ESCAPE_LEFT_BRACE:
5700 buf[1] = '{';
5701 break;
5702 case ESCAPE_RIGHT_BRACE:
5703 buf[1] = '}';
5704 break;
5705 case ESCAPE_LEFT_QUOTE:
5706 buf[1] = '`';
5707 break;
5708 case ESCAPE_RIGHT_QUOTE:
5709 buf[1] = '\'';
5710 break;
5711 case ESCAPE_HYPHEN:
5712 buf[1] = '-';
5713 break;
5714 case ESCAPE_BANG:
5715 buf[1] = '!';
5716 break;
5717 case ESCAPE_c:
5718 buf[1] = 'c';
5719 break;
5720 case ESCAPE_e:
5721 buf[1] = 'e';
5722 break;
5723 case ESCAPE_E:
5724 buf[1] = 'E';
5725 break;
5726 case ESCAPE_PERCENT:
5727 buf[1] = '%';
5728 break;
5729 case ESCAPE_SPACE:
5730 buf[1] = ' ';
5731 break;
5732 case ESCAPE_TILDE:
5733 buf[1] = '~';
5734 break;
5735 case ESCAPE_COLON:
5736 buf[1] = ':';
5737 break;
5738 case COMPATIBLE_SAVE:
5739 case COMPATIBLE_RESTORE:
5740 buf[0] = '\0';
5741 break;
5742 default:
5743 if (invalid_input_char(c))
5744 buf[0] = '\0';
5745 else
5746 buf[0] = c;
5747 break;
5749 return buf;
5752 const char *input_char_description(int c)
5754 switch (c) {
5755 case '\n':
5756 return "a newline character";
5757 case '\b':
5758 return "a backspace character";
5759 case '\001':
5760 return "a leader character";
5761 case '\t':
5762 return "a tab character";
5763 case ' ':
5764 return "a space character";
5765 case '\0':
5766 return "a node";
5768 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
5769 if (invalid_input_char(c)) {
5770 const char *s = asciify(c);
5771 if (*s) {
5772 buf[0] = '`';
5773 strcpy(buf + 1, s);
5774 strcat(buf, "'");
5775 return buf;
5777 sprintf(buf, "magic character code %d", c);
5778 return buf;
5780 if (csprint(c)) {
5781 buf[0] = '`';
5782 buf[1] = c;
5783 buf[2] = '\'';
5784 return buf;
5786 sprintf(buf, "character code %d", c);
5787 return buf;
5790 // .tm, .tm1, and .tmc
5792 void do_terminal(int newline, int string_like)
5794 if (!tok.newline() && !tok.eof()) {
5795 int c;
5796 for (;;) {
5797 c = get_copy(0);
5798 if (string_like && c == '"') {
5799 c = get_copy(0);
5800 break;
5802 if (c != ' ' && c != '\t')
5803 break;
5805 for (; c != '\n' && c != EOF; c = get_copy(0))
5806 fputs(asciify(c), stderr);
5808 if (newline)
5809 fputc('\n', stderr);
5810 fflush(stderr);
5811 tok.next();
5814 void terminal()
5816 do_terminal(1, 0);
5819 void terminal1()
5821 do_terminal(1, 1);
5824 void terminal_continue()
5826 do_terminal(0, 1);
5829 dictionary stream_dictionary(20);
5831 void do_open(int append)
5833 symbol stream = get_name(1);
5834 if (!stream.is_null()) {
5835 symbol filename = get_long_name(1);
5836 if (!filename.is_null()) {
5837 errno = 0;
5838 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
5839 if (!fp) {
5840 error("can't open `%1' for %2: %3",
5841 filename.contents(),
5842 append ? "appending" : "writing",
5843 strerror(errno));
5844 fp = (FILE *)stream_dictionary.remove(stream);
5846 else
5847 fp = (FILE *)stream_dictionary.lookup(stream, fp);
5848 if (fp)
5849 fclose(fp);
5852 skip_line();
5855 void open_request()
5857 if (safer_flag) {
5858 error(".open request not allowed in safer mode");
5859 skip_line();
5861 else
5862 do_open(0);
5865 void opena_request()
5867 if (safer_flag) {
5868 error(".opena request not allowed in safer mode");
5869 skip_line();
5871 else
5872 do_open(1);
5875 void close_request()
5877 symbol stream = get_name(1);
5878 if (!stream.is_null()) {
5879 FILE *fp = (FILE *)stream_dictionary.remove(stream);
5880 if (!fp)
5881 error("no stream named `%1'", stream.contents());
5882 else
5883 fclose(fp);
5885 skip_line();
5888 // .write and .writec
5890 void do_write_request(int newline)
5892 symbol stream = get_name(1);
5893 if (stream.is_null()) {
5894 skip_line();
5895 return;
5897 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5898 if (!fp) {
5899 error("no stream named `%1'", stream.contents());
5900 skip_line();
5901 return;
5903 int c;
5904 while ((c = get_copy(0)) == ' ')
5906 if (c == '"')
5907 c = get_copy(0);
5908 for (; c != '\n' && c != EOF; c = get_copy(0))
5909 fputs(asciify(c), fp);
5910 if (newline)
5911 fputc('\n', fp);
5912 fflush(fp);
5913 tok.next();
5916 void write_request()
5918 do_write_request(1);
5921 void write_request_continue()
5923 do_write_request(0);
5926 void write_macro_request()
5928 symbol stream = get_name(1);
5929 if (stream.is_null()) {
5930 skip_line();
5931 return;
5933 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5934 if (!fp) {
5935 error("no stream named `%1'", stream.contents());
5936 skip_line();
5937 return;
5939 symbol s = get_name(1);
5940 if (s.is_null()) {
5941 skip_line();
5942 return;
5944 request_or_macro *p = lookup_request(s);
5945 macro *m = p->to_macro();
5946 if (!m)
5947 error("cannot write request");
5948 else {
5949 string_iterator iter(*m);
5950 for (;;) {
5951 int c = iter.get(0);
5952 if (c == EOF)
5953 break;
5954 fputs(asciify(c), fp);
5956 fflush(fp);
5958 skip_line();
5961 void warnscale_request()
5963 if (has_arg()) {
5964 char c = tok.ch();
5965 if (c == 'u')
5966 warn_scale = 1.0;
5967 else if (c == 'i')
5968 warn_scale = (double)units_per_inch;
5969 else if (c == 'c')
5970 warn_scale = (double)units_per_inch / 2.54;
5971 else if (c == 'p')
5972 warn_scale = (double)units_per_inch / 72.0;
5973 else if (c == 'P')
5974 warn_scale = (double)units_per_inch / 6.0;
5975 else {
5976 warning(WARN_SCALE,
5977 "invalid scaling indicator `%1', using `i' instead", c);
5978 c = 'i';
5980 warn_scaling_indicator = c;
5982 skip_line();
5985 void spreadwarn_request()
5987 hunits n;
5988 if (has_arg() && get_hunits(&n, 'm')) {
5989 if (n < 0)
5990 n = 0;
5991 hunits em = curenv->get_size();
5992 spread_limit = (double)n.to_units()
5993 / (em.is_zero() ? hresolution : em.to_units());
5995 else
5996 spread_limit = -spread_limit - 1; // no arg toggles on/off without
5997 // changing value; we mirror at
5998 // -0.5 to make zero a valid value
5999 skip_line();
6002 static void init_charset_table()
6004 char buf[16];
6005 strcpy(buf, "char");
6006 for (int i = 0; i < 256; i++) {
6007 strcpy(buf + 4, i_to_a(i));
6008 charset_table[i] = get_charinfo(symbol(buf));
6009 charset_table[i]->set_ascii_code(i);
6010 if (csalpha(i))
6011 charset_table[i]->set_hyphenation_code(cmlower(i));
6013 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
6014 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
6015 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
6016 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
6017 charset_table['"']->set_flags(charinfo::TRANSPARENT);
6018 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
6019 charset_table[')']->set_flags(charinfo::TRANSPARENT);
6020 charset_table[']']->set_flags(charinfo::TRANSPARENT);
6021 charset_table['*']->set_flags(charinfo::TRANSPARENT);
6022 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
6023 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
6024 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
6025 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6026 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6027 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6028 get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6029 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6030 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
6031 page_character = charset_table['%'];
6034 static void init_hpf_code_table()
6036 for (int i = 0; i < 256; i++)
6037 hpf_code_table[i] = i;
6040 static void do_translate(int translate_transparent, int translate_input)
6042 tok.skip();
6043 while (!tok.newline() && !tok.eof()) {
6044 if (tok.space()) {
6045 // This is a really bizarre troff feature.
6046 tok.next();
6047 translate_space_to_dummy = tok.dummy();
6048 if (tok.newline() || tok.eof())
6049 break;
6050 tok.next();
6051 continue;
6053 charinfo *ci1 = tok.get_char(1);
6054 if (ci1 == 0)
6055 break;
6056 tok.next();
6057 if (tok.newline() || tok.eof()) {
6058 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6059 translate_transparent);
6060 break;
6062 if (tok.space())
6063 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6064 translate_transparent);
6065 else if (tok.stretchable_space())
6066 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
6067 translate_transparent);
6068 else if (tok.dummy())
6069 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
6070 translate_transparent);
6071 else if (tok.hyphen_indicator())
6072 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
6073 translate_transparent);
6074 else {
6075 charinfo *ci2 = tok.get_char(1);
6076 if (ci2 == 0)
6077 break;
6078 if (ci1 == ci2)
6079 ci1->set_translation(0, translate_transparent, translate_input);
6080 else
6081 ci1->set_translation(ci2, translate_transparent, translate_input);
6083 tok.next();
6085 skip_line();
6088 void translate()
6090 do_translate(1, 0);
6093 void translate_no_transparent()
6095 do_translate(0, 0);
6098 void translate_input()
6100 do_translate(1, 1);
6103 void char_flags()
6105 int flags;
6106 if (get_integer(&flags))
6107 while (has_arg()) {
6108 charinfo *ci = tok.get_char(1);
6109 if (ci) {
6110 charinfo *tem = ci->get_translation();
6111 if (tem)
6112 ci = tem;
6113 ci->set_flags(flags);
6115 tok.next();
6117 skip_line();
6120 void hyphenation_code()
6122 tok.skip();
6123 while (!tok.newline() && !tok.eof()) {
6124 charinfo *ci = tok.get_char(1);
6125 if (ci == 0)
6126 break;
6127 tok.next();
6128 tok.skip();
6129 unsigned char c = tok.ch();
6130 if (c == 0) {
6131 error("hyphenation code must be ordinary character");
6132 break;
6134 if (csdigit(c)) {
6135 error("hyphenation code cannot be digit");
6136 break;
6138 ci->set_hyphenation_code(c);
6139 if (ci->get_translation()
6140 && ci->get_translation()->get_translation_input())
6141 ci->get_translation()->set_hyphenation_code(c);
6142 tok.next();
6143 tok.skip();
6145 skip_line();
6148 void hyphenation_patterns_file_code()
6150 tok.skip();
6151 while (!tok.newline() && !tok.eof()) {
6152 int n1, n2;
6153 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
6154 if (!has_arg()) {
6155 error("missing output hyphenation code");
6156 break;
6158 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
6159 hpf_code_table[n1] = n2;
6160 tok.skip();
6162 else {
6163 error("output hyphenation code must be integer in the range 0..255");
6164 break;
6167 else {
6168 error("input hyphenation code must be integer in the range 0..255");
6169 break;
6172 skip_line();
6175 charinfo *token::get_char(int required)
6177 if (type == TOKEN_CHAR)
6178 return charset_table[c];
6179 if (type == TOKEN_SPECIAL)
6180 return get_charinfo(nm);
6181 if (type == TOKEN_NUMBERED_CHAR)
6182 return get_charinfo_by_number(val);
6183 if (type == TOKEN_ESCAPE) {
6184 if (escape_char != 0)
6185 return charset_table[escape_char];
6186 else {
6187 error("`\\e' used while no current escape character");
6188 return 0;
6191 if (required) {
6192 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6193 warning(WARN_MISSING, "missing normal or special character");
6194 else
6195 error("normal or special character expected (got %1)", description());
6197 return 0;
6200 charinfo *get_optional_char()
6202 while (tok.space())
6203 tok.next();
6204 charinfo *ci = tok.get_char();
6205 if (!ci)
6206 check_missing_character();
6207 else
6208 tok.next();
6209 return ci;
6212 void check_missing_character()
6214 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6215 error("normal or special character expected (got %1): "
6216 "treated as missing",
6217 tok.description());
6220 // this is for \Z
6222 int token::add_to_node_list(node **pp)
6224 hunits w;
6225 int s;
6226 node *n = 0;
6227 switch (type) {
6228 case TOKEN_CHAR:
6229 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6230 break;
6231 case TOKEN_DUMMY:
6232 n = new dummy_node;
6233 break;
6234 case TOKEN_ESCAPE:
6235 if (escape_char != 0)
6236 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6237 break;
6238 case TOKEN_HYPHEN_INDICATOR:
6239 *pp = (*pp)->add_discretionary_hyphen();
6240 break;
6241 case TOKEN_ITALIC_CORRECTION:
6242 *pp = (*pp)->add_italic_correction(&w);
6243 break;
6244 case TOKEN_LEFT_BRACE:
6245 break;
6246 case TOKEN_MARK_INPUT:
6247 set_number_reg(nm, curenv->get_input_line_position().to_units());
6248 break;
6249 case TOKEN_NODE:
6250 n = nd;
6251 nd = 0;
6252 break;
6253 case TOKEN_NUMBERED_CHAR:
6254 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6255 break;
6256 case TOKEN_RIGHT_BRACE:
6257 break;
6258 case TOKEN_SPACE:
6259 n = new hmotion_node(curenv->get_space_width(),
6260 curenv->get_fill_color());
6261 break;
6262 case TOKEN_SPECIAL:
6263 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6264 break;
6265 case TOKEN_STRETCHABLE_SPACE:
6266 n = new unbreakable_space_node(curenv->get_space_width(),
6267 curenv->get_fill_color());
6268 break;
6269 case TOKEN_UNSTRETCHABLE_SPACE:
6270 n = new space_char_hmotion_node(curenv->get_space_width(),
6271 curenv->get_fill_color());
6272 break;
6273 case TOKEN_TRANSPARENT_DUMMY:
6274 n = new transparent_dummy_node;
6275 break;
6276 case TOKEN_ZERO_WIDTH_BREAK:
6277 n = new space_node(H0, curenv->get_fill_color());
6278 n->freeze_space();
6279 n->is_escape_colon();
6280 break;
6281 default:
6282 return 0;
6284 if (n) {
6285 n->next = *pp;
6286 *pp = n;
6288 return 1;
6291 void token::process()
6293 if (possibly_handle_first_page_transition())
6294 return;
6295 switch (type) {
6296 case TOKEN_BACKSPACE:
6297 curenv->add_node(new hmotion_node(-curenv->get_space_width(),
6298 curenv->get_fill_color()));
6299 break;
6300 case TOKEN_CHAR:
6301 curenv->add_char(charset_table[c]);
6302 break;
6303 case TOKEN_DUMMY:
6304 curenv->add_node(new dummy_node);
6305 break;
6306 case TOKEN_EMPTY:
6307 assert(0);
6308 break;
6309 case TOKEN_EOF:
6310 assert(0);
6311 break;
6312 case TOKEN_ESCAPE:
6313 if (escape_char != 0)
6314 curenv->add_char(charset_table[escape_char]);
6315 break;
6316 case TOKEN_BEGIN_TRAP:
6317 case TOKEN_END_TRAP:
6318 case TOKEN_PAGE_EJECTOR:
6319 // these are all handled in process_input_stack()
6320 break;
6321 case TOKEN_HYPHEN_INDICATOR:
6322 curenv->add_hyphen_indicator();
6323 break;
6324 case TOKEN_INTERRUPT:
6325 curenv->interrupt();
6326 break;
6327 case TOKEN_ITALIC_CORRECTION:
6328 curenv->add_italic_correction();
6329 break;
6330 case TOKEN_LEADER:
6331 curenv->handle_tab(1);
6332 break;
6333 case TOKEN_LEFT_BRACE:
6334 break;
6335 case TOKEN_MARK_INPUT:
6336 set_number_reg(nm, curenv->get_input_line_position().to_units());
6337 break;
6338 case TOKEN_NEWLINE:
6339 curenv->newline();
6340 break;
6341 case TOKEN_NODE:
6342 curenv->add_node(nd);
6343 nd = 0;
6344 break;
6345 case TOKEN_NUMBERED_CHAR:
6346 curenv->add_char(get_charinfo_by_number(val));
6347 break;
6348 case TOKEN_REQUEST:
6349 // handled in process_input_stack()
6350 break;
6351 case TOKEN_RIGHT_BRACE:
6352 break;
6353 case TOKEN_SPACE:
6354 curenv->space();
6355 break;
6356 case TOKEN_SPECIAL:
6357 curenv->add_char(get_charinfo(nm));
6358 break;
6359 case TOKEN_SPREAD:
6360 curenv->spread();
6361 break;
6362 case TOKEN_STRETCHABLE_SPACE:
6363 curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
6364 curenv->get_fill_color()));
6365 break;
6366 case TOKEN_UNSTRETCHABLE_SPACE:
6367 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
6368 curenv->get_fill_color()));
6369 break;
6370 case TOKEN_TAB:
6371 curenv->handle_tab(0);
6372 break;
6373 case TOKEN_TRANSPARENT:
6374 break;
6375 case TOKEN_TRANSPARENT_DUMMY:
6376 curenv->add_node(new transparent_dummy_node);
6377 break;
6378 case TOKEN_ZERO_WIDTH_BREAK:
6380 node *tmp = new space_node(H0, curenv->get_fill_color());
6381 tmp->freeze_space();
6382 tmp->is_escape_colon();
6383 curenv->add_node(tmp);
6384 break;
6386 default:
6387 assert(0);
6391 class nargs_reg : public reg {
6392 public:
6393 const char *get_string();
6396 const char *nargs_reg::get_string()
6398 return i_to_a(input_stack::nargs());
6401 class lineno_reg : public reg {
6402 public:
6403 const char *get_string();
6406 const char *lineno_reg::get_string()
6408 int line;
6409 const char *file;
6410 if (!input_stack::get_location(0, &file, &line))
6411 line = 0;
6412 return i_to_a(line);
6415 class writable_lineno_reg : public general_reg {
6416 public:
6417 writable_lineno_reg();
6418 void set_value(units);
6419 int get_value(units *);
6422 writable_lineno_reg::writable_lineno_reg()
6426 int writable_lineno_reg::get_value(units *res)
6428 int line;
6429 const char *file;
6430 if (!input_stack::get_location(0, &file, &line))
6431 return 0;
6432 *res = line;
6433 return 1;
6436 void writable_lineno_reg::set_value(units n)
6438 input_stack::set_location(0, n);
6441 class filename_reg : public reg {
6442 public:
6443 const char *get_string();
6446 const char *filename_reg::get_string()
6448 int line;
6449 const char *file;
6450 if (input_stack::get_location(0, &file, &line))
6451 return file;
6452 else
6453 return 0;
6456 class constant_reg : public reg {
6457 const char *s;
6458 public:
6459 constant_reg(const char *);
6460 const char *get_string();
6463 constant_reg::constant_reg(const char *p) : s(p)
6467 const char *constant_reg::get_string()
6469 return s;
6472 constant_int_reg::constant_int_reg(int *q) : p(q)
6476 const char *constant_int_reg::get_string()
6478 return i_to_a(*p);
6481 void abort_request()
6483 int c;
6484 if (tok.eof())
6485 c = EOF;
6486 else if (tok.newline())
6487 c = '\n';
6488 else {
6489 while ((c = get_copy(0)) == ' ')
6492 if (c == EOF || c == '\n')
6493 fputs("User Abort.", stderr);
6494 else {
6495 for (; c != '\n' && c != EOF; c = get_copy(0))
6496 fputs(asciify(c), stderr);
6498 fputc('\n', stderr);
6499 cleanup_and_exit(1);
6502 char *read_string()
6504 int len = 256;
6505 char *s = new char[len];
6506 int c;
6507 while ((c = get_copy(0)) == ' ')
6509 int i = 0;
6510 while (c != '\n' && c != EOF) {
6511 if (!invalid_input_char(c)) {
6512 if (i + 2 > len) {
6513 char *tem = s;
6514 s = new char[len*2];
6515 memcpy(s, tem, len);
6516 len *= 2;
6517 a_delete tem;
6519 s[i++] = c;
6521 c = get_copy(0);
6523 s[i] = '\0';
6524 tok.next();
6525 if (i == 0) {
6526 a_delete s;
6527 return 0;
6529 return s;
6532 void pipe_output()
6534 if (safer_flag) {
6535 error(".pi request not allowed in safer mode");
6536 skip_line();
6538 else {
6539 #ifdef POPEN_MISSING
6540 error("pipes not available on this system");
6541 skip_line();
6542 #else /* not POPEN_MISSING */
6543 if (the_output) {
6544 error("can't pipe: output already started");
6545 skip_line();
6547 else {
6548 char *pc;
6549 if ((pc = read_string()) == 0)
6550 error("can't pipe to empty command");
6551 if (pipe_command) {
6552 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
6553 strcpy(s, pipe_command);
6554 strcat(s, "|");
6555 strcat(s, pc);
6556 a_delete pipe_command;
6557 a_delete pc;
6558 pipe_command = s;
6560 else
6561 pipe_command = pc;
6563 #endif /* not POPEN_MISSING */
6567 static int system_status;
6569 void system_request()
6571 if (safer_flag) {
6572 error(".sy request not allowed in safer mode");
6573 skip_line();
6575 else {
6576 char *command = read_string();
6577 if (!command)
6578 error("empty command");
6579 else {
6580 system_status = system(command);
6581 a_delete command;
6586 void copy_file()
6588 if (curdiv == topdiv && topdiv->before_first_page) {
6589 handle_initial_request(COPY_FILE_REQUEST);
6590 return;
6592 symbol filename = get_long_name(1);
6593 while (!tok.newline() && !tok.eof())
6594 tok.next();
6595 if (break_flag)
6596 curenv->do_break();
6597 if (!filename.is_null())
6598 curdiv->copy_file(filename.contents());
6599 tok.next();
6602 #ifdef COLUMN
6604 void vjustify()
6606 if (curdiv == topdiv && topdiv->before_first_page) {
6607 handle_initial_request(VJUSTIFY_REQUEST);
6608 return;
6610 symbol type = get_long_name(1);
6611 if (!type.is_null())
6612 curdiv->vjustify(type);
6613 skip_line();
6616 #endif /* COLUMN */
6618 void transparent_file()
6620 if (curdiv == topdiv && topdiv->before_first_page) {
6621 handle_initial_request(TRANSPARENT_FILE_REQUEST);
6622 return;
6624 symbol filename = get_long_name(1);
6625 while (!tok.newline() && !tok.eof())
6626 tok.next();
6627 if (break_flag)
6628 curenv->do_break();
6629 if (!filename.is_null()) {
6630 errno = 0;
6631 FILE *fp = fopen(filename.contents(), "r");
6632 if (!fp)
6633 error("can't open `%1': %2", filename.contents(), strerror(errno));
6634 else {
6635 int bol = 1;
6636 for (;;) {
6637 int c = getc(fp);
6638 if (c == EOF)
6639 break;
6640 if (invalid_input_char(c))
6641 warning(WARN_INPUT, "invalid input character code %1", int(c));
6642 else {
6643 curdiv->transparent_output(c);
6644 bol = c == '\n';
6647 if (!bol)
6648 curdiv->transparent_output('\n');
6649 fclose(fp);
6652 tok.next();
6655 class page_range {
6656 int first;
6657 int last;
6658 public:
6659 page_range *next;
6660 page_range(int, int, page_range *);
6661 int contains(int n);
6664 page_range::page_range(int i, int j, page_range *p)
6665 : first(i), last(j), next(p)
6669 int page_range::contains(int n)
6671 return n >= first && (last <= 0 || n <= last);
6674 page_range *output_page_list = 0;
6676 int in_output_page_list(int n)
6678 if (!output_page_list)
6679 return 1;
6680 for (page_range *p = output_page_list; p; p = p->next)
6681 if (p->contains(n))
6682 return 1;
6683 return 0;
6686 static void parse_output_page_list(char *p)
6688 for (;;) {
6689 int i;
6690 if (*p == '-')
6691 i = 1;
6692 else if (csdigit(*p)) {
6693 i = 0;
6695 i = i*10 + *p++ - '0';
6696 while (csdigit(*p));
6698 else
6699 break;
6700 int j;
6701 if (*p == '-') {
6702 p++;
6703 j = 0;
6704 if (csdigit(*p)) {
6706 j = j*10 + *p++ - '0';
6707 while (csdigit(*p));
6710 else
6711 j = i;
6712 if (j == 0)
6713 last_page_number = -1;
6714 else if (last_page_number >= 0 && j > last_page_number)
6715 last_page_number = j;
6716 output_page_list = new page_range(i, j, output_page_list);
6717 if (*p != ',')
6718 break;
6719 ++p;
6721 if (*p != '\0') {
6722 error("bad output page list");
6723 output_page_list = 0;
6727 static FILE *open_mac_file(const char *mac, char **path)
6729 // Try first FOOBAR.tmac, then tmac.FOOBAR
6730 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
6731 strcpy(s1, mac);
6732 strcat(s1, MACRO_POSTFIX);
6733 FILE *fp = mac_path->open_file(s1, path);
6734 a_delete s1;
6735 if (!fp) {
6736 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
6737 strcpy(s2, MACRO_PREFIX);
6738 strcat(s2, mac);
6739 fp = mac_path->open_file(s2, path);
6740 a_delete s2;
6742 return fp;
6745 static void process_macro_file(const char *mac)
6747 char *path;
6748 FILE *fp = open_mac_file(mac, &path);
6749 if (!fp)
6750 fatal("can't find macro file %1", mac);
6751 const char *s = symbol(path).contents();
6752 a_delete path;
6753 input_stack::push(new file_iterator(fp, s));
6754 tok.next();
6755 process_input_stack();
6758 static void process_startup_file(char *filename)
6760 char *path;
6761 search_path *orig_mac_path = mac_path;
6762 mac_path = &config_macro_path;
6763 FILE *fp = mac_path->open_file(filename, &path);
6764 if (fp) {
6765 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6766 a_delete path;
6767 tok.next();
6768 process_input_stack();
6770 mac_path = orig_mac_path;
6773 void macro_source()
6775 symbol nm = get_long_name(1);
6776 if (nm.is_null())
6777 skip_line();
6778 else {
6779 while (!tok.newline() && !tok.eof())
6780 tok.next();
6781 char *path;
6782 FILE *fp = mac_path->open_file(nm.contents(), &path);
6783 // .mso doesn't (and cannot) go through open_mac_file, so we
6784 // need to do it here manually: If we have tmac.FOOBAR, try
6785 // FOOBAR.tmac and vice versa
6786 if (!fp) {
6787 const char *fn = nm.contents();
6788 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
6789 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
6790 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
6791 strcat(s, MACRO_POSTFIX);
6792 fp = mac_path->open_file(s, &path);
6793 a_delete s;
6795 if (!fp) {
6796 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
6797 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
6798 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
6799 strcpy(s, MACRO_PREFIX);
6800 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
6801 fp = mac_path->open_file(s, &path);
6802 a_delete s;
6806 if (fp) {
6807 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6808 a_delete path;
6810 else
6811 error("can't find macro file `%1'", nm.contents());
6812 tok.next();
6816 static void process_input_file(const char *name)
6818 FILE *fp;
6819 if (strcmp(name, "-") == 0) {
6820 clearerr(stdin);
6821 fp = stdin;
6823 else {
6824 errno = 0;
6825 fp = fopen(name, "r");
6826 if (!fp)
6827 fatal("can't open `%1': %2", name, strerror(errno));
6829 input_stack::push(new file_iterator(fp, name));
6830 tok.next();
6831 process_input_stack();
6834 // make sure the_input is empty before calling this
6836 static int evaluate_expression(const char *expr, units *res)
6838 input_stack::push(make_temp_iterator(expr));
6839 tok.next();
6840 int success = get_number(res, 'u');
6841 while (input_stack::get(0) != EOF)
6843 return success;
6846 static void do_register_assignment(const char *s)
6848 const char *p = strchr(s, '=');
6849 if (!p) {
6850 char buf[2];
6851 buf[0] = s[0];
6852 buf[1] = 0;
6853 units n;
6854 if (evaluate_expression(s + 1, &n))
6855 set_number_reg(buf, n);
6857 else {
6858 char *buf = new char[p - s + 1];
6859 memcpy(buf, s, p - s);
6860 buf[p - s] = 0;
6861 units n;
6862 if (evaluate_expression(p + 1, &n))
6863 set_number_reg(buf, n);
6864 a_delete buf;
6868 static void set_string(const char *name, const char *value)
6870 macro *m = new macro;
6871 for (const char *p = value; *p; p++)
6872 if (!invalid_input_char((unsigned char)*p))
6873 m->append(*p);
6874 request_dictionary.define(name, m);
6877 static void do_string_assignment(const char *s)
6879 const char *p = strchr(s, '=');
6880 if (!p) {
6881 char buf[2];
6882 buf[0] = s[0];
6883 buf[1] = 0;
6884 set_string(buf, s + 1);
6886 else {
6887 char *buf = new char[p - s + 1];
6888 memcpy(buf, s, p - s);
6889 buf[p - s] = 0;
6890 set_string(buf, p + 1);
6891 a_delete buf;
6895 struct string_list {
6896 const char *s;
6897 string_list *next;
6898 string_list(const char *ss) : s(ss), next(0) {}
6901 #if 0
6902 static void prepend_string(const char *s, string_list **p)
6904 string_list *l = new string_list(s);
6905 l->next = *p;
6906 *p = l;
6908 #endif
6910 static void add_string(const char *s, string_list **p)
6912 while (*p)
6913 p = &((*p)->next);
6914 *p = new string_list(s);
6917 void usage(FILE *stream, const char *prog)
6919 fprintf(stream,
6920 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
6921 " -rcn -Tname -Fdir -Mdir [files...]\n",
6922 prog);
6925 int main(int argc, char **argv)
6927 program_name = argv[0];
6928 static char stderr_buf[BUFSIZ];
6929 setbuf(stderr, stderr_buf);
6930 int c;
6931 string_list *macros = 0;
6932 string_list *register_assignments = 0;
6933 string_list *string_assignments = 0;
6934 int iflag = 0;
6935 int tflag = 0;
6936 int fflag = 0;
6937 int nflag = 0;
6938 int no_rc = 0; // don't process troffrc and troffrc-end
6939 int next_page_number;
6940 opterr = 0;
6941 hresolution = vresolution = 1;
6942 // restore $PATH if called from groff
6943 char* groff_path = getenv("GROFF_PATH__");
6944 if (groff_path) {
6945 string e = "PATH";
6946 e += '=';
6947 if (*groff_path)
6948 e += groff_path;
6949 e += '\0';
6950 if (putenv(strsave(e.contents())))
6951 fatal("putenv failed");
6953 static const struct option long_options[] = {
6954 { "help", no_argument, 0, CHAR_MAX + 1 },
6955 { "version", no_argument, 0, 'v' },
6956 { 0, 0, 0, 0 }
6958 while ((c = getopt_long(argc, argv, "abcivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU",
6959 long_options, 0))
6960 != EOF)
6961 switch(c) {
6962 case 'v':
6964 printf("GNU troff (groff) version %s\n", Version_string);
6965 exit(0);
6966 break;
6968 case 'T':
6969 device = optarg;
6970 tflag = 1;
6971 is_html = (strcmp(device, "html") == 0);
6972 break;
6973 case 'C':
6974 compatible_flag = 1;
6975 // fall through
6976 case 'c':
6977 color_flag = 0;
6978 break;
6979 case 'M':
6980 macro_path.command_line_dir(optarg);
6981 safer_macro_path.command_line_dir(optarg);
6982 config_macro_path.command_line_dir(optarg);
6983 break;
6984 case 'F':
6985 font::command_line_font_dir(optarg);
6986 break;
6987 case 'm':
6988 add_string(optarg, &macros);
6989 break;
6990 case 'E':
6991 inhibit_errors = 1;
6992 break;
6993 case 'R':
6994 no_rc = 1;
6995 break;
6996 case 'w':
6997 enable_warning(optarg);
6998 break;
6999 case 'W':
7000 disable_warning(optarg);
7001 break;
7002 case 'i':
7003 iflag = 1;
7004 break;
7005 case 'b':
7006 backtrace_flag = 1;
7007 break;
7008 case 'a':
7009 ascii_output_flag = 1;
7010 break;
7011 case 'z':
7012 suppress_output_flag = 1;
7013 break;
7014 case 'n':
7015 if (sscanf(optarg, "%d", &next_page_number) == 1)
7016 nflag++;
7017 else
7018 error("bad page number");
7019 break;
7020 case 'o':
7021 parse_output_page_list(optarg);
7022 break;
7023 case 'd':
7024 if (*optarg == '\0')
7025 error("`-d' requires non-empty argument");
7026 else
7027 add_string(optarg, &string_assignments);
7028 break;
7029 case 'r':
7030 if (*optarg == '\0')
7031 error("`-r' requires non-empty argument");
7032 else
7033 add_string(optarg, &register_assignments);
7034 break;
7035 case 'f':
7036 default_family = symbol(optarg);
7037 fflag = 1;
7038 break;
7039 case 'q':
7040 case 's':
7041 case 't':
7042 // silently ignore these
7043 break;
7044 case 'U':
7045 safer_flag = 0; // unsafe behaviour
7046 break;
7047 case CHAR_MAX + 1: // --help
7048 usage(stdout, argv[0]);
7049 exit(0);
7050 break;
7051 case '?':
7052 usage(stderr, argv[0]);
7053 exit(1);
7054 break; // never reached
7055 default:
7056 assert(0);
7058 if (!safer_flag)
7059 mac_path = &macro_path;
7060 set_string(".T", device);
7061 init_charset_table();
7062 init_hpf_code_table();
7063 if (!font::load_desc())
7064 fatal("sorry, I can't continue");
7065 units_per_inch = font::res;
7066 hresolution = font::hor;
7067 vresolution = font::vert;
7068 sizescale = font::sizescale;
7069 tcommand_flag = font::tcommand;
7070 warn_scale = (double)units_per_inch;
7071 warn_scaling_indicator = 'i';
7072 if (!fflag && font::family != 0 && *font::family != '\0')
7073 default_family = symbol(font::family);
7074 font_size::init_size_table(font::sizes);
7075 int i;
7076 int j = 1;
7077 if (font::style_table) {
7078 for (i = 0; font::style_table[i]; i++)
7079 mount_style(j++, symbol(font::style_table[i]));
7081 for (i = 0; font::font_name_table[i]; i++, j++)
7082 // In the DESC file a font name of 0 (zero) means leave this
7083 // position empty.
7084 if (strcmp(font::font_name_table[i], "0") != 0)
7085 mount_font(j, symbol(font::font_name_table[i]));
7086 curdiv = topdiv = new top_level_diversion;
7087 if (nflag)
7088 topdiv->set_next_page_number(next_page_number);
7089 init_input_requests();
7090 init_env_requests();
7091 init_div_requests();
7092 #ifdef COLUMN
7093 init_column_requests();
7094 #endif /* COLUMN */
7095 init_node_requests();
7096 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
7097 init_registers();
7098 init_reg_requests();
7099 init_hyphen_requests();
7100 init_environments();
7101 while (string_assignments) {
7102 do_string_assignment(string_assignments->s);
7103 string_list *tem = string_assignments;
7104 string_assignments = string_assignments->next;
7105 delete tem;
7107 while (register_assignments) {
7108 do_register_assignment(register_assignments->s);
7109 string_list *tem = register_assignments;
7110 register_assignments = register_assignments->next;
7111 delete tem;
7113 if (!no_rc)
7114 process_startup_file(INITIAL_STARTUP_FILE);
7115 while (macros) {
7116 process_macro_file(macros->s);
7117 string_list *tem = macros;
7118 macros = macros->next;
7119 delete tem;
7121 if (!no_rc)
7122 process_startup_file(FINAL_STARTUP_FILE);
7123 for (i = optind; i < argc; i++)
7124 process_input_file(argv[i]);
7125 if (optind >= argc || iflag)
7126 process_input_file("-");
7127 exit_troff();
7128 return 0; // not reached
7131 void warn_request()
7133 int n;
7134 if (has_arg() && get_integer(&n)) {
7135 if (n & ~WARN_TOTAL) {
7136 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
7137 n &= WARN_TOTAL;
7139 warning_mask = n;
7141 else
7142 warning_mask = WARN_TOTAL;
7143 skip_line();
7146 static void init_registers()
7148 #ifdef LONG_FOR_TIME_T
7149 long
7150 #else /* not LONG_FOR_TIME_T */
7151 time_t
7152 #endif /* not LONG_FOR_TIME_T */
7153 t = time(0);
7154 // Use struct here to work around misfeature in old versions of g++.
7155 struct tm *tt = localtime(&t);
7156 set_number_reg("seconds", int(tt->tm_sec));
7157 set_number_reg("minutes", int(tt->tm_min));
7158 set_number_reg("hours", int(tt->tm_hour));
7159 set_number_reg("dw", int(tt->tm_wday + 1));
7160 set_number_reg("dy", int(tt->tm_mday));
7161 set_number_reg("mo", int(tt->tm_mon + 1));
7162 set_number_reg("year", int(1900 + tt->tm_year));
7163 set_number_reg("yr", int(tt->tm_year));
7164 set_number_reg("$$", getpid());
7165 number_reg_dictionary.define(".A",
7166 new constant_reg(ascii_output_flag
7167 ? "1"
7168 : "0"));
7172 * registers associated with \O
7175 static int output_reg_minx_contents = -1;
7176 static int output_reg_miny_contents = -1;
7177 static int output_reg_maxx_contents = -1;
7178 static int output_reg_maxy_contents = -1;
7180 void check_output_limits(int x, int y)
7182 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
7183 output_reg_minx_contents = x;
7184 if (x > output_reg_maxx_contents)
7185 output_reg_maxx_contents = x;
7186 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
7187 output_reg_miny_contents = y;
7188 if (y > output_reg_maxy_contents)
7189 output_reg_maxy_contents = y;
7192 void reset_output_registers()
7194 output_reg_minx_contents = -1;
7195 output_reg_miny_contents = -1;
7196 output_reg_maxx_contents = -1;
7197 output_reg_maxy_contents = -1;
7200 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
7202 *minx = output_reg_minx_contents;
7203 *miny = output_reg_miny_contents;
7204 *maxx = output_reg_maxx_contents;
7205 *maxy = output_reg_maxy_contents;
7208 void init_input_requests()
7210 init_request("ab", abort_request);
7211 init_request("als", alias_macro);
7212 init_request("am", append_macro);
7213 init_request("am1", append_nocomp_macro);
7214 init_request("ami", append_indirect_macro);
7215 init_request("as", append_string);
7216 init_request("as1", append_nocomp_string);
7217 init_request("asciify", asciify_macro);
7218 init_request("backtrace", backtrace_request);
7219 init_request("blm", blank_line_macro);
7220 init_request("break", while_break_request);
7221 init_request("cf", copy_file);
7222 init_request("cflags", char_flags);
7223 init_request("char", define_character);
7224 init_request("chop", chop_macro);
7225 init_request("close", close_request);
7226 init_request("color", activate_color);
7227 init_request("composite", composite_request);
7228 init_request("continue", while_continue_request);
7229 init_request("cp", compatible);
7230 init_request("de", define_macro);
7231 init_request("de1", define_nocomp_macro);
7232 init_request("defcolor", define_color);
7233 init_request("dei", define_indirect_macro);
7234 init_request("do", do_request);
7235 init_request("ds", define_string);
7236 init_request("ds1", define_nocomp_string);
7237 init_request("ec", set_escape_char);
7238 init_request("ecr", restore_escape_char);
7239 init_request("ecs", save_escape_char);
7240 init_request("el", else_request);
7241 init_request("em", end_macro);
7242 init_request("eo", escape_off);
7243 init_request("ex", exit_request);
7244 init_request("fchar", define_fallback_character);
7245 #ifdef WIDOW_CONTROL
7246 init_request("fpl", flush_pending_lines);
7247 #endif /* WIDOW_CONTROL */
7248 init_request("hcode", hyphenation_code);
7249 init_request("hpfcode", hyphenation_patterns_file_code);
7250 init_request("ie", if_else_request);
7251 init_request("if", if_request);
7252 init_request("ig", ignore);
7253 init_request("length", length_request);
7254 init_request("lf", line_file);
7255 init_request("mso", macro_source);
7256 init_request("nop", nop_request);
7257 init_request("nroff", nroff_request);
7258 init_request("nx", next_file);
7259 init_request("open", open_request);
7260 init_request("opena", opena_request);
7261 init_request("output", output_request);
7262 init_request("pc", set_page_character);
7263 init_request("pi", pipe_output);
7264 init_request("pm", print_macros);
7265 init_request("psbb", ps_bbox_request);
7266 #ifndef POPEN_MISSING
7267 init_request("pso", pipe_source);
7268 #endif /* not POPEN_MISSING */
7269 init_request("rchar", remove_character);
7270 init_request("rd", read_request);
7271 init_request("return", return_macro_request);
7272 init_request("rm", remove_macro);
7273 init_request("rn", rename_macro);
7274 init_request("schar", define_special_character);
7275 init_request("shift", shift);
7276 init_request("so", source);
7277 init_request("spreadwarn", spreadwarn_request);
7278 init_request("substring", substring_request);
7279 init_request("sy", system_request);
7280 init_request("tm", terminal);
7281 init_request("tm1", terminal1);
7282 init_request("tmc", terminal_continue);
7283 init_request("tr", translate);
7284 init_request("trf", transparent_file);
7285 init_request("trin", translate_input);
7286 init_request("trnt", translate_no_transparent);
7287 init_request("troff", troff_request);
7288 init_request("unformat", unformat_macro);
7289 #ifdef COLUMN
7290 init_request("vj", vjustify);
7291 #endif /* COLUMN */
7292 init_request("warn", warn_request);
7293 init_request("warnscale", warnscale_request);
7294 init_request("while", while_request);
7295 init_request("write", write_request);
7296 init_request("writec", write_request_continue);
7297 init_request("writem", write_macro_request);
7298 number_reg_dictionary.define(".$", new nargs_reg);
7299 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
7300 number_reg_dictionary.define(".c", new lineno_reg);
7301 number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
7302 number_reg_dictionary.define(".F", new filename_reg);
7303 number_reg_dictionary.define(".g", new constant_reg("1"));
7304 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
7305 number_reg_dictionary.define(".R", new constant_reg("10000"));
7306 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
7307 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
7308 extern const char *major_version;
7309 number_reg_dictionary.define(".x", new constant_reg(major_version));
7310 extern const char *revision;
7311 number_reg_dictionary.define(".Y", new constant_reg(revision));
7312 extern const char *minor_version;
7313 number_reg_dictionary.define(".y", new constant_reg(minor_version));
7314 number_reg_dictionary.define("c.", new writable_lineno_reg);
7315 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
7316 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
7317 number_reg_dictionary.define("opmaxx",
7318 new variable_reg(&output_reg_maxx_contents));
7319 number_reg_dictionary.define("opmaxy",
7320 new variable_reg(&output_reg_maxy_contents));
7321 number_reg_dictionary.define("opminx",
7322 new variable_reg(&output_reg_minx_contents));
7323 number_reg_dictionary.define("opminy",
7324 new variable_reg(&output_reg_miny_contents));
7325 number_reg_dictionary.define("slimit",
7326 new variable_reg(&input_stack::limit));
7327 number_reg_dictionary.define("systat", new variable_reg(&system_status));
7328 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
7329 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
7332 object_dictionary request_dictionary(501);
7334 void init_request(const char *s, REQUEST_FUNCP f)
7336 request_dictionary.define(s, new request(f));
7339 static request_or_macro *lookup_request(symbol nm)
7341 assert(!nm.is_null());
7342 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
7343 if (p == 0) {
7344 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
7345 p = new macro;
7346 request_dictionary.define(nm, p);
7348 return p;
7351 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7353 // Don't interpret character definitions in compatible mode.
7354 int old_compatible_flag = compatible_flag;
7355 compatible_flag = 0;
7356 int old_escape_char = escape_char;
7357 escape_char = '\\';
7358 macro *mac = ci->set_macro(0);
7359 assert(mac != 0);
7360 environment *oldenv = curenv;
7361 environment env(envp);
7362 curenv = &env;
7363 curenv->set_composite();
7364 token old_tok = tok;
7365 input_stack::add_boundary();
7366 string_iterator *si =
7367 new string_iterator(*mac, "composite character", ci->nm);
7368 input_stack::push(si);
7369 // we don't use process_input_stack, because we don't want to recognise
7370 // requests
7371 for (;;) {
7372 tok.next();
7373 if (tok.eof())
7374 break;
7375 if (tok.newline()) {
7376 error("composite character mustn't contain newline");
7377 while (!tok.eof())
7378 tok.next();
7379 break;
7381 else
7382 tok.process();
7384 node *n = curenv->extract_output_line();
7385 input_stack::remove_boundary();
7386 ci->set_macro(mac);
7387 tok = old_tok;
7388 curenv = oldenv;
7389 compatible_flag = old_compatible_flag;
7390 escape_char = old_escape_char;
7391 have_input = 0;
7392 return n;
7395 static node *read_draw_node()
7397 token start;
7398 start.next();
7399 if (!start.delimiter(1)){
7400 do {
7401 tok.next();
7402 } while (tok != start && !tok.newline() && !tok.eof());
7404 else {
7405 tok.next();
7406 if (tok == start)
7407 error("missing argument");
7408 else {
7409 unsigned char type = tok.ch();
7410 if (type == 'F') {
7411 read_color_draw_node(start);
7412 return 0;
7414 tok.next();
7415 int maxpoints = 10;
7416 hvpair *point = new hvpair[maxpoints];
7417 int npoints = 0;
7418 int no_last_v = 0;
7419 int err = 0;
7420 int i;
7421 for (i = 0; tok != start; i++) {
7422 if (i == maxpoints) {
7423 hvpair *oldpoint = point;
7424 point = new hvpair[maxpoints*2];
7425 for (int j = 0; j < maxpoints; j++)
7426 point[j] = oldpoint[j];
7427 maxpoints *= 2;
7428 a_delete oldpoint;
7430 if (!get_hunits(&point[i].h,
7431 type == 'f' || type == 't' ? 'u' : 'm')) {
7432 err = 1;
7433 break;
7435 ++npoints;
7436 tok.skip();
7437 point[i].v = V0;
7438 if (tok == start) {
7439 no_last_v = 1;
7440 break;
7442 if (!get_vunits(&point[i].v, 'v')) {
7443 err = 1;
7444 break;
7446 tok.skip();
7448 while (tok != start && !tok.newline() && !tok.eof())
7449 tok.next();
7450 if (!err) {
7451 switch (type) {
7452 case 'l':
7453 if (npoints != 1 || no_last_v) {
7454 error("two arguments needed for line");
7455 npoints = 1;
7457 break;
7458 case 'c':
7459 if (npoints != 1 || !no_last_v) {
7460 error("one argument needed for circle");
7461 npoints = 1;
7462 point[0].v = V0;
7464 break;
7465 case 'e':
7466 if (npoints != 1 || no_last_v) {
7467 error("two arguments needed for ellipse");
7468 npoints = 1;
7470 break;
7471 case 'a':
7472 if (npoints != 2 || no_last_v) {
7473 error("four arguments needed for arc");
7474 npoints = 2;
7476 break;
7477 case '~':
7478 if (no_last_v)
7479 error("even number of arguments needed for spline");
7480 break;
7481 case 'f':
7482 if (npoints != 1 || !no_last_v) {
7483 error("one argument needed for gray shade");
7484 npoints = 1;
7485 point[0].v = V0;
7487 default:
7488 // silently pass it through
7489 break;
7491 draw_node *dn = new draw_node(type, point, npoints,
7492 curenv->get_font_size(),
7493 curenv->get_glyph_color(),
7494 curenv->get_fill_color());
7495 a_delete point;
7496 return dn;
7498 else {
7499 a_delete point;
7503 return 0;
7506 static void read_color_draw_node(token &start)
7508 tok.next();
7509 if (tok == start) {
7510 error("missing color scheme");
7511 return;
7513 unsigned char scheme = tok.ch();
7514 tok.next();
7515 color *col;
7516 char end = start.ch();
7517 switch (scheme) {
7518 case 'c':
7519 col = read_cmy(end);
7520 break;
7521 case 'd':
7522 col = &default_color;
7523 break;
7524 case 'g':
7525 col = read_gray(end);
7526 break;
7527 case 'k':
7528 col = read_cmyk(end);
7529 break;
7530 case 'r':
7531 col = read_rgb(end);
7532 break;
7534 if (col)
7535 curenv->set_fill_color(col);
7536 while (tok != start) {
7537 if (tok.newline() || tok.eof()) {
7538 warning(WARN_DELIM, "missing closing delimiter");
7539 input_stack::push(make_temp_iterator("\n"));
7540 break;
7542 tok.next();
7544 have_input = 1;
7547 static struct {
7548 const char *name;
7549 int mask;
7550 } warning_table[] = {
7551 { "char", WARN_CHAR },
7552 { "range", WARN_RANGE },
7553 { "break", WARN_BREAK },
7554 { "delim", WARN_DELIM },
7555 { "el", WARN_EL },
7556 { "scale", WARN_SCALE },
7557 { "number", WARN_NUMBER },
7558 { "syntax", WARN_SYNTAX },
7559 { "tab", WARN_TAB },
7560 { "right-brace", WARN_RIGHT_BRACE },
7561 { "missing", WARN_MISSING },
7562 { "input", WARN_INPUT },
7563 { "escape", WARN_ESCAPE },
7564 { "space", WARN_SPACE },
7565 { "font", WARN_FONT },
7566 { "di", WARN_DI },
7567 { "mac", WARN_MAC },
7568 { "reg", WARN_REG },
7569 { "ig", WARN_IG },
7570 { "color", WARN_COLOR },
7571 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
7572 { "w", WARN_TOTAL },
7573 { "default", DEFAULT_WARNING_MASK },
7576 static int lookup_warning(const char *name)
7578 for (unsigned int i = 0;
7579 i < sizeof(warning_table)/sizeof(warning_table[0]);
7580 i++)
7581 if (strcmp(name, warning_table[i].name) == 0)
7582 return warning_table[i].mask;
7583 return 0;
7586 static void enable_warning(const char *name)
7588 int mask = lookup_warning(name);
7589 if (mask)
7590 warning_mask |= mask;
7591 else
7592 error("unknown warning `%1'", name);
7595 static void disable_warning(const char *name)
7597 int mask = lookup_warning(name);
7598 if (mask)
7599 warning_mask &= ~mask;
7600 else
7601 error("unknown warning `%1'", name);
7604 static void copy_mode_error(const char *format,
7605 const errarg &arg1,
7606 const errarg &arg2,
7607 const errarg &arg3)
7609 if (ignoring) {
7610 static const char prefix[] = "(in ignored input) ";
7611 char *s = new char[sizeof(prefix) + strlen(format)];
7612 strcpy(s, prefix);
7613 strcat(s, format);
7614 warning(WARN_IG, s, arg1, arg2, arg3);
7615 a_delete s;
7617 else
7618 error(format, arg1, arg2, arg3);
7621 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
7623 static void do_error(error_type type,
7624 const char *format,
7625 const errarg &arg1,
7626 const errarg &arg2,
7627 const errarg &arg3)
7629 const char *filename;
7630 int lineno;
7631 if (inhibit_errors && type < FATAL)
7632 return;
7633 if (backtrace_flag)
7634 input_stack::backtrace();
7635 if (!get_file_line(&filename, &lineno))
7636 filename = 0;
7637 if (filename)
7638 errprint("%1:%2: ", filename, lineno);
7639 else if (program_name)
7640 fprintf(stderr, "%s: ", program_name);
7641 switch (type) {
7642 case FATAL:
7643 fputs("fatal error: ", stderr);
7644 break;
7645 case ERROR:
7646 break;
7647 case WARNING:
7648 fputs("warning: ", stderr);
7649 break;
7650 case OUTPUT_WARNING:
7651 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
7652 fprintf(stderr, "warning [p %d, %.1f%c",
7653 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
7654 if (topdiv != curdiv) {
7655 double fromtop1 = curdiv->get_vertical_position().to_units()
7656 / warn_scale;
7657 fprintf(stderr, ", div `%s', %.1f%c",
7658 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
7660 fprintf(stderr, "]: ");
7661 break;
7663 errprint(format, arg1, arg2, arg3);
7664 fputc('\n', stderr);
7665 fflush(stderr);
7666 if (type == FATAL)
7667 cleanup_and_exit(1);
7670 int warning(warning_type t,
7671 const char *format,
7672 const errarg &arg1,
7673 const errarg &arg2,
7674 const errarg &arg3)
7676 if ((t & warning_mask) != 0) {
7677 do_error(WARNING, format, arg1, arg2, arg3);
7678 return 1;
7680 else
7681 return 0;
7684 int output_warning(warning_type t,
7685 const char *format,
7686 const errarg &arg1,
7687 const errarg &arg2,
7688 const errarg &arg3)
7690 if ((t & warning_mask) != 0) {
7691 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
7692 return 1;
7694 else
7695 return 0;
7698 void error(const char *format,
7699 const errarg &arg1,
7700 const errarg &arg2,
7701 const errarg &arg3)
7703 do_error(ERROR, format, arg1, arg2, arg3);
7706 void fatal(const char *format,
7707 const errarg &arg1,
7708 const errarg &arg2,
7709 const errarg &arg3)
7711 do_error(FATAL, format, arg1, arg2, arg3);
7714 void fatal_with_file_and_line(const char *filename, int lineno,
7715 const char *format,
7716 const errarg &arg1,
7717 const errarg &arg2,
7718 const errarg &arg3)
7720 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
7721 errprint(format, arg1, arg2, arg3);
7722 fputc('\n', stderr);
7723 fflush(stderr);
7724 cleanup_and_exit(1);
7727 void error_with_file_and_line(const char *filename, int lineno,
7728 const char *format,
7729 const errarg &arg1,
7730 const errarg &arg2,
7731 const errarg &arg3)
7733 fprintf(stderr, "%s:%d: error: ", filename, lineno);
7734 errprint(format, arg1, arg2, arg3);
7735 fputc('\n', stderr);
7736 fflush(stderr);
7739 dictionary charinfo_dictionary(501);
7741 charinfo *get_charinfo(symbol nm)
7743 void *p = charinfo_dictionary.lookup(nm);
7744 if (p != 0)
7745 return (charinfo *)p;
7746 charinfo *cp = new charinfo(nm);
7747 (void)charinfo_dictionary.lookup(nm, cp);
7748 return cp;
7751 int charinfo::next_index = 0;
7753 charinfo::charinfo(symbol s)
7754 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
7755 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
7756 not_found(0), transparent_translate(1), translate_input(0),
7757 mode(CHAR_NORMAL), nm(s)
7759 index = next_index++;
7762 void charinfo::set_hyphenation_code(unsigned char c)
7764 hyphenation_code = c;
7767 void charinfo::set_translation(charinfo *ci, int tt, int ti)
7769 translation = ci;
7770 if (ci && ti) {
7771 if (hyphenation_code != 0)
7772 ci->set_hyphenation_code(hyphenation_code);
7773 if (asciify_code != 0)
7774 ci->set_asciify_code(asciify_code);
7775 else if (ascii_code != 0)
7776 ci->set_asciify_code(ascii_code);
7777 ci->set_translation_input();
7779 special_translation = TRANSLATE_NONE;
7780 transparent_translate = tt;
7783 void charinfo::set_special_translation(int c, int tt)
7785 special_translation = c;
7786 translation = 0;
7787 transparent_translate = tt;
7790 void charinfo::set_ascii_code(unsigned char c)
7792 ascii_code = c;
7795 void charinfo::set_asciify_code(unsigned char c)
7797 asciify_code = c;
7800 macro *charinfo::set_macro(macro *m)
7802 macro *tem = mac;
7803 mac = m;
7804 return tem;
7807 macro *charinfo::setx_macro(macro *m, char_mode cm)
7809 macro *tem = mac;
7810 mac = m;
7811 mode = cm;
7812 return tem;
7815 void charinfo::set_number(int n)
7817 number = n;
7818 flags |= NUMBERED;
7821 int charinfo::get_number()
7823 assert(flags & NUMBERED);
7824 return number;
7827 symbol UNNAMED_SYMBOL("---");
7829 // For numbered characters not between 0 and 255, we make a symbol out
7830 // of the number and store them in this dictionary.
7832 dictionary numbered_charinfo_dictionary(11);
7834 charinfo *get_charinfo_by_number(int n)
7836 static charinfo *number_table[256];
7838 if (n >= 0 && n < 256) {
7839 charinfo *ci = number_table[n];
7840 if (!ci) {
7841 ci = new charinfo(UNNAMED_SYMBOL);
7842 ci->set_number(n);
7843 number_table[n] = ci;
7845 return ci;
7847 else {
7848 symbol ns(i_to_a(n));
7849 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
7850 if (!ci) {
7851 ci = new charinfo(UNNAMED_SYMBOL);
7852 ci->set_number(n);
7853 (void)numbered_charinfo_dictionary.lookup(ns, ci);
7855 return ci;
7859 int font::name_to_index(const char *nm)
7861 charinfo *ci;
7862 if (nm[1] == 0)
7863 ci = charset_table[nm[0] & 0xff];
7864 else if (nm[0] == '\\' && nm[2] == 0)
7865 ci = get_charinfo(symbol(nm + 1));
7866 else
7867 ci = get_charinfo(symbol(nm));
7868 if (ci == 0)
7869 return -1;
7870 else
7871 return ci->get_index();
7874 int font::number_to_index(int n)
7876 return get_charinfo_by_number(n)->get_index();