Implement string-valued registers \n[.m] and \n[.M] to return the
[s-roff.git] / src / roff / troff / input.cpp
blobabe54f31e2f1707008fff87a42031d532a59f492
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004
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 "dictionary.h"
24 #include "hvunits.h"
25 #include "env.h"
26 #include "request.h"
27 #include "node.h"
28 #include "reg.h"
29 #include "token.h"
30 #include "div.h"
31 #include "charinfo.h"
32 #include "stringclass.h"
33 #include "font.h"
34 #include "macropath.h"
35 #include "defs.h"
36 #include "input.h"
37 #include "unicode.h"
39 // Needed for getpid() and isatty()
40 #include "posix.h"
42 #include "nonposix.h"
44 #ifdef NEED_DECLARATION_PUTENV
45 extern "C" {
46 int putenv(const char *);
48 #endif /* NEED_DECLARATION_PUTENV */
50 #define MACRO_PREFIX "tmac."
51 #define MACRO_POSTFIX ".tmac"
52 #define INITIAL_STARTUP_FILE "troffrc"
53 #define FINAL_STARTUP_FILE "troffrc-end"
54 #define DEFAULT_INPUT_STACK_LIMIT 1000
56 #ifndef DEFAULT_WARNING_MASK
57 // warnings that are enabled by default
58 #define DEFAULT_WARNING_MASK \
59 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
60 #endif
62 // initial size of buffer for reading names; expanded as necessary
63 #define ABUF_SIZE 16
65 extern "C" const char *Version_string;
67 #ifdef COLUMN
68 void init_column_requests();
69 #endif /* COLUMN */
71 static node *read_draw_node();
72 static void read_color_draw_node(token &);
73 void handle_first_page_transition();
74 static void push_token(const token &);
75 void copy_file();
76 #ifdef COLUMN
77 void vjustify();
78 #endif /* COLUMN */
79 void transparent_file();
80 void process_input_stack();
82 const char *program_name = 0;
83 token tok;
84 int break_flag = 0;
85 int color_flag = 1; // colors are on by default
86 static int backtrace_flag = 0;
87 #ifndef POPEN_MISSING
88 char *pipe_command = 0;
89 #endif
90 charinfo *charset_table[256];
91 unsigned char hpf_code_table[256];
93 static int warning_mask = DEFAULT_WARNING_MASK;
94 static int inhibit_errors = 0;
95 static int ignoring = 0;
97 static void enable_warning(const char *);
98 static void disable_warning(const char *);
100 static int escape_char = '\\';
101 static symbol end_macro_name;
102 static symbol blank_line_macro_name;
103 static int compatible_flag = 0;
104 int ascii_output_flag = 0;
105 int suppress_output_flag = 0;
106 int is_html = 0;
107 int begin_level = 0; // number of nested .begin requests
109 int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
110 // \R, \s, or \S has been processed in
111 // token::next()
112 int old_have_input = 0; // value of have_input right before \n
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 // Defaults to the current directory.
126 search_path include_search_path(0, 0, 0, 1);
128 static int get_copy(node**, int = 0);
129 static void copy_mode_error(const char *,
130 const errarg & = empty_errarg,
131 const errarg & = empty_errarg,
132 const errarg & = empty_errarg);
134 enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
135 static symbol read_escape_name(read_mode mode = NO_ARGS);
136 static symbol read_long_escape_name(read_mode mode = NO_ARGS);
137 static void interpolate_string(symbol);
138 static void interpolate_string_with_args(symbol);
139 static void interpolate_macro(symbol);
140 static void interpolate_number_format(symbol);
141 static void interpolate_environment_variable(symbol);
143 static symbol composite_glyph_name(symbol);
144 static void interpolate_arg(symbol);
145 static request_or_macro *lookup_request(symbol);
146 static int get_delim_number(units *, int);
147 static int get_delim_number(units *, int, units);
148 static symbol do_get_long_name(int, char);
149 static int get_line_arg(units *res, int si, charinfo **cp);
150 static int read_size(int *);
151 static symbol get_delim_name();
152 static void init_registers();
153 static void trapping_blank_line();
155 struct input_iterator;
156 input_iterator *make_temp_iterator(const char *);
157 const char *input_char_description(int);
160 void set_escape_char()
162 if (has_arg()) {
163 if (tok.ch() == 0) {
164 error("bad escape character");
165 escape_char = '\\';
167 else
168 escape_char = tok.ch();
170 else
171 escape_char = '\\';
172 skip_line();
175 void escape_off()
177 escape_char = 0;
178 skip_line();
181 static int saved_escape_char = '\\';
183 void save_escape_char()
185 saved_escape_char = escape_char;
186 skip_line();
189 void restore_escape_char()
191 escape_char = saved_escape_char;
192 skip_line();
195 class input_iterator {
196 public:
197 input_iterator();
198 virtual ~input_iterator() {}
199 int get(node **);
200 friend class input_stack;
201 protected:
202 const unsigned char *ptr;
203 const unsigned char *eptr;
204 input_iterator *next;
205 private:
206 virtual int fill(node **);
207 virtual int peek();
208 virtual int has_args() { return 0; }
209 virtual int nargs() { return 0; }
210 virtual input_iterator *get_arg(int) { return 0; }
211 virtual int get_location(int, const char **, int *) { return 0; }
212 virtual void backtrace() {}
213 virtual int set_location(const char *, int) { return 0; }
214 virtual int next_file(FILE *, const char *) { return 0; }
215 virtual void shift(int) {}
216 virtual int is_boundary() {return 0; }
217 virtual int internal_level() { return 0; }
218 virtual int is_file() { return 0; }
219 virtual int is_macro() { return 0; }
220 virtual void save_compatible_flag(int) {}
221 virtual int get_compatible_flag() { return 0; }
224 input_iterator::input_iterator()
225 : ptr(0), eptr(0)
229 int input_iterator::fill(node **)
231 return EOF;
234 int input_iterator::peek()
236 return EOF;
239 inline int input_iterator::get(node **p)
241 return ptr < eptr ? *ptr++ : fill(p);
244 class input_boundary : public input_iterator {
245 public:
246 int is_boundary() { return 1; }
249 class input_return_boundary : public input_iterator {
250 public:
251 int is_boundary() { return 2; }
254 class file_iterator : public input_iterator {
255 FILE *fp;
256 int lineno;
257 const char *filename;
258 int popened;
259 int newline_flag;
260 int seen_escape;
261 enum { BUF_SIZE = 512 };
262 unsigned char buf[BUF_SIZE];
263 void close();
264 public:
265 file_iterator(FILE *, const char *, int = 0);
266 ~file_iterator();
267 int fill(node **);
268 int peek();
269 int get_location(int, const char **, int *);
270 void backtrace();
271 int set_location(const char *, int);
272 int next_file(FILE *, const char *);
273 int is_file();
276 file_iterator::file_iterator(FILE *f, const char *fn, int po)
277 : fp(f), lineno(1), filename(fn), popened(po),
278 newline_flag(0), seen_escape(0)
280 if ((font::use_charnames_in_special) && (fn != 0)) {
281 if (!the_output)
282 init_output();
283 the_output->put_filename(fn);
287 file_iterator::~file_iterator()
289 close();
292 void file_iterator::close()
294 if (fp == stdin)
295 clearerr(stdin);
296 #ifndef POPEN_MISSING
297 else if (popened)
298 pclose(fp);
299 #endif /* not POPEN_MISSING */
300 else
301 fclose(fp);
304 int file_iterator::is_file()
306 return 1;
309 int file_iterator::next_file(FILE *f, const char *s)
311 close();
312 filename = s;
313 fp = f;
314 lineno = 1;
315 newline_flag = 0;
316 seen_escape = 0;
317 popened = 0;
318 ptr = 0;
319 eptr = 0;
320 return 1;
323 int file_iterator::fill(node **)
325 if (newline_flag)
326 lineno++;
327 newline_flag = 0;
328 unsigned char *p = buf;
329 ptr = p;
330 unsigned char *e = p + BUF_SIZE;
331 while (p < e) {
332 int c = getc(fp);
333 if (c == EOF)
334 break;
335 if (invalid_input_char(c))
336 warning(WARN_INPUT, "invalid input character code %1", int(c));
337 else {
338 *p++ = c;
339 if (c == '\n') {
340 seen_escape = 0;
341 newline_flag = 1;
342 break;
344 seen_escape = (c == '\\');
347 if (p > buf) {
348 eptr = p;
349 return *ptr++;
351 else {
352 eptr = p;
353 return EOF;
357 int file_iterator::peek()
359 int c = getc(fp);
360 while (invalid_input_char(c)) {
361 warning(WARN_INPUT, "invalid input character code %1", int(c));
362 c = getc(fp);
364 if (c != EOF)
365 ungetc(c, fp);
366 return c;
369 int file_iterator::get_location(int /*allow_macro*/,
370 const char **filenamep, int *linenop)
372 *linenop = lineno;
373 if (filename != 0 && strcmp(filename, "-") == 0)
374 *filenamep = "<standard input>";
375 else
376 *filenamep = filename;
377 return 1;
380 void file_iterator::backtrace()
382 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
383 popened ? "process" : "file");
386 int file_iterator::set_location(const char *f, int ln)
388 if (f) {
389 filename = f;
390 if (!the_output)
391 init_output();
392 the_output->put_filename(f);
394 lineno = ln;
395 return 1;
398 input_iterator nil_iterator;
400 class input_stack {
401 public:
402 static int get(node **);
403 static int peek();
404 static void push(input_iterator *);
405 static input_iterator *get_arg(int);
406 static int nargs();
407 static int get_location(int, const char **, int *);
408 static int set_location(const char *, int);
409 static void backtrace();
410 static void backtrace_all();
411 static void next_file(FILE *, const char *);
412 static void end_file();
413 static void shift(int n);
414 static void add_boundary();
415 static void add_return_boundary();
416 static int is_return_boundary();
417 static void remove_boundary();
418 static int get_level();
419 static void clear();
420 static void pop_macro();
421 static void save_compatible_flag(int);
422 static int get_compatible_flag();
424 static int limit;
425 private:
426 static input_iterator *top;
427 static int level;
429 static int finish_get(node **);
430 static int finish_peek();
433 input_iterator *input_stack::top = &nil_iterator;
434 int input_stack::level = 0;
435 int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
437 inline int input_stack::get_level()
439 return level + top->internal_level();
442 inline int input_stack::get(node **np)
444 int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
445 if (res == '\n') {
446 old_have_input = have_input;
447 have_input = 0;
449 return res;
452 int input_stack::finish_get(node **np)
454 for (;;) {
455 int c = top->fill(np);
456 if (c != EOF || top->is_boundary())
457 return c;
458 if (top == &nil_iterator)
459 break;
460 input_iterator *tem = top;
461 top = top->next;
462 level--;
463 delete tem;
464 if (top->ptr < top->eptr)
465 return *top->ptr++;
467 assert(level == 0);
468 return EOF;
471 inline int input_stack::peek()
473 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
476 int input_stack::finish_peek()
478 for (;;) {
479 int c = top->peek();
480 if (c != EOF || top->is_boundary())
481 return c;
482 if (top == &nil_iterator)
483 break;
484 input_iterator *tem = top;
485 top = top->next;
486 level--;
487 delete tem;
488 if (top->ptr < top->eptr)
489 return *top->ptr;
491 assert(level == 0);
492 return EOF;
495 void input_stack::add_boundary()
497 push(new input_boundary);
500 void input_stack::add_return_boundary()
502 push(new input_return_boundary);
505 int input_stack::is_return_boundary()
507 return top->is_boundary() == 2;
510 void input_stack::remove_boundary()
512 assert(top->is_boundary());
513 input_iterator *temp = top->next;
514 delete top;
515 top = temp;
516 level--;
519 void input_stack::push(input_iterator *in)
521 if (in == 0)
522 return;
523 if (++level > limit && limit > 0)
524 fatal("input stack limit exceeded (probable infinite loop)");
525 in->next = top;
526 top = in;
529 input_iterator *input_stack::get_arg(int i)
531 input_iterator *p;
532 for (p = top; p != 0; p = p->next)
533 if (p->has_args())
534 return p->get_arg(i);
535 return 0;
538 void input_stack::shift(int n)
540 for (input_iterator *p = top; p; p = p->next)
541 if (p->has_args()) {
542 p->shift(n);
543 return;
547 int input_stack::nargs()
549 for (input_iterator *p =top; p != 0; p = p->next)
550 if (p->has_args())
551 return p->nargs();
552 return 0;
555 int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
557 for (input_iterator *p = top; p; p = p->next)
558 if (p->get_location(allow_macro, filenamep, linenop))
559 return 1;
560 return 0;
563 void input_stack::backtrace()
565 const char *f;
566 int n;
567 // only backtrace down to (not including) the topmost file
568 for (input_iterator *p = top;
569 p && !p->get_location(0, &f, &n);
570 p = p->next)
571 p->backtrace();
574 void input_stack::backtrace_all()
576 for (input_iterator *p = top; p; p = p->next)
577 p->backtrace();
580 int input_stack::set_location(const char *filename, int lineno)
582 for (input_iterator *p = top; p; p = p->next)
583 if (p->set_location(filename, lineno))
584 return 1;
585 return 0;
588 void input_stack::next_file(FILE *fp, const char *s)
590 input_iterator **pp;
591 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
592 if ((*pp)->next_file(fp, s))
593 return;
594 if (++level > limit && limit > 0)
595 fatal("input stack limit exceeded");
596 *pp = new file_iterator(fp, s);
597 (*pp)->next = &nil_iterator;
600 void input_stack::end_file()
602 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
603 if ((*pp)->is_file()) {
604 input_iterator *tem = *pp;
605 *pp = (*pp)->next;
606 delete tem;
607 level--;
608 return;
612 void input_stack::clear()
614 int nboundaries = 0;
615 while (top != &nil_iterator) {
616 if (top->is_boundary())
617 nboundaries++;
618 input_iterator *tem = top;
619 top = top->next;
620 level--;
621 delete tem;
623 // Keep while_request happy.
624 for (; nboundaries > 0; --nboundaries)
625 add_return_boundary();
628 void input_stack::pop_macro()
630 int nboundaries = 0;
631 int is_macro = 0;
632 do {
633 if (top->next == &nil_iterator)
634 break;
635 if (top->is_boundary())
636 nboundaries++;
637 is_macro = top->is_macro();
638 input_iterator *tem = top;
639 top = top->next;
640 level--;
641 delete tem;
642 } while (!is_macro);
643 // Keep while_request happy.
644 for (; nboundaries > 0; --nboundaries)
645 add_return_boundary();
648 inline void input_stack::save_compatible_flag(int f)
650 top->save_compatible_flag(f);
653 inline int input_stack::get_compatible_flag()
655 return top->get_compatible_flag();
658 void backtrace_request()
660 input_stack::backtrace_all();
661 fflush(stderr);
662 skip_line();
665 void next_file()
667 symbol nm = get_long_name();
668 while (!tok.newline() && !tok.eof())
669 tok.next();
670 if (nm.is_null())
671 input_stack::end_file();
672 else {
673 errno = 0;
674 FILE *fp = include_search_path.open_file_cautious(nm.contents());
675 if (!fp)
676 error("can't open `%1': %2", nm.contents(), strerror(errno));
677 else
678 input_stack::next_file(fp, nm.contents());
680 tok.next();
683 void shift()
685 int n;
686 if (!has_arg() || !get_integer(&n))
687 n = 1;
688 input_stack::shift(n);
689 skip_line();
692 static int get_char_for_escape_name(int allow_space = 0)
694 int c = get_copy(0);
695 switch (c) {
696 case EOF:
697 copy_mode_error("end of input in escape name");
698 return '\0';
699 default:
700 if (!invalid_input_char(c))
701 break;
702 // fall through
703 case '\n':
704 if (c == '\n')
705 input_stack::push(make_temp_iterator("\n"));
706 // fall through
707 case ' ':
708 if (c == ' ' && allow_space)
709 break;
710 // fall through
711 case '\t':
712 case '\001':
713 case '\b':
714 copy_mode_error("%1 is not allowed in an escape name",
715 input_char_description(c));
716 return '\0';
718 return c;
721 static symbol read_two_char_escape_name()
723 char buf[3];
724 buf[0] = get_char_for_escape_name();
725 if (buf[0] != '\0') {
726 buf[1] = get_char_for_escape_name();
727 if (buf[1] == '\0')
728 buf[0] = 0;
729 else
730 buf[2] = 0;
732 return symbol(buf);
735 static symbol read_long_escape_name(read_mode mode)
737 int start_level = input_stack::get_level();
738 char abuf[ABUF_SIZE];
739 char *buf = abuf;
740 int buf_size = ABUF_SIZE;
741 int i = 0;
742 int c;
743 int have_char = 0;
744 for (;;) {
745 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
746 if (c == 0) {
747 if (buf != abuf)
748 a_delete buf;
749 return NULL_SYMBOL;
751 have_char = 1;
752 if (mode == WITH_ARGS && c == ' ')
753 break;
754 if (i + 2 > buf_size) {
755 if (buf == abuf) {
756 buf = new char[ABUF_SIZE*2];
757 memcpy(buf, abuf, buf_size);
758 buf_size = ABUF_SIZE*2;
760 else {
761 char *old_buf = buf;
762 buf = new char[buf_size*2];
763 memcpy(buf, old_buf, buf_size);
764 buf_size *= 2;
765 a_delete old_buf;
768 if (c == ']' && input_stack::get_level() == start_level)
769 break;
770 buf[i++] = c;
772 buf[i] = 0;
773 if (c == ' ')
774 have_string_arg = 1;
775 if (buf == abuf) {
776 if (i == 0) {
777 if (mode != ALLOW_EMPTY)
778 copy_mode_error("empty escape name");
779 return EMPTY_SYMBOL;
781 return symbol(abuf);
783 else {
784 symbol s(buf);
785 a_delete buf;
786 return s;
790 static symbol read_escape_name(read_mode mode)
792 int c = get_char_for_escape_name();
793 if (c == 0)
794 return NULL_SYMBOL;
795 if (c == '(')
796 return read_two_char_escape_name();
797 if (c == '[' && !compatible_flag)
798 return read_long_escape_name(mode);
799 char buf[2];
800 buf[0] = c;
801 buf[1] = '\0';
802 return symbol(buf);
805 static symbol read_increment_and_escape_name(int *incp)
807 int c = get_char_for_escape_name();
808 switch (c) {
809 case 0:
810 *incp = 0;
811 return NULL_SYMBOL;
812 case '(':
813 *incp = 0;
814 return read_two_char_escape_name();
815 case '+':
816 *incp = 1;
817 return read_escape_name();
818 case '-':
819 *incp = -1;
820 return read_escape_name();
821 case '[':
822 if (!compatible_flag) {
823 *incp = 0;
824 return read_long_escape_name();
826 break;
828 *incp = 0;
829 char buf[2];
830 buf[0] = c;
831 buf[1] = '\0';
832 return symbol(buf);
835 static int get_copy(node **nd, int defining)
837 for (;;) {
838 int c = input_stack::get(nd);
839 if (c == ESCAPE_NEWLINE) {
840 if (defining)
841 return c;
842 do {
843 c = input_stack::get(nd);
844 } while (c == ESCAPE_NEWLINE);
846 if (c != escape_char || escape_char <= 0)
847 return c;
848 c = input_stack::peek();
849 switch(c) {
850 case 0:
851 return escape_char;
852 case '"':
853 (void)input_stack::get(0);
854 while ((c = input_stack::get(0)) != '\n' && c != EOF)
856 return c;
857 case '#': // Like \" but newline is ignored.
858 (void)input_stack::get(0);
859 while ((c = input_stack::get(0)) != '\n')
860 if (c == EOF)
861 return EOF;
862 break;
863 case '$':
865 (void)input_stack::get(0);
866 symbol s = read_escape_name();
867 if (!(s.is_null() || s.is_empty()))
868 interpolate_arg(s);
869 break;
871 case '*':
873 (void)input_stack::get(0);
874 symbol s = read_escape_name(WITH_ARGS);
875 if (!(s.is_null() || s.is_empty())) {
876 if (have_string_arg) {
877 have_string_arg = 0;
878 interpolate_string_with_args(s);
880 else
881 interpolate_string(s);
883 break;
885 case 'a':
886 (void)input_stack::get(0);
887 return '\001';
888 case 'e':
889 (void)input_stack::get(0);
890 return ESCAPE_e;
891 case 'E':
892 (void)input_stack::get(0);
893 return ESCAPE_E;
894 case 'n':
896 (void)input_stack::get(0);
897 int inc;
898 symbol s = read_increment_and_escape_name(&inc);
899 if (!(s.is_null() || s.is_empty()))
900 interpolate_number_reg(s, inc);
901 break;
903 case 'g':
905 (void)input_stack::get(0);
906 symbol s = read_escape_name();
907 if (!(s.is_null() || s.is_empty()))
908 interpolate_number_format(s);
909 break;
911 case 't':
912 (void)input_stack::get(0);
913 return '\t';
914 case 'V':
916 (void)input_stack::get(0);
917 symbol s = read_escape_name();
918 if (!(s.is_null() || s.is_empty()))
919 interpolate_environment_variable(s);
920 break;
922 case '\n':
923 (void)input_stack::get(0);
924 if (defining)
925 return ESCAPE_NEWLINE;
926 break;
927 case ' ':
928 (void)input_stack::get(0);
929 return ESCAPE_SPACE;
930 case '~':
931 (void)input_stack::get(0);
932 return ESCAPE_TILDE;
933 case ':':
934 (void)input_stack::get(0);
935 return ESCAPE_COLON;
936 case '|':
937 (void)input_stack::get(0);
938 return ESCAPE_BAR;
939 case '^':
940 (void)input_stack::get(0);
941 return ESCAPE_CIRCUMFLEX;
942 case '{':
943 (void)input_stack::get(0);
944 return ESCAPE_LEFT_BRACE;
945 case '}':
946 (void)input_stack::get(0);
947 return ESCAPE_RIGHT_BRACE;
948 case '`':
949 (void)input_stack::get(0);
950 return ESCAPE_LEFT_QUOTE;
951 case '\'':
952 (void)input_stack::get(0);
953 return ESCAPE_RIGHT_QUOTE;
954 case '-':
955 (void)input_stack::get(0);
956 return ESCAPE_HYPHEN;
957 case '_':
958 (void)input_stack::get(0);
959 return ESCAPE_UNDERSCORE;
960 case 'c':
961 (void)input_stack::get(0);
962 return ESCAPE_c;
963 case '!':
964 (void)input_stack::get(0);
965 return ESCAPE_BANG;
966 case '?':
967 (void)input_stack::get(0);
968 return ESCAPE_QUESTION;
969 case '&':
970 (void)input_stack::get(0);
971 return ESCAPE_AMPERSAND;
972 case ')':
973 (void)input_stack::get(0);
974 return ESCAPE_RIGHT_PARENTHESIS;
975 case '.':
976 (void)input_stack::get(0);
977 return c;
978 case '%':
979 (void)input_stack::get(0);
980 return ESCAPE_PERCENT;
981 default:
982 if (c == escape_char) {
983 (void)input_stack::get(0);
984 return c;
986 else
987 return escape_char;
992 class non_interpreted_char_node : public node {
993 unsigned char c;
994 public:
995 non_interpreted_char_node(unsigned char);
996 node *copy();
997 int interpret(macro *);
998 int same(node *);
999 const char *type();
1000 int force_tprint();
1003 int non_interpreted_char_node::same(node *nd)
1005 return c == ((non_interpreted_char_node *)nd)->c;
1008 const char *non_interpreted_char_node::type()
1010 return "non_interpreted_char_node";
1013 int non_interpreted_char_node::force_tprint()
1015 return 0;
1018 non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1020 assert(n != 0);
1023 node *non_interpreted_char_node::copy()
1025 return new non_interpreted_char_node(c);
1028 int non_interpreted_char_node::interpret(macro *mac)
1030 mac->append(c);
1031 return 1;
1034 static void do_width();
1035 static node *do_non_interpreted();
1036 static node *do_special();
1037 static node *do_suppress(symbol nm);
1038 static void do_register();
1040 dictionary color_dictionary(501);
1042 static color *lookup_color(symbol nm)
1044 assert(!nm.is_null());
1045 if (nm == default_symbol)
1046 return &default_color;
1047 color *c = (color *)color_dictionary.lookup(nm);
1048 if (c == 0)
1049 warning(WARN_COLOR, "color `%1' not defined", nm.contents());
1050 return c;
1053 void do_glyph_color(symbol nm)
1055 if (nm.is_null())
1056 return;
1057 if (nm.is_empty())
1058 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1059 else {
1060 color *tem = lookup_color(nm);
1061 if (tem)
1062 curenv->set_glyph_color(tem);
1063 else
1064 (void)color_dictionary.lookup(nm, new color(nm));
1068 void do_fill_color(symbol nm)
1070 if (nm.is_null())
1071 return;
1072 if (nm.is_empty())
1073 curenv->set_fill_color(curenv->get_prev_fill_color());
1074 else {
1075 color *tem = lookup_color(nm);
1076 if (tem)
1077 curenv->set_fill_color(tem);
1078 else
1079 (void)color_dictionary.lookup(nm, new color(nm));
1083 static unsigned int get_color_element(const char *scheme, const char *col)
1085 units val;
1086 if (!get_number(&val, 'f')) {
1087 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1088 tok.next();
1089 return 0;
1091 if (val < 0) {
1092 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1093 return 0;
1095 if (val > color::MAX_COLOR_VAL+1) {
1096 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1097 // we change 0x10000 to 0xffff
1098 return color::MAX_COLOR_VAL;
1100 return (unsigned int)val;
1103 static color *read_rgb(char end = 0)
1105 symbol component = do_get_long_name(0, end);
1106 if (component.is_null()) {
1107 warning(WARN_COLOR, "missing rgb color values");
1108 return 0;
1110 const char *s = component.contents();
1111 color *col = new color;
1112 if (*s == '#') {
1113 if (!col->read_rgb(s)) {
1114 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1115 delete col;
1116 return 0;
1119 else {
1120 if (!end)
1121 input_stack::push(make_temp_iterator(" "));
1122 input_stack::push(make_temp_iterator(s));
1123 tok.next();
1124 unsigned int r = get_color_element("rgb color", "red component");
1125 unsigned int g = get_color_element("rgb color", "green component");
1126 unsigned int b = get_color_element("rgb color", "blue component");
1127 col->set_rgb(r, g, b);
1129 return col;
1132 static color *read_cmy(char end = 0)
1134 symbol component = do_get_long_name(0, end);
1135 if (component.is_null()) {
1136 warning(WARN_COLOR, "missing cmy color values");
1137 return 0;
1139 const char *s = component.contents();
1140 color *col = new color;
1141 if (*s == '#') {
1142 if (!col->read_cmy(s)) {
1143 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1144 delete col;
1145 return 0;
1148 else {
1149 if (!end)
1150 input_stack::push(make_temp_iterator(" "));
1151 input_stack::push(make_temp_iterator(s));
1152 tok.next();
1153 unsigned int c = get_color_element("cmy color", "cyan component");
1154 unsigned int m = get_color_element("cmy color", "magenta component");
1155 unsigned int y = get_color_element("cmy color", "yellow component");
1156 col->set_cmy(c, m, y);
1158 return col;
1161 static color *read_cmyk(char end = 0)
1163 symbol component = do_get_long_name(0, end);
1164 if (component.is_null()) {
1165 warning(WARN_COLOR, "missing cmyk color values");
1166 return 0;
1168 const char *s = component.contents();
1169 color *col = new color;
1170 if (*s == '#') {
1171 if (!col->read_cmyk(s)) {
1172 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1173 delete col;
1174 return 0;
1177 else {
1178 if (!end)
1179 input_stack::push(make_temp_iterator(" "));
1180 input_stack::push(make_temp_iterator(s));
1181 tok.next();
1182 unsigned int c = get_color_element("cmyk color", "cyan component");
1183 unsigned int m = get_color_element("cmyk color", "magenta component");
1184 unsigned int y = get_color_element("cmyk color", "yellow component");
1185 unsigned int k = get_color_element("cmyk color", "black component");
1186 col->set_cmyk(c, m, y, k);
1188 return col;
1191 static color *read_gray(char end = 0)
1193 symbol component = do_get_long_name(0, end);
1194 if (component.is_null()) {
1195 warning(WARN_COLOR, "missing gray values");
1196 return 0;
1198 const char *s = component.contents();
1199 color *col = new color;
1200 if (*s == '#') {
1201 if (!col->read_gray(s)) {
1202 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1203 delete col;
1204 return 0;
1207 else {
1208 if (!end)
1209 input_stack::push(make_temp_iterator("\n"));
1210 input_stack::push(make_temp_iterator(s));
1211 tok.next();
1212 unsigned int g = get_color_element("gray", "gray value");
1213 col->set_gray(g);
1215 return col;
1218 static void activate_color()
1220 int n;
1221 if (has_arg() && get_integer(&n))
1222 color_flag = n != 0;
1223 else
1224 color_flag = 1;
1225 skip_line();
1228 static void define_color()
1230 symbol color_name = get_long_name(1);
1231 if (color_name.is_null()) {
1232 skip_line();
1233 return;
1235 if (color_name == default_symbol) {
1236 warning(WARN_COLOR, "default color can't be redefined");
1237 skip_line();
1238 return;
1240 symbol style = get_long_name(1);
1241 if (style.is_null()) {
1242 skip_line();
1243 return;
1245 color *col;
1246 if (strcmp(style.contents(), "rgb") == 0)
1247 col = read_rgb();
1248 else if (strcmp(style.contents(), "cmyk") == 0)
1249 col = read_cmyk();
1250 else if (strcmp(style.contents(), "gray") == 0)
1251 col = read_gray();
1252 else if (strcmp(style.contents(), "grey") == 0)
1253 col = read_gray();
1254 else if (strcmp(style.contents(), "cmy") == 0)
1255 col = read_cmy();
1256 else {
1257 warning(WARN_COLOR,
1258 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1259 style.contents());
1260 skip_line();
1261 return;
1263 if (col) {
1264 col->nm = color_name;
1265 (void)color_dictionary.lookup(color_name, col);
1267 skip_line();
1270 static node *do_overstrike()
1272 token start;
1273 overstrike_node *on = new overstrike_node;
1274 int start_level = input_stack::get_level();
1275 start.next();
1276 for (;;) {
1277 tok.next();
1278 if (tok.newline() || tok.eof()) {
1279 warning(WARN_DELIM, "missing closing delimiter");
1280 input_stack::push(make_temp_iterator("\n"));
1281 break;
1283 if (tok == start
1284 && (compatible_flag || input_stack::get_level() == start_level))
1285 break;
1286 charinfo *ci = tok.get_char(1);
1287 if (ci) {
1288 node *n = curenv->make_char_node(ci);
1289 if (n)
1290 on->overstrike(n);
1293 return on;
1296 static node *do_bracket()
1298 token start;
1299 bracket_node *bn = new bracket_node;
1300 start.next();
1301 int start_level = input_stack::get_level();
1302 for (;;) {
1303 tok.next();
1304 if (tok.eof()) {
1305 warning(WARN_DELIM, "missing closing delimiter");
1306 break;
1308 if (tok.newline()) {
1309 warning(WARN_DELIM, "missing closing delimiter");
1310 input_stack::push(make_temp_iterator("\n"));
1311 break;
1313 if (tok == start
1314 && (compatible_flag || input_stack::get_level() == start_level))
1315 break;
1316 charinfo *ci = tok.get_char(1);
1317 if (ci) {
1318 node *n = curenv->make_char_node(ci);
1319 if (n)
1320 bn->bracket(n);
1323 return bn;
1326 static int do_name_test()
1328 token start;
1329 start.next();
1330 int start_level = input_stack::get_level();
1331 int bad_char = 0;
1332 int some_char = 0;
1333 for (;;) {
1334 tok.next();
1335 if (tok.newline() || tok.eof()) {
1336 warning(WARN_DELIM, "missing closing delimiter");
1337 input_stack::push(make_temp_iterator("\n"));
1338 break;
1340 if (tok == start
1341 && (compatible_flag || input_stack::get_level() == start_level))
1342 break;
1343 if (!tok.ch())
1344 bad_char = 1;
1345 some_char = 1;
1347 return some_char && !bad_char;
1350 static int do_expr_test()
1352 token start;
1353 start.next();
1354 int start_level = input_stack::get_level();
1355 if (!start.delimiter(1))
1356 return 0;
1357 tok.next();
1358 // disable all warning and error messages temporarily
1359 int saved_warning_mask = warning_mask;
1360 int saved_inhibit_errors = inhibit_errors;
1361 warning_mask = 0;
1362 inhibit_errors = 1;
1363 int dummy;
1364 int result = get_number_rigidly(&dummy, 'u');
1365 warning_mask = saved_warning_mask;
1366 inhibit_errors = saved_inhibit_errors;
1367 if (tok == start && input_stack::get_level() == start_level)
1368 return result;
1369 // ignore everything up to the delimiter in case we aren't right there
1370 for (;;) {
1371 tok.next();
1372 if (tok.newline() || tok.eof()) {
1373 warning(WARN_DELIM, "missing closing delimiter");
1374 input_stack::push(make_temp_iterator("\n"));
1375 break;
1377 if (tok == start && input_stack::get_level() == start_level)
1378 break;
1380 return 0;
1383 #if 0
1384 static node *do_zero_width()
1386 token start;
1387 start.next();
1388 int start_level = input_stack::get_level();
1389 environment env(curenv);
1390 environment *oldenv = curenv;
1391 curenv = &env;
1392 for (;;) {
1393 tok.next();
1394 if (tok.newline() || tok.eof()) {
1395 error("missing closing delimiter");
1396 break;
1398 if (tok == start
1399 && (compatible_flag || input_stack::get_level() == start_level))
1400 break;
1401 tok.process();
1403 curenv = oldenv;
1404 node *rev = env.extract_output_line();
1405 node *n = 0;
1406 while (rev) {
1407 node *tem = rev;
1408 rev = rev->next;
1409 tem->next = n;
1410 n = tem;
1412 return new zero_width_node(n);
1415 #else
1417 // It's undesirable for \Z to change environments, because then
1418 // \n(.w won't work as expected.
1420 static node *do_zero_width()
1422 node *rev = new dummy_node;
1423 token start;
1424 start.next();
1425 int start_level = input_stack::get_level();
1426 for (;;) {
1427 tok.next();
1428 if (tok.newline() || tok.eof()) {
1429 warning(WARN_DELIM, "missing closing delimiter");
1430 input_stack::push(make_temp_iterator("\n"));
1431 break;
1433 if (tok == start
1434 && (compatible_flag || input_stack::get_level() == start_level))
1435 break;
1436 if (!tok.add_to_node_list(&rev))
1437 error("invalid token in argument to \\Z");
1439 node *n = 0;
1440 while (rev) {
1441 node *tem = rev;
1442 rev = rev->next;
1443 tem->next = n;
1444 n = tem;
1446 return new zero_width_node(n);
1449 #endif
1451 token_node *node::get_token_node()
1453 return 0;
1456 class token_node : public node {
1457 public:
1458 token tk;
1459 token_node(const token &t);
1460 node *copy();
1461 token_node *get_token_node();
1462 int same(node *);
1463 const char *type();
1464 int force_tprint();
1467 token_node::token_node(const token &t) : tk(t)
1471 node *token_node::copy()
1473 return new token_node(tk);
1476 token_node *token_node::get_token_node()
1478 return this;
1481 int token_node::same(node *nd)
1483 return tk == ((token_node *)nd)->tk;
1486 const char *token_node::type()
1488 return "token_node";
1491 int token_node::force_tprint()
1493 return 0;
1496 token::token() : nd(0), type(TOKEN_EMPTY)
1500 token::~token()
1502 delete nd;
1505 token::token(const token &t)
1506 : nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1508 // Use two statements to work around bug in SGI C++.
1509 node *tem = t.nd;
1510 nd = tem ? tem->copy() : 0;
1513 void token::operator=(const token &t)
1515 delete nd;
1516 nm = t.nm;
1517 // Use two statements to work around bug in SGI C++.
1518 node *tem = t.nd;
1519 nd = tem ? tem->copy() : 0;
1520 c = t.c;
1521 val = t.val;
1522 dim = t.dim;
1523 type = t.type;
1526 void token::skip()
1528 while (space())
1529 next();
1532 int has_arg()
1534 while (tok.space())
1535 tok.next();
1536 return !tok.newline();
1539 void token::make_space()
1541 type = TOKEN_SPACE;
1544 void token::make_newline()
1546 type = TOKEN_NEWLINE;
1549 void token::next()
1551 if (nd) {
1552 delete nd;
1553 nd = 0;
1555 units x;
1556 for (;;) {
1557 node *n;
1558 int cc = input_stack::get(&n);
1559 if (cc != escape_char || escape_char == 0) {
1560 handle_normal_char:
1561 switch(cc) {
1562 case COMPATIBLE_SAVE:
1563 input_stack::save_compatible_flag(compatible_flag);
1564 compatible_flag = 0;
1565 continue;
1566 case COMPATIBLE_RESTORE:
1567 compatible_flag = input_stack::get_compatible_flag();
1568 continue;
1569 case EOF:
1570 type = TOKEN_EOF;
1571 return;
1572 case TRANSPARENT_FILE_REQUEST:
1573 case TITLE_REQUEST:
1574 case COPY_FILE_REQUEST:
1575 #ifdef COLUMN
1576 case VJUSTIFY_REQUEST:
1577 #endif /* COLUMN */
1578 type = TOKEN_REQUEST;
1579 c = cc;
1580 return;
1581 case BEGIN_TRAP:
1582 type = TOKEN_BEGIN_TRAP;
1583 return;
1584 case END_TRAP:
1585 type = TOKEN_END_TRAP;
1586 return;
1587 case LAST_PAGE_EJECTOR:
1588 seen_last_page_ejector = 1;
1589 // fall through
1590 case PAGE_EJECTOR:
1591 type = TOKEN_PAGE_EJECTOR;
1592 return;
1593 case ESCAPE_PERCENT:
1594 ESCAPE_PERCENT:
1595 type = TOKEN_HYPHEN_INDICATOR;
1596 return;
1597 case ESCAPE_SPACE:
1598 ESCAPE_SPACE:
1599 type = TOKEN_UNSTRETCHABLE_SPACE;
1600 return;
1601 case ESCAPE_TILDE:
1602 ESCAPE_TILDE:
1603 type = TOKEN_STRETCHABLE_SPACE;
1604 return;
1605 case ESCAPE_COLON:
1606 ESCAPE_COLON:
1607 type = TOKEN_ZERO_WIDTH_BREAK;
1608 return;
1609 case ESCAPE_e:
1610 ESCAPE_e:
1611 type = TOKEN_ESCAPE;
1612 return;
1613 case ESCAPE_E:
1614 goto handle_escape_char;
1615 case ESCAPE_BAR:
1616 ESCAPE_BAR:
1617 type = TOKEN_NODE;
1618 nd = new hmotion_node(curenv->get_narrow_space_width(),
1619 curenv->get_fill_color());
1620 return;
1621 case ESCAPE_CIRCUMFLEX:
1622 ESCAPE_CIRCUMFLEX:
1623 type = TOKEN_NODE;
1624 nd = new hmotion_node(curenv->get_half_narrow_space_width(),
1625 curenv->get_fill_color());
1626 return;
1627 case ESCAPE_NEWLINE:
1628 have_input = 0;
1629 break;
1630 case ESCAPE_LEFT_BRACE:
1631 ESCAPE_LEFT_BRACE:
1632 type = TOKEN_LEFT_BRACE;
1633 return;
1634 case ESCAPE_RIGHT_BRACE:
1635 ESCAPE_RIGHT_BRACE:
1636 type = TOKEN_RIGHT_BRACE;
1637 return;
1638 case ESCAPE_LEFT_QUOTE:
1639 ESCAPE_LEFT_QUOTE:
1640 type = TOKEN_SPECIAL;
1641 nm = symbol("ga");
1642 return;
1643 case ESCAPE_RIGHT_QUOTE:
1644 ESCAPE_RIGHT_QUOTE:
1645 type = TOKEN_SPECIAL;
1646 nm = symbol("aa");
1647 return;
1648 case ESCAPE_HYPHEN:
1649 ESCAPE_HYPHEN:
1650 type = TOKEN_SPECIAL;
1651 nm = symbol("-");
1652 return;
1653 case ESCAPE_UNDERSCORE:
1654 ESCAPE_UNDERSCORE:
1655 type = TOKEN_SPECIAL;
1656 nm = symbol("ul");
1657 return;
1658 case ESCAPE_c:
1659 ESCAPE_c:
1660 type = TOKEN_INTERRUPT;
1661 return;
1662 case ESCAPE_BANG:
1663 ESCAPE_BANG:
1664 type = TOKEN_TRANSPARENT;
1665 return;
1666 case ESCAPE_QUESTION:
1667 ESCAPE_QUESTION:
1668 nd = do_non_interpreted();
1669 if (nd) {
1670 type = TOKEN_NODE;
1671 return;
1673 break;
1674 case ESCAPE_AMPERSAND:
1675 ESCAPE_AMPERSAND:
1676 type = TOKEN_DUMMY;
1677 return;
1678 case ESCAPE_RIGHT_PARENTHESIS:
1679 ESCAPE_RIGHT_PARENTHESIS:
1680 type = TOKEN_TRANSPARENT_DUMMY;
1681 return;
1682 case '\b':
1683 type = TOKEN_BACKSPACE;
1684 return;
1685 case ' ':
1686 type = TOKEN_SPACE;
1687 return;
1688 case '\t':
1689 type = TOKEN_TAB;
1690 return;
1691 case '\n':
1692 type = TOKEN_NEWLINE;
1693 return;
1694 case '\001':
1695 type = TOKEN_LEADER;
1696 return;
1697 case 0:
1699 assert(n != 0);
1700 token_node *tn = n->get_token_node();
1701 if (tn) {
1702 *this = tn->tk;
1703 delete tn;
1705 else {
1706 nd = n;
1707 type = TOKEN_NODE;
1710 return;
1711 default:
1712 type = TOKEN_CHAR;
1713 c = cc;
1714 return;
1717 else {
1718 handle_escape_char:
1719 cc = input_stack::get(&n);
1720 switch(cc) {
1721 case '(':
1722 nm = read_two_char_escape_name();
1723 type = TOKEN_SPECIAL;
1724 return;
1725 case EOF:
1726 type = TOKEN_EOF;
1727 error("end of input after escape character");
1728 return;
1729 case '`':
1730 goto ESCAPE_LEFT_QUOTE;
1731 case '\'':
1732 goto ESCAPE_RIGHT_QUOTE;
1733 case '-':
1734 goto ESCAPE_HYPHEN;
1735 case '_':
1736 goto ESCAPE_UNDERSCORE;
1737 case '%':
1738 goto ESCAPE_PERCENT;
1739 case ' ':
1740 goto ESCAPE_SPACE;
1741 case '0':
1742 nd = new hmotion_node(curenv->get_digit_width(),
1743 curenv->get_fill_color());
1744 type = TOKEN_NODE;
1745 return;
1746 case '|':
1747 goto ESCAPE_BAR;
1748 case '^':
1749 goto ESCAPE_CIRCUMFLEX;
1750 case '/':
1751 type = TOKEN_ITALIC_CORRECTION;
1752 return;
1753 case ',':
1754 type = TOKEN_NODE;
1755 nd = new left_italic_corrected_node;
1756 return;
1757 case '&':
1758 goto ESCAPE_AMPERSAND;
1759 case ')':
1760 goto ESCAPE_RIGHT_PARENTHESIS;
1761 case '!':
1762 goto ESCAPE_BANG;
1763 case '?':
1764 goto ESCAPE_QUESTION;
1765 case '~':
1766 goto ESCAPE_TILDE;
1767 case ':':
1768 goto ESCAPE_COLON;
1769 case '"':
1770 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1772 if (cc == '\n')
1773 type = TOKEN_NEWLINE;
1774 else
1775 type = TOKEN_EOF;
1776 return;
1777 case '#': // Like \" but newline is ignored.
1778 while ((cc = input_stack::get(0)) != '\n')
1779 if (cc == EOF) {
1780 type = TOKEN_EOF;
1781 return;
1783 break;
1784 case '$':
1786 symbol s = read_escape_name();
1787 if (!(s.is_null() || s.is_empty()))
1788 interpolate_arg(s);
1789 break;
1791 case '*':
1793 symbol s = read_escape_name(WITH_ARGS);
1794 if (!(s.is_null() || s.is_empty())) {
1795 if (have_string_arg) {
1796 have_string_arg = 0;
1797 interpolate_string_with_args(s);
1799 else
1800 interpolate_string(s);
1802 break;
1804 case 'a':
1805 nd = new non_interpreted_char_node('\001');
1806 type = TOKEN_NODE;
1807 return;
1808 case 'A':
1809 c = '0' + do_name_test();
1810 type = TOKEN_CHAR;
1811 return;
1812 case 'b':
1813 nd = do_bracket();
1814 type = TOKEN_NODE;
1815 return;
1816 case 'B':
1817 c = '0' + do_expr_test();
1818 type = TOKEN_CHAR;
1819 return;
1820 case 'c':
1821 goto ESCAPE_c;
1822 case 'C':
1823 nm = get_delim_name();
1824 if (nm.is_null())
1825 break;
1826 type = TOKEN_SPECIAL;
1827 return;
1828 case 'd':
1829 type = TOKEN_NODE;
1830 nd = new vmotion_node(curenv->get_size() / 2,
1831 curenv->get_fill_color());
1832 return;
1833 case 'D':
1834 nd = read_draw_node();
1835 if (!nd)
1836 break;
1837 type = TOKEN_NODE;
1838 return;
1839 case 'e':
1840 goto ESCAPE_e;
1841 case 'E':
1842 goto handle_escape_char;
1843 case 'f':
1845 symbol s = read_escape_name(ALLOW_EMPTY);
1846 if (s.is_null())
1847 break;
1848 const char *p;
1849 for (p = s.contents(); *p != '\0'; p++)
1850 if (!csdigit(*p))
1851 break;
1852 if (*p || s.is_empty())
1853 curenv->set_font(s);
1854 else
1855 curenv->set_font(atoi(s.contents()));
1856 if (!compatible_flag)
1857 have_input = 1;
1858 break;
1860 case 'F':
1862 symbol s = read_escape_name(ALLOW_EMPTY);
1863 if (s.is_null())
1864 break;
1865 curenv->set_family(s);
1866 have_input = 1;
1867 break;
1869 case 'g':
1871 symbol s = read_escape_name();
1872 if (!(s.is_null() || s.is_empty()))
1873 interpolate_number_format(s);
1874 break;
1876 case 'h':
1877 if (!get_delim_number(&x, 'm'))
1878 break;
1879 type = TOKEN_NODE;
1880 nd = new hmotion_node(x, curenv->get_fill_color());
1881 return;
1882 case 'H':
1883 // don't take height increments relative to previous height if
1884 // in compatibility mode
1885 if (!compatible_flag && curenv->get_char_height())
1887 if (get_delim_number(&x, 'z', curenv->get_char_height()))
1888 curenv->set_char_height(x);
1890 else
1892 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
1893 curenv->set_char_height(x);
1895 if (!compatible_flag)
1896 have_input = 1;
1897 break;
1898 case 'k':
1899 nm = read_escape_name();
1900 if (nm.is_null() || nm.is_empty())
1901 break;
1902 type = TOKEN_MARK_INPUT;
1903 return;
1904 case 'l':
1905 case 'L':
1907 charinfo *s = 0;
1908 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
1909 break;
1910 if (s == 0)
1911 s = get_charinfo(cc == 'l' ? "ru" : "br");
1912 type = TOKEN_NODE;
1913 node *n = curenv->make_char_node(s);
1914 if (cc == 'l')
1915 nd = new hline_node(x, n);
1916 else
1917 nd = new vline_node(x, n);
1918 return;
1920 case 'm':
1921 do_glyph_color(read_escape_name(ALLOW_EMPTY));
1922 if (!compatible_flag)
1923 have_input = 1;
1924 break;
1925 case 'M':
1926 do_fill_color(read_escape_name(ALLOW_EMPTY));
1927 if (!compatible_flag)
1928 have_input = 1;
1929 break;
1930 case 'n':
1932 int inc;
1933 symbol s = read_increment_and_escape_name(&inc);
1934 if (!(s.is_null() || s.is_empty()))
1935 interpolate_number_reg(s, inc);
1936 break;
1938 case 'N':
1939 if (!get_delim_number(&val, 0))
1940 break;
1941 type = TOKEN_NUMBERED_CHAR;
1942 return;
1943 case 'o':
1944 nd = do_overstrike();
1945 type = TOKEN_NODE;
1946 return;
1947 case 'O':
1948 nd = do_suppress(read_escape_name());
1949 if (!nd)
1950 break;
1951 type = TOKEN_NODE;
1952 return;
1953 case 'p':
1954 type = TOKEN_SPREAD;
1955 return;
1956 case 'r':
1957 type = TOKEN_NODE;
1958 nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
1959 return;
1960 case 'R':
1961 do_register();
1962 if (!compatible_flag)
1963 have_input = 1;
1964 break;
1965 case 's':
1966 if (read_size(&x))
1967 curenv->set_size(x);
1968 if (!compatible_flag)
1969 have_input = 1;
1970 break;
1971 case 'S':
1972 if (get_delim_number(&x, 0))
1973 curenv->set_char_slant(x);
1974 if (!compatible_flag)
1975 have_input = 1;
1976 break;
1977 case 't':
1978 type = TOKEN_NODE;
1979 nd = new non_interpreted_char_node('\t');
1980 return;
1981 case 'u':
1982 type = TOKEN_NODE;
1983 nd = new vmotion_node(-curenv->get_size() / 2,
1984 curenv->get_fill_color());
1985 return;
1986 case 'v':
1987 if (!get_delim_number(&x, 'v'))
1988 break;
1989 type = TOKEN_NODE;
1990 nd = new vmotion_node(x, curenv->get_fill_color());
1991 return;
1992 case 'V':
1994 symbol s = read_escape_name();
1995 if (!(s.is_null() || s.is_empty()))
1996 interpolate_environment_variable(s);
1997 break;
1999 case 'w':
2000 do_width();
2001 break;
2002 case 'x':
2003 if (!get_delim_number(&x, 'v'))
2004 break;
2005 type = TOKEN_NODE;
2006 nd = new extra_size_node(x);
2007 return;
2008 case 'X':
2009 nd = do_special();
2010 if (!nd)
2011 break;
2012 type = TOKEN_NODE;
2013 return;
2014 case 'Y':
2016 symbol s = read_escape_name();
2017 if (s.is_null() || s.is_empty())
2018 break;
2019 request_or_macro *p = lookup_request(s);
2020 macro *m = p->to_macro();
2021 if (!m) {
2022 error("can't transparently throughput a request");
2023 break;
2025 nd = new special_node(*m);
2026 type = TOKEN_NODE;
2027 return;
2029 case 'z':
2031 next();
2032 if (type == TOKEN_NODE)
2033 nd = new zero_width_node(nd);
2034 else {
2035 charinfo *ci = get_char(1);
2036 if (ci == 0)
2037 break;
2038 node *gn = curenv->make_char_node(ci);
2039 if (gn == 0)
2040 break;
2041 nd = new zero_width_node(gn);
2042 type = TOKEN_NODE;
2044 return;
2046 case 'Z':
2047 nd = do_zero_width();
2048 if (nd == 0)
2049 break;
2050 type = TOKEN_NODE;
2051 return;
2052 case '{':
2053 goto ESCAPE_LEFT_BRACE;
2054 case '}':
2055 goto ESCAPE_RIGHT_BRACE;
2056 case '\n':
2057 break;
2058 case '[':
2059 if (!compatible_flag) {
2060 symbol s = read_long_escape_name(WITH_ARGS);
2061 if (s.is_null() || s.is_empty())
2062 break;
2063 if (have_string_arg) {
2064 have_string_arg = 0;
2065 nm = composite_glyph_name(s);
2067 else {
2068 const char *gn = check_unicode_name(s.contents());
2069 if (gn) {
2070 const char *gn_decomposed = decompose_unicode(gn);
2071 if (gn_decomposed)
2072 gn = &gn_decomposed[1];
2073 const char *groff_gn = unicode_to_glyph_name(gn);
2074 if (groff_gn)
2075 nm = symbol(groff_gn);
2076 else {
2077 char *buf = new char[strlen(gn) + 1 + 1];
2078 strcpy(buf, "u");
2079 strcat(buf, gn);
2080 nm = symbol(buf);
2081 a_delete buf;
2084 else
2085 nm = symbol(s.contents());
2087 type = TOKEN_SPECIAL;
2088 return;
2090 goto handle_normal_char;
2091 default:
2092 if (cc != escape_char && cc != '.')
2093 warning(WARN_ESCAPE, "escape character ignored before %1",
2094 input_char_description(cc));
2095 goto handle_normal_char;
2101 int token::operator==(const token &t)
2103 if (type != t.type)
2104 return 0;
2105 switch(type) {
2106 case TOKEN_CHAR:
2107 return c == t.c;
2108 case TOKEN_SPECIAL:
2109 return nm == t.nm;
2110 case TOKEN_NUMBERED_CHAR:
2111 return val == t.val;
2112 default:
2113 return 1;
2117 int token::operator!=(const token &t)
2119 return !(*this == t);
2122 // is token a suitable delimiter (like ')?
2124 int token::delimiter(int err)
2126 switch(type) {
2127 case TOKEN_CHAR:
2128 switch(c) {
2129 case '0':
2130 case '1':
2131 case '2':
2132 case '3':
2133 case '4':
2134 case '5':
2135 case '6':
2136 case '7':
2137 case '8':
2138 case '9':
2139 case '+':
2140 case '-':
2141 case '/':
2142 case '*':
2143 case '%':
2144 case '<':
2145 case '>':
2146 case '=':
2147 case '&':
2148 case ':':
2149 case '(':
2150 case ')':
2151 case '.':
2152 if (err)
2153 error("cannot use character `%1' as a starting delimiter", char(c));
2154 return 0;
2155 default:
2156 return 1;
2158 case TOKEN_NODE:
2159 case TOKEN_SPACE:
2160 case TOKEN_STRETCHABLE_SPACE:
2161 case TOKEN_UNSTRETCHABLE_SPACE:
2162 case TOKEN_TAB:
2163 case TOKEN_NEWLINE:
2164 if (err)
2165 error("cannot use %1 as a starting delimiter", description());
2166 return 0;
2167 default:
2168 return 1;
2172 const char *token::description()
2174 static char buf[4];
2175 switch (type) {
2176 case TOKEN_BACKSPACE:
2177 return "a backspace character";
2178 case TOKEN_CHAR:
2179 buf[0] = '`';
2180 buf[1] = c;
2181 buf[2] = '\'';
2182 buf[3] = '\0';
2183 return buf;
2184 case TOKEN_DUMMY:
2185 return "`\\&'";
2186 case TOKEN_ESCAPE:
2187 return "`\\e'";
2188 case TOKEN_HYPHEN_INDICATOR:
2189 return "`\\%'";
2190 case TOKEN_INTERRUPT:
2191 return "`\\c'";
2192 case TOKEN_ITALIC_CORRECTION:
2193 return "`\\/'";
2194 case TOKEN_LEADER:
2195 return "a leader character";
2196 case TOKEN_LEFT_BRACE:
2197 return "`\\{'";
2198 case TOKEN_MARK_INPUT:
2199 return "`\\k'";
2200 case TOKEN_NEWLINE:
2201 return "newline";
2202 case TOKEN_NODE:
2203 return "a node";
2204 case TOKEN_NUMBERED_CHAR:
2205 return "`\\N'";
2206 case TOKEN_RIGHT_BRACE:
2207 return "`\\}'";
2208 case TOKEN_SPACE:
2209 return "a space";
2210 case TOKEN_SPECIAL:
2211 return "a special character";
2212 case TOKEN_SPREAD:
2213 return "`\\p'";
2214 case TOKEN_STRETCHABLE_SPACE:
2215 return "`\\~'";
2216 case TOKEN_UNSTRETCHABLE_SPACE:
2217 return "`\\ '";
2218 case TOKEN_TAB:
2219 return "a tab character";
2220 case TOKEN_TRANSPARENT:
2221 return "`\\!'";
2222 case TOKEN_TRANSPARENT_DUMMY:
2223 return "`\\)'";
2224 case TOKEN_ZERO_WIDTH_BREAK:
2225 return "`\\:'";
2226 case TOKEN_EOF:
2227 return "end of input";
2228 default:
2229 break;
2231 return "a magic token";
2234 void skip_line()
2236 while (!tok.newline())
2237 if (tok.eof())
2238 return;
2239 else
2240 tok.next();
2241 tok.next();
2244 void compatible()
2246 int n;
2247 if (has_arg() && get_integer(&n))
2248 compatible_flag = n != 0;
2249 else
2250 compatible_flag = 1;
2251 skip_line();
2254 static void empty_name_warning(int required)
2256 if (tok.newline() || tok.eof()) {
2257 if (required)
2258 warning(WARN_MISSING, "missing name");
2260 else if (tok.right_brace() || tok.tab()) {
2261 const char *start = tok.description();
2262 do {
2263 tok.next();
2264 } while (tok.space() || tok.right_brace() || tok.tab());
2265 if (!tok.newline() && !tok.eof())
2266 error("%1 is not allowed before an argument", start);
2267 else if (required)
2268 warning(WARN_MISSING, "missing name");
2270 else if (required)
2271 error("name expected (got %1)", tok.description());
2272 else
2273 error("name expected (got %1): treated as missing", tok.description());
2276 static void non_empty_name_warning()
2278 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2279 && !tok.right_brace()
2280 // We don't want to give a warning for .el\{
2281 && !tok.left_brace())
2282 error("%1 is not allowed in a name", tok.description());
2285 symbol get_name(int required)
2287 if (compatible_flag) {
2288 char buf[3];
2289 tok.skip();
2290 if ((buf[0] = tok.ch()) != 0) {
2291 tok.next();
2292 if ((buf[1] = tok.ch()) != 0) {
2293 buf[2] = 0;
2294 tok.make_space();
2296 else
2297 non_empty_name_warning();
2298 return symbol(buf);
2300 else {
2301 empty_name_warning(required);
2302 return NULL_SYMBOL;
2305 else
2306 return get_long_name(required);
2309 symbol get_long_name(int required)
2311 return do_get_long_name(required, 0);
2314 static symbol do_get_long_name(int required, char end)
2316 while (tok.space())
2317 tok.next();
2318 char abuf[ABUF_SIZE];
2319 char *buf = abuf;
2320 int buf_size = ABUF_SIZE;
2321 int i = 0;
2322 for (;;) {
2323 // If end != 0 we normally have to append a null byte
2324 if (i + 2 > buf_size) {
2325 if (buf == abuf) {
2326 buf = new char[ABUF_SIZE*2];
2327 memcpy(buf, abuf, buf_size);
2328 buf_size = ABUF_SIZE*2;
2330 else {
2331 char *old_buf = buf;
2332 buf = new char[buf_size*2];
2333 memcpy(buf, old_buf, buf_size);
2334 buf_size *= 2;
2335 a_delete old_buf;
2338 if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
2339 break;
2340 i++;
2341 tok.next();
2343 if (i == 0) {
2344 empty_name_warning(required);
2345 return NULL_SYMBOL;
2347 if (end && buf[i] == end)
2348 buf[i+1] = '\0';
2349 else
2350 non_empty_name_warning();
2351 if (buf == abuf)
2352 return symbol(buf);
2353 else {
2354 symbol s(buf);
2355 a_delete buf;
2356 return s;
2360 void exit_troff()
2362 exit_started = 1;
2363 topdiv->set_last_page();
2364 if (!end_macro_name.is_null()) {
2365 spring_trap(end_macro_name);
2366 tok.next();
2367 process_input_stack();
2369 curenv->final_break();
2370 tok.next();
2371 process_input_stack();
2372 end_diversions();
2373 if (topdiv->get_page_length() > 0) {
2374 done_end_macro = 1;
2375 topdiv->set_ejecting();
2376 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2377 input_stack::push(make_temp_iterator((char *)buf));
2378 topdiv->space(topdiv->get_page_length(), 1);
2379 tok.next();
2380 process_input_stack();
2381 seen_last_page_ejector = 1; // should be set already
2382 topdiv->set_ejecting();
2383 push_page_ejector();
2384 topdiv->space(topdiv->get_page_length(), 1);
2385 tok.next();
2386 process_input_stack();
2388 // This will only happen if a trap-invoked macro starts a diversion,
2389 // or if vertical position traps have been disabled.
2390 cleanup_and_exit(0);
2393 // This implements .ex. The input stack must be cleared before calling
2394 // exit_troff().
2396 void exit_request()
2398 input_stack::clear();
2399 if (exit_started)
2400 tok.next();
2401 else
2402 exit_troff();
2405 void return_macro_request()
2407 if (has_arg() && tok.ch())
2408 input_stack::pop_macro();
2409 input_stack::pop_macro();
2410 tok.next();
2413 void end_macro()
2415 end_macro_name = get_name();
2416 skip_line();
2419 void blank_line_macro()
2421 blank_line_macro_name = get_name();
2422 skip_line();
2425 static void trapping_blank_line()
2427 if (!blank_line_macro_name.is_null())
2428 spring_trap(blank_line_macro_name);
2429 else
2430 blank_line();
2433 void do_request()
2435 int old_compatible_flag = compatible_flag;
2436 compatible_flag = 0;
2437 symbol nm = get_name();
2438 if (nm.is_null())
2439 skip_line();
2440 else
2441 interpolate_macro(nm);
2442 compatible_flag = old_compatible_flag;
2445 inline int possibly_handle_first_page_transition()
2447 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2448 handle_first_page_transition();
2449 return 1;
2451 else
2452 return 0;
2455 static int transparent_translate(int cc)
2457 if (!invalid_input_char(cc)) {
2458 charinfo *ci = charset_table[cc];
2459 switch (ci->get_special_translation(1)) {
2460 case charinfo::TRANSLATE_SPACE:
2461 return ' ';
2462 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2463 return ESCAPE_TILDE;
2464 case charinfo::TRANSLATE_DUMMY:
2465 return ESCAPE_AMPERSAND;
2466 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2467 return ESCAPE_PERCENT;
2469 // This is really ugly.
2470 ci = ci->get_translation(1);
2471 if (ci) {
2472 int c = ci->get_ascii_code();
2473 if (c != '\0')
2474 return c;
2475 error("can't translate %1 to special character `%2'"
2476 " in transparent throughput",
2477 input_char_description(cc),
2478 ci->nm.contents());
2481 return cc;
2484 class int_stack {
2485 struct int_stack_element {
2486 int n;
2487 int_stack_element *next;
2488 } *top;
2489 public:
2490 int_stack();
2491 ~int_stack();
2492 void push(int);
2493 int is_empty();
2494 int pop();
2497 int_stack::int_stack()
2499 top = 0;
2502 int_stack::~int_stack()
2504 while (top != 0) {
2505 int_stack_element *temp = top;
2506 top = top->next;
2507 delete temp;
2511 int int_stack::is_empty()
2513 return top == 0;
2516 void int_stack::push(int n)
2518 int_stack_element *p = new int_stack_element;
2519 p->next = top;
2520 p->n = n;
2521 top = p;
2524 int int_stack::pop()
2526 assert(top != 0);
2527 int_stack_element *p = top;
2528 top = top->next;
2529 int n = p->n;
2530 delete p;
2531 return n;
2534 int node::reread(int *)
2536 return 0;
2539 int diverted_space_node::reread(int *bolp)
2541 if (curenv->get_fill())
2542 trapping_blank_line();
2543 else
2544 curdiv->space(n);
2545 *bolp = 1;
2546 return 1;
2549 int diverted_copy_file_node::reread(int *bolp)
2551 curdiv->copy_file(filename.contents());
2552 *bolp = 1;
2553 return 1;
2556 int word_space_node::reread(int *)
2558 if (unformat) {
2559 for (width_list *w = orig_width; w; w = w->next)
2560 curenv->space(w->width, w->sentence_width);
2561 unformat = 0;
2562 return 1;
2564 return 0;
2567 int unbreakable_space_node::reread(int *)
2569 return 0;
2572 int hmotion_node::reread(int *)
2574 if (unformat && was_tab) {
2575 curenv->handle_tab(0);
2576 unformat = 0;
2577 return 1;
2579 return 0;
2582 void process_input_stack()
2584 int_stack trap_bol_stack;
2585 int bol = 1;
2586 for (;;) {
2587 int suppress_next = 0;
2588 switch (tok.type) {
2589 case token::TOKEN_CHAR:
2591 unsigned char ch = tok.c;
2592 if (bol && !have_input
2593 && (ch == curenv->control_char
2594 || ch == curenv->no_break_control_char)) {
2595 break_flag = ch == curenv->control_char;
2596 // skip tabs as well as spaces here
2597 do {
2598 tok.next();
2599 } while (tok.white_space());
2600 symbol nm = get_name();
2601 if (nm.is_null())
2602 skip_line();
2603 else
2604 interpolate_macro(nm);
2605 suppress_next = 1;
2607 else {
2608 if (possibly_handle_first_page_transition())
2610 else {
2611 for (;;) {
2612 curenv->add_char(charset_table[ch]);
2613 tok.next();
2614 if (tok.type != token::TOKEN_CHAR)
2615 break;
2616 ch = tok.c;
2618 suppress_next = 1;
2619 bol = 0;
2622 break;
2624 case token::TOKEN_TRANSPARENT:
2626 if (bol) {
2627 if (possibly_handle_first_page_transition())
2629 else {
2630 int cc;
2631 do {
2632 node *n;
2633 cc = get_copy(&n);
2634 if (cc != EOF)
2635 if (cc != '\0')
2636 curdiv->transparent_output(transparent_translate(cc));
2637 else
2638 curdiv->transparent_output(n);
2639 } while (cc != '\n' && cc != EOF);
2640 if (cc == EOF)
2641 curdiv->transparent_output('\n');
2644 break;
2646 case token::TOKEN_NEWLINE:
2648 if (bol && !old_have_input
2649 && !curenv->get_prev_line_interrupted())
2650 trapping_blank_line();
2651 else {
2652 curenv->newline();
2653 bol = 1;
2655 break;
2657 case token::TOKEN_REQUEST:
2659 int request_code = tok.c;
2660 tok.next();
2661 switch (request_code) {
2662 case TITLE_REQUEST:
2663 title();
2664 break;
2665 case COPY_FILE_REQUEST:
2666 copy_file();
2667 break;
2668 case TRANSPARENT_FILE_REQUEST:
2669 transparent_file();
2670 break;
2671 #ifdef COLUMN
2672 case VJUSTIFY_REQUEST:
2673 vjustify();
2674 break;
2675 #endif /* COLUMN */
2676 default:
2677 assert(0);
2678 break;
2680 suppress_next = 1;
2681 break;
2683 case token::TOKEN_SPACE:
2685 if (possibly_handle_first_page_transition())
2687 else if (bol && !curenv->get_prev_line_interrupted()) {
2688 int nspaces = 0;
2689 // save space_width now so that it isn't changed by \f or \s
2690 // which we wouldn't notice here
2691 hunits space_width = curenv->get_space_width();
2692 do {
2693 nspaces += tok.nspaces();
2694 tok.next();
2695 } while (tok.space());
2696 if (tok.newline())
2697 trapping_blank_line();
2698 else {
2699 push_token(tok);
2700 curenv->do_break();
2701 curenv->add_node(new hmotion_node(space_width * nspaces,
2702 curenv->get_fill_color()));
2703 bol = 0;
2706 else {
2707 curenv->space();
2708 bol = 0;
2710 break;
2712 case token::TOKEN_EOF:
2713 return;
2714 case token::TOKEN_NODE:
2716 if (possibly_handle_first_page_transition())
2718 else if (tok.nd->reread(&bol)) {
2719 delete tok.nd;
2720 tok.nd = 0;
2722 else {
2723 curenv->add_node(tok.nd);
2724 tok.nd = 0;
2725 bol = 0;
2726 curenv->possibly_break_line(1);
2728 break;
2730 case token::TOKEN_PAGE_EJECTOR:
2732 continue_page_eject();
2733 // I think we just want to preserve bol.
2734 // bol = 1;
2735 break;
2737 case token::TOKEN_BEGIN_TRAP:
2739 trap_bol_stack.push(bol);
2740 bol = 1;
2741 have_input = 0;
2742 break;
2744 case token::TOKEN_END_TRAP:
2746 if (trap_bol_stack.is_empty())
2747 error("spurious end trap token detected!");
2748 else
2749 bol = trap_bol_stack.pop();
2750 have_input = 0;
2752 /* I'm not totally happy about this. But I can't think of any other
2753 way to do it. Doing an output_pending_lines() whenever a
2754 TOKEN_END_TRAP is detected doesn't work: for example,
2756 .wh -1i x
2757 .de x
2760 .wh -.5i y
2761 .de y
2762 .tl ''-%-''
2765 .ll .5i
2766 .sp |\n(.pu-1i-.5v
2767 a\%very\%very\%long\%word
2769 will print all but the first lines from the word immediately
2770 after the footer, rather than on the next page. */
2772 if (trap_bol_stack.is_empty())
2773 curenv->output_pending_lines();
2774 break;
2776 default:
2778 bol = 0;
2779 tok.process();
2780 break;
2783 if (!suppress_next)
2784 tok.next();
2785 trap_sprung_flag = 0;
2789 #ifdef WIDOW_CONTROL
2791 void flush_pending_lines()
2793 while (!tok.newline() && !tok.eof())
2794 tok.next();
2795 curenv->output_pending_lines();
2796 tok.next();
2799 #endif /* WIDOW_CONTROL */
2801 request_or_macro::request_or_macro()
2805 macro *request_or_macro::to_macro()
2807 return 0;
2810 request::request(REQUEST_FUNCP pp) : p(pp)
2814 void request::invoke(symbol)
2816 (*p)();
2819 struct char_block {
2820 enum { SIZE = 128 };
2821 unsigned char s[SIZE];
2822 char_block *next;
2823 char_block();
2826 char_block::char_block()
2827 : next(0)
2831 class char_list {
2832 public:
2833 char_list();
2834 ~char_list();
2835 void append(unsigned char);
2836 void set(unsigned char, int);
2837 unsigned char get(int);
2838 int length();
2839 private:
2840 unsigned char *ptr;
2841 int len;
2842 char_block *head;
2843 char_block *tail;
2844 friend class macro_header;
2845 friend class string_iterator;
2848 char_list::char_list()
2849 : ptr(0), len(0), head(0), tail(0)
2853 char_list::~char_list()
2855 while (head != 0) {
2856 char_block *tem = head;
2857 head = head->next;
2858 delete tem;
2862 int char_list::length()
2864 return len;
2867 void char_list::append(unsigned char c)
2869 if (tail == 0) {
2870 head = tail = new char_block;
2871 ptr = tail->s;
2873 else {
2874 if (ptr >= tail->s + char_block::SIZE) {
2875 tail->next = new char_block;
2876 tail = tail->next;
2877 ptr = tail->s;
2880 *ptr++ = c;
2881 len++;
2884 void char_list::set(unsigned char c, int offset)
2886 assert(len > offset);
2887 // optimization for access at the end
2888 int boundary = len - len % char_block::SIZE;
2889 if (offset >= boundary) {
2890 *(tail->s + offset - boundary) = c;
2891 return;
2893 char_block *tem = head;
2894 int l = 0;
2895 for (;;) {
2896 l += char_block::SIZE;
2897 if (l > offset) {
2898 *(tem->s + offset % char_block::SIZE) = c;
2899 return;
2901 tem = tem->next;
2905 unsigned char char_list::get(int offset)
2907 assert(len > offset);
2908 // optimization for access at the end
2909 int boundary = len - len % char_block::SIZE;
2910 if (offset >= boundary)
2911 return *(tail->s + offset - boundary);
2912 char_block *tem = head;
2913 int l = 0;
2914 for (;;) {
2915 l += char_block::SIZE;
2916 if (l > offset)
2917 return *(tem->s + offset % char_block::SIZE);
2918 tem = tem->next;
2922 class node_list {
2923 node *head;
2924 node *tail;
2925 public:
2926 node_list();
2927 ~node_list();
2928 void append(node *);
2929 int length();
2930 node *extract();
2932 friend class macro_header;
2933 friend class string_iterator;
2936 void node_list::append(node *n)
2938 if (head == 0) {
2939 n->next = 0;
2940 head = tail = n;
2942 else {
2943 n->next = 0;
2944 tail = tail->next = n;
2948 int node_list::length()
2950 int total = 0;
2951 for (node *n = head; n != 0; n = n->next)
2952 ++total;
2953 return total;
2956 node_list::node_list()
2958 head = tail = 0;
2961 node *node_list::extract()
2963 node *temp = head;
2964 head = tail = 0;
2965 return temp;
2968 node_list::~node_list()
2970 delete_node_list(head);
2973 struct macro_header {
2974 public:
2975 int count;
2976 char_list cl;
2977 node_list nl;
2978 macro_header() { count = 1; }
2979 macro_header *copy(int);
2982 macro::~macro()
2984 if (p != 0 && --(p->count) <= 0)
2985 delete p;
2988 macro::macro()
2990 if (!input_stack::get_location(1, &filename, &lineno)) {
2991 filename = 0;
2992 lineno = 0;
2994 len = 0;
2995 empty_macro = 1;
2996 p = 0;
2999 macro::macro(const macro &m)
3000 : p(m.p), filename(m.filename), lineno(m.lineno), len(m.len),
3001 empty_macro(m.empty_macro)
3003 if (p != 0)
3004 p->count++;
3007 macro &macro::operator=(const macro &m)
3009 // don't assign object
3010 if (m.p != 0)
3011 m.p->count++;
3012 if (p != 0 && --(p->count) <= 0)
3013 delete p;
3014 p = m.p;
3015 filename = m.filename;
3016 lineno = m.lineno;
3017 len = m.len;
3018 empty_macro = m.empty_macro;
3019 return *this;
3022 void macro::append(unsigned char c)
3024 assert(c != 0);
3025 if (p == 0)
3026 p = new macro_header;
3027 if (p->cl.length() != len) {
3028 macro_header *tem = p->copy(len);
3029 if (--(p->count) <= 0)
3030 delete p;
3031 p = tem;
3033 p->cl.append(c);
3034 ++len;
3035 if (c != COMPATIBLE_SAVE && c != COMPATIBLE_RESTORE)
3036 empty_macro = 0;
3039 void macro::set(unsigned char c, int offset)
3041 assert(p != 0);
3042 assert(c != 0);
3043 p->cl.set(c, offset);
3046 unsigned char macro::get(int offset)
3048 assert(p != 0);
3049 return p->cl.get(offset);
3052 int macro::length()
3054 return len;
3057 void macro::append_str(const char *s)
3059 int i = 0;
3061 if (s) {
3062 while (s[i] != (char)0) {
3063 append(s[i]);
3064 i++;
3069 void macro::append(node *n)
3071 assert(n != 0);
3072 if (p == 0)
3073 p = new macro_header;
3074 if (p->cl.length() != len) {
3075 macro_header *tem = p->copy(len);
3076 if (--(p->count) <= 0)
3077 delete p;
3078 p = tem;
3080 p->cl.append(0);
3081 p->nl.append(n);
3082 ++len;
3083 empty_macro = 0;
3086 void macro::append_unsigned(unsigned int i)
3088 unsigned int j = i / 10;
3089 if (j != 0)
3090 append_unsigned(j);
3091 append(((unsigned char)(((int)'0') + i % 10)));
3094 void macro::append_int(int i)
3096 if (i < 0) {
3097 append('-');
3098 i = -i;
3100 append_unsigned((unsigned int)i);
3103 void macro::print_size()
3105 errprint("%1", len);
3108 // make a copy of the first n bytes
3110 macro_header *macro_header::copy(int n)
3112 macro_header *p = new macro_header;
3113 char_block *bp = cl.head;
3114 unsigned char *ptr = bp->s;
3115 node *nd = nl.head;
3116 while (--n >= 0) {
3117 if (ptr >= bp->s + char_block::SIZE) {
3118 bp = bp->next;
3119 ptr = bp->s;
3121 int c = *ptr++;
3122 p->cl.append(c);
3123 if (c == 0) {
3124 p->nl.append(nd->copy());
3125 nd = nd->next;
3128 return p;
3131 void print_macros()
3133 object_dictionary_iterator iter(request_dictionary);
3134 request_or_macro *rm;
3135 symbol s;
3136 while (iter.get(&s, (object **)&rm)) {
3137 assert(!s.is_null());
3138 macro *m = rm->to_macro();
3139 if (m) {
3140 errprint("%1\t", s.contents());
3141 m->print_size();
3142 errprint("\n");
3145 fflush(stderr);
3146 skip_line();
3149 class string_iterator : public input_iterator {
3150 macro mac;
3151 const char *how_invoked;
3152 int newline_flag;
3153 int lineno;
3154 char_block *bp;
3155 int count; // of characters remaining
3156 node *nd;
3157 int saved_compatible_flag;
3158 protected:
3159 symbol nm;
3160 string_iterator();
3161 public:
3162 string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL);
3163 int fill(node **);
3164 int peek();
3165 int get_location(int, const char **, int *);
3166 void backtrace();
3167 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3168 int get_compatible_flag() { return saved_compatible_flag; }
3171 string_iterator::string_iterator(const macro &m, const char *p, symbol s)
3172 : mac(m), how_invoked(p),
3173 newline_flag(0), lineno(1), nm(s)
3175 count = mac.len;
3176 if (count != 0) {
3177 bp = mac.p->cl.head;
3178 nd = mac.p->nl.head;
3179 ptr = eptr = bp->s;
3181 else {
3182 bp = 0;
3183 nd = 0;
3184 ptr = eptr = 0;
3188 string_iterator::string_iterator()
3190 bp = 0;
3191 nd = 0;
3192 ptr = eptr = 0;
3193 newline_flag = 0;
3194 how_invoked = 0;
3195 lineno = 1;
3196 count = 0;
3199 int string_iterator::fill(node **np)
3201 if (newline_flag)
3202 lineno++;
3203 newline_flag = 0;
3204 if (count <= 0)
3205 return EOF;
3206 const unsigned char *p = eptr;
3207 if (p >= bp->s + char_block::SIZE) {
3208 bp = bp->next;
3209 p = bp->s;
3211 if (*p == '\0') {
3212 if (np)
3213 *np = nd->copy();
3214 nd = nd->next;
3215 eptr = ptr = p + 1;
3216 count--;
3217 return 0;
3219 const unsigned char *e = bp->s + char_block::SIZE;
3220 if (e - p > count)
3221 e = p + count;
3222 ptr = p;
3223 while (p < e) {
3224 unsigned char c = *p;
3225 if (c == '\n' || c == ESCAPE_NEWLINE) {
3226 newline_flag = 1;
3227 p++;
3228 break;
3230 if (c == '\0')
3231 break;
3232 p++;
3234 eptr = p;
3235 count -= p - ptr;
3236 return *ptr++;
3239 int string_iterator::peek()
3241 if (count <= 0)
3242 return EOF;
3243 const unsigned char *p = eptr;
3244 if (p >= bp->s + char_block::SIZE) {
3245 p = bp->next->s;
3247 return *p;
3250 int string_iterator::get_location(int allow_macro,
3251 const char **filep, int *linep)
3253 if (!allow_macro)
3254 return 0;
3255 if (mac.filename == 0)
3256 return 0;
3257 *filep = mac.filename;
3258 *linep = mac.lineno + lineno - 1;
3259 return 1;
3262 void string_iterator::backtrace()
3264 if (mac.filename) {
3265 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3266 if (how_invoked) {
3267 if (!nm.is_null())
3268 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3269 else
3270 errprint(": %1\n", how_invoked);
3272 else
3273 errprint("\n");
3277 class temp_iterator : public input_iterator {
3278 unsigned char *base;
3279 temp_iterator(const char *, int len);
3280 public:
3281 ~temp_iterator();
3282 friend input_iterator *make_temp_iterator(const char *);
3285 #ifdef __GNUG__
3286 inline
3287 #endif
3288 temp_iterator::temp_iterator(const char *s, int len)
3290 base = new unsigned char[len];
3291 memcpy(base, s, len);
3292 ptr = base;
3293 eptr = base + len;
3296 temp_iterator::~temp_iterator()
3298 a_delete base;
3301 class small_temp_iterator : public input_iterator {
3302 private:
3303 small_temp_iterator(const char *, int);
3304 ~small_temp_iterator();
3305 enum { BLOCK = 16 };
3306 static small_temp_iterator *free_list;
3307 void *operator new(size_t);
3308 void operator delete(void *);
3309 enum { SIZE = 12 };
3310 unsigned char buf[SIZE];
3311 friend input_iterator *make_temp_iterator(const char *);
3314 small_temp_iterator *small_temp_iterator::free_list = 0;
3316 void *small_temp_iterator::operator new(size_t n)
3318 assert(n == sizeof(small_temp_iterator));
3319 if (!free_list) {
3320 free_list =
3321 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3322 for (int i = 0; i < BLOCK - 1; i++)
3323 free_list[i].next = free_list + i + 1;
3324 free_list[BLOCK-1].next = 0;
3326 small_temp_iterator *p = free_list;
3327 free_list = (small_temp_iterator *)(free_list->next);
3328 p->next = 0;
3329 return p;
3332 #ifdef __GNUG__
3333 inline
3334 #endif
3335 void small_temp_iterator::operator delete(void *p)
3337 if (p) {
3338 ((small_temp_iterator *)p)->next = free_list;
3339 free_list = (small_temp_iterator *)p;
3343 small_temp_iterator::~small_temp_iterator()
3347 #ifdef __GNUG__
3348 inline
3349 #endif
3350 small_temp_iterator::small_temp_iterator(const char *s, int len)
3352 for (int i = 0; i < len; i++)
3353 buf[i] = s[i];
3354 ptr = buf;
3355 eptr = buf + len;
3358 input_iterator *make_temp_iterator(const char *s)
3360 if (s == 0)
3361 return new small_temp_iterator(s, 0);
3362 else {
3363 int n = strlen(s);
3364 if (n <= small_temp_iterator::SIZE)
3365 return new small_temp_iterator(s, n);
3366 else
3367 return new temp_iterator(s, n);
3371 // this is used when macros with arguments are interpolated
3373 struct arg_list {
3374 macro mac;
3375 arg_list *next;
3376 arg_list(const macro &);
3377 ~arg_list();
3380 arg_list::arg_list(const macro &m) : mac(m), next(0)
3384 arg_list::~arg_list()
3388 class macro_iterator : public string_iterator {
3389 arg_list *args;
3390 int argc;
3391 public:
3392 macro_iterator(symbol, macro &, const char *how_invoked = "macro");
3393 macro_iterator();
3394 ~macro_iterator();
3395 int has_args() { return 1; }
3396 input_iterator *get_arg(int i);
3397 int nargs() { return argc; }
3398 void add_arg(const macro &m);
3399 void shift(int n);
3400 int is_macro() { return 1; }
3403 input_iterator *macro_iterator::get_arg(int i)
3405 if (i == 0)
3406 return make_temp_iterator(nm.contents());
3407 if (i > 0 && i <= argc) {
3408 arg_list *p = args;
3409 for (int j = 1; j < i; j++) {
3410 assert(p != 0);
3411 p = p->next;
3413 return new string_iterator(p->mac);
3415 else
3416 return 0;
3419 void macro_iterator::add_arg(const macro &m)
3421 arg_list **p;
3422 for (p = &args; *p; p = &((*p)->next))
3424 *p = new arg_list(m);
3425 ++argc;
3428 void macro_iterator::shift(int n)
3430 while (n > 0 && argc > 0) {
3431 arg_list *tem = args;
3432 args = args->next;
3433 delete tem;
3434 --argc;
3435 --n;
3439 // This gets used by eg .if '\?xxx\?''.
3441 int operator==(const macro &m1, const macro &m2)
3443 if (m1.len != m2.len)
3444 return 0;
3445 string_iterator iter1(m1);
3446 string_iterator iter2(m2);
3447 int n = m1.len;
3448 while (--n >= 0) {
3449 node *nd1 = 0;
3450 int c1 = iter1.get(&nd1);
3451 assert(c1 != EOF);
3452 node *nd2 = 0;
3453 int c2 = iter2.get(&nd2);
3454 assert(c2 != EOF);
3455 if (c1 != c2) {
3456 if (c1 == 0)
3457 delete nd1;
3458 else if (c2 == 0)
3459 delete nd2;
3460 return 0;
3462 if (c1 == 0) {
3463 assert(nd1 != 0);
3464 assert(nd2 != 0);
3465 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3466 delete nd1;
3467 delete nd2;
3468 if (!are_same)
3469 return 0;
3472 return 1;
3475 static void interpolate_macro(symbol nm)
3477 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3478 if (p == 0) {
3479 int warned = 0;
3480 const char *s = nm.contents();
3481 if (strlen(s) > 2) {
3482 request_or_macro *r;
3483 char buf[3];
3484 buf[0] = s[0];
3485 buf[1] = s[1];
3486 buf[2] = '\0';
3487 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3488 if (r) {
3489 macro *m = r->to_macro();
3490 if (!m || !m->empty())
3491 warned = warning(WARN_SPACE,
3492 "macro `%1' not defined "
3493 "(probably missing space after `%2')",
3494 nm.contents(), buf);
3497 if (!warned) {
3498 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
3499 p = new macro;
3500 request_dictionary.define(nm, p);
3503 if (p)
3504 p->invoke(nm);
3505 else {
3506 skip_line();
3507 return;
3511 static void decode_args(macro_iterator *mi)
3513 if (!tok.newline() && !tok.eof()) {
3514 node *n;
3515 int c = get_copy(&n);
3516 for (;;) {
3517 while (c == ' ')
3518 c = get_copy(&n);
3519 if (c == '\n' || c == EOF)
3520 break;
3521 macro arg;
3522 int quote_input_level = 0;
3523 int done_tab_warning = 0;
3524 if (c == '\"') {
3525 quote_input_level = input_stack::get_level();
3526 c = get_copy(&n);
3528 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
3529 if (quote_input_level > 0 && c == '\"'
3530 && (compatible_flag
3531 || input_stack::get_level() == quote_input_level)) {
3532 c = get_copy(&n);
3533 if (c == '"') {
3534 arg.append(c);
3535 c = get_copy(&n);
3537 else
3538 break;
3540 else {
3541 if (c == 0)
3542 arg.append(n);
3543 else {
3544 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3545 warning(WARN_TAB, "tab character in unquoted macro argument");
3546 done_tab_warning = 1;
3548 arg.append(c);
3550 c = get_copy(&n);
3553 mi->add_arg(arg);
3558 static void decode_string_args(macro_iterator *mi)
3560 node *n;
3561 int c = get_copy(&n);
3562 for (;;) {
3563 while (c == ' ')
3564 c = get_copy(&n);
3565 if (c == '\n' || c == EOF) {
3566 error("missing `]'");
3567 break;
3569 if (c == ']')
3570 break;
3571 macro arg;
3572 int quote_input_level = 0;
3573 int done_tab_warning = 0;
3574 if (c == '\"') {
3575 quote_input_level = input_stack::get_level();
3576 c = get_copy(&n);
3578 while (c != EOF && c != '\n'
3579 && !(c == ']' && quote_input_level == 0)
3580 && !(c == ' ' && quote_input_level == 0)) {
3581 if (quote_input_level > 0 && c == '\"'
3582 && input_stack::get_level() == quote_input_level) {
3583 c = get_copy(&n);
3584 if (c == '"') {
3585 arg.append(c);
3586 c = get_copy(&n);
3588 else
3589 break;
3591 else {
3592 if (c == 0)
3593 arg.append(n);
3594 else {
3595 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3596 warning(WARN_TAB, "tab character in unquoted string argument");
3597 done_tab_warning = 1;
3599 arg.append(c);
3601 c = get_copy(&n);
3604 mi->add_arg(arg);
3608 void macro::invoke(symbol nm)
3610 macro_iterator *mi = new macro_iterator(nm, *this);
3611 decode_args(mi);
3612 input_stack::push(mi);
3613 tok.next();
3616 macro *macro::to_macro()
3618 return this;
3621 int macro::empty()
3623 return empty_macro == 1;
3626 macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked)
3627 : string_iterator(m, how_invoked, s), args(0), argc(0)
3631 macro_iterator::macro_iterator() : args(0), argc(0)
3635 macro_iterator::~macro_iterator()
3637 while (args != 0) {
3638 arg_list *tem = args;
3639 args = args->next;
3640 delete tem;
3644 dictionary composite_dictionary(17);
3646 void composite_request()
3648 symbol from = get_name(1);
3649 if (!from.is_null()) {
3650 const char *from_gn = glyph_name_to_unicode(from.contents());
3651 if (!from_gn) {
3652 from_gn = check_unicode_name(from.contents());
3653 if (!from_gn) {
3654 error("invalid composite glyph name `%1'", from.contents());
3655 skip_line();
3656 return;
3659 const char *from_decomposed = decompose_unicode(from_gn);
3660 if (from_decomposed)
3661 from_gn = &from_decomposed[1];
3662 symbol to = get_name(1);
3663 if (to.is_null())
3664 composite_dictionary.remove(symbol(from_gn));
3665 else {
3666 const char *to_gn = glyph_name_to_unicode(to.contents());
3667 if (!to_gn) {
3668 to_gn = check_unicode_name(to.contents());
3669 if (!to_gn) {
3670 error("invalid composite glyph name `%1'", to.contents());
3671 skip_line();
3672 return;
3675 const char *to_decomposed = decompose_unicode(to_gn);
3676 if (to_decomposed)
3677 to_gn = &to_decomposed[1];
3678 if (strcmp(from_gn, to_gn) == 0)
3679 composite_dictionary.remove(symbol(from_gn));
3680 else
3681 (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
3684 skip_line();
3687 static symbol composite_glyph_name(symbol nm)
3689 macro_iterator *mi = new macro_iterator();
3690 decode_string_args(mi);
3691 input_stack::push(mi);
3692 const char *gn = glyph_name_to_unicode(nm.contents());
3693 if (!gn) {
3694 gn = check_unicode_name(nm.contents());
3695 if (!gn) {
3696 error("invalid base glyph `%1' in composite glyph name", nm.contents());
3697 return EMPTY_SYMBOL;
3700 const char *gn_decomposed = decompose_unicode(gn);
3701 string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
3702 string gl;
3703 int n = input_stack::nargs();
3704 for (int i = 1; i <= n; i++) {
3705 glyph_name += '_';
3706 input_iterator *p = input_stack::get_arg(i);
3707 gl.clear();
3708 int c;
3709 while ((c = p->get(0)) != EOF)
3710 gl += c;
3711 gl += '\0';
3712 const char *u = glyph_name_to_unicode(gl.contents());
3713 if (!u) {
3714 u = check_unicode_name(gl.contents());
3715 if (!u) {
3716 error("invalid component `%1' in composite glyph name",
3717 gl.contents());
3718 return EMPTY_SYMBOL;
3721 const char *decomposed = decompose_unicode(u);
3722 if (decomposed)
3723 u = &decomposed[1];
3724 void *mapped_composite = composite_dictionary.lookup(symbol(u));
3725 if (mapped_composite)
3726 u = (const char *)mapped_composite;
3727 glyph_name += u;
3729 glyph_name += '\0';
3730 const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
3731 if (groff_gn)
3732 return symbol(groff_gn);
3733 gl.clear();
3734 gl += 'u';
3735 gl += glyph_name;
3736 return symbol(gl.contents());
3739 int trap_sprung_flag = 0;
3740 int postpone_traps_flag = 0;
3741 symbol postponed_trap;
3743 void spring_trap(symbol nm)
3745 assert(!nm.is_null());
3746 trap_sprung_flag = 1;
3747 if (postpone_traps_flag) {
3748 postponed_trap = nm;
3749 return;
3751 static char buf[2] = { BEGIN_TRAP, 0 };
3752 static char buf2[2] = { END_TRAP, '\0' };
3753 input_stack::push(make_temp_iterator(buf2));
3754 request_or_macro *p = lookup_request(nm);
3755 macro *m = p->to_macro();
3756 if (m)
3757 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
3758 else
3759 error("you can't invoke a request with a trap");
3760 input_stack::push(make_temp_iterator(buf));
3763 void postpone_traps()
3765 postpone_traps_flag = 1;
3768 int unpostpone_traps()
3770 postpone_traps_flag = 0;
3771 if (!postponed_trap.is_null()) {
3772 spring_trap(postponed_trap);
3773 postponed_trap = NULL_SYMBOL;
3774 return 1;
3776 else
3777 return 0;
3780 void read_request()
3782 macro_iterator *mi = new macro_iterator;
3783 int reading_from_terminal = isatty(fileno(stdin));
3784 int had_prompt = 0;
3785 if (!tok.newline() && !tok.eof()) {
3786 int c = get_copy(0);
3787 while (c == ' ')
3788 c = get_copy(0);
3789 while (c != EOF && c != '\n' && c != ' ') {
3790 if (!invalid_input_char(c)) {
3791 if (reading_from_terminal)
3792 fputc(c, stderr);
3793 had_prompt = 1;
3795 c = get_copy(0);
3797 if (c == ' ') {
3798 tok.make_space();
3799 decode_args(mi);
3802 if (reading_from_terminal) {
3803 fputc(had_prompt ? ':' : '\a', stderr);
3804 fflush(stderr);
3806 input_stack::push(mi);
3807 macro mac;
3808 int nl = 0;
3809 int c;
3810 while ((c = getchar()) != EOF) {
3811 if (invalid_input_char(c))
3812 warning(WARN_INPUT, "invalid input character code %1", int(c));
3813 else {
3814 if (c == '\n') {
3815 if (nl)
3816 break;
3817 else
3818 nl = 1;
3820 else
3821 nl = 0;
3822 mac.append(c);
3825 if (reading_from_terminal)
3826 clearerr(stdin);
3827 input_stack::push(new string_iterator(mac));
3828 tok.next();
3831 enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
3832 enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
3833 enum comp_mode { COMP_IGNORE, COMP_DISABLE };
3835 void do_define_string(define_mode mode, comp_mode comp)
3837 symbol nm;
3838 node *n;
3839 int c;
3840 nm = get_name(1);
3841 if (nm.is_null()) {
3842 skip_line();
3843 return;
3845 if (tok.newline())
3846 c = '\n';
3847 else if (tok.tab())
3848 c = '\t';
3849 else if (!tok.space()) {
3850 error("bad string definition");
3851 skip_line();
3852 return;
3854 else
3855 c = get_copy(&n);
3856 while (c == ' ')
3857 c = get_copy(&n);
3858 if (c == '"')
3859 c = get_copy(&n);
3860 macro mac;
3861 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
3862 macro *mm = rm ? rm->to_macro() : 0;
3863 if (mode == DEFINE_APPEND && mm)
3864 mac = *mm;
3865 if (comp == COMP_DISABLE)
3866 mac.append(COMPATIBLE_SAVE);
3867 while (c != '\n' && c != EOF) {
3868 if (c == 0)
3869 mac.append(n);
3870 else
3871 mac.append((unsigned char)c);
3872 c = get_copy(&n);
3874 if (!mm) {
3875 mm = new macro;
3876 request_dictionary.define(nm, mm);
3878 if (comp == COMP_DISABLE)
3879 mac.append(COMPATIBLE_RESTORE);
3880 *mm = mac;
3881 tok.next();
3884 void define_string()
3886 do_define_string(DEFINE_NORMAL, COMP_IGNORE);
3889 void define_nocomp_string()
3891 do_define_string(DEFINE_NORMAL, COMP_DISABLE);
3894 void append_string()
3896 do_define_string(DEFINE_APPEND, COMP_IGNORE);
3899 void append_nocomp_string()
3901 do_define_string(DEFINE_APPEND, COMP_DISABLE);
3904 void do_define_character(char_mode mode, const char *font_name)
3906 node *n;
3907 int c;
3908 tok.skip();
3909 charinfo *ci = tok.get_char(1);
3910 if (ci == 0) {
3911 skip_line();
3912 return;
3914 if (font_name) {
3915 string s(font_name);
3916 s += ' ';
3917 s += ci->nm.contents();
3918 s += '\0';
3919 ci = get_charinfo(symbol(s.contents()));
3921 tok.next();
3922 if (tok.newline())
3923 c = '\n';
3924 else if (tok.tab())
3925 c = '\t';
3926 else if (!tok.space()) {
3927 error("bad character definition");
3928 skip_line();
3929 return;
3931 else
3932 c = get_copy(&n);
3933 while (c == ' ' || c == '\t')
3934 c = get_copy(&n);
3935 if (c == '"')
3936 c = get_copy(&n);
3937 macro *m = new macro;
3938 while (c != '\n' && c != EOF) {
3939 if (c == 0)
3940 m->append(n);
3941 else
3942 m->append((unsigned char)c);
3943 c = get_copy(&n);
3945 m = ci->setx_macro(m, mode);
3946 if (m)
3947 delete m;
3948 tok.next();
3951 void define_character()
3953 do_define_character(CHAR_NORMAL);
3956 void define_fallback_character()
3958 do_define_character(CHAR_FALLBACK);
3961 void define_special_character()
3963 do_define_character(CHAR_SPECIAL);
3966 static void remove_character()
3968 tok.skip();
3969 while (!tok.newline() && !tok.eof()) {
3970 if (!tok.space() && !tok.tab()) {
3971 charinfo *ci = tok.get_char(1);
3972 if (!ci)
3973 break;
3974 macro *m = ci->set_macro(0);
3975 if (m)
3976 delete m;
3978 tok.next();
3980 skip_line();
3983 static void interpolate_string(symbol nm)
3985 request_or_macro *p = lookup_request(nm);
3986 macro *m = p->to_macro();
3987 if (!m)
3988 error("you can only invoke a string or macro using \\*");
3989 else {
3990 string_iterator *si = new string_iterator(*m, "string", nm);
3991 input_stack::push(si);
3995 static void interpolate_string_with_args(symbol s)
3997 request_or_macro *p = lookup_request(s);
3998 macro *m = p->to_macro();
3999 if (!m)
4000 error("you can only invoke a string or macro using \\*");
4001 else {
4002 macro_iterator *mi = new macro_iterator(s, *m);
4003 decode_string_args(mi);
4004 input_stack::push(mi);
4008 /* This class is used for the implementation of \$@. It is used for
4009 each of the closing double quotes. It artificially increases the
4010 input level by 2, so that the closing double quote will appear to have
4011 the same input level as the opening quote. */
4013 class end_quote_iterator : public input_iterator {
4014 unsigned char buf[1];
4015 public:
4016 end_quote_iterator();
4017 ~end_quote_iterator() { }
4018 int internal_level() { return 2; }
4021 end_quote_iterator::end_quote_iterator()
4023 buf[0] = '"';
4024 ptr = buf;
4025 eptr = buf + 1;
4028 static void interpolate_arg(symbol nm)
4030 const char *s = nm.contents();
4031 if (!s || *s == '\0')
4032 copy_mode_error("missing argument name");
4033 else if (s[1] == 0 && csdigit(s[0]))
4034 input_stack::push(input_stack::get_arg(s[0] - '0'));
4035 else if (s[0] == '*' && s[1] == '\0') {
4036 for (int i = input_stack::nargs(); i > 0; i--) {
4037 input_stack::push(input_stack::get_arg(i));
4038 if (i != 1)
4039 input_stack::push(make_temp_iterator(" "));
4042 else if (s[0] == '@' && s[1] == '\0') {
4043 for (int i = input_stack::nargs(); i > 0; i--) {
4044 input_stack::push(new end_quote_iterator);
4045 input_stack::push(input_stack::get_arg(i));
4046 input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \""));
4049 else {
4050 const char *p;
4051 for (p = s; *p && csdigit(*p); p++)
4053 if (*p)
4054 copy_mode_error("bad argument name `%1'", s);
4055 else
4056 input_stack::push(input_stack::get_arg(atoi(s)));
4060 void handle_first_page_transition()
4062 push_token(tok);
4063 topdiv->begin_page();
4066 // We push back a token by wrapping it up in a token_node, and
4067 // wrapping that up in a string_iterator.
4069 static void push_token(const token &t)
4071 macro m;
4072 m.append(new token_node(t));
4073 input_stack::push(new string_iterator(m));
4076 void push_page_ejector()
4078 static char buf[2] = { PAGE_EJECTOR, '\0' };
4079 input_stack::push(make_temp_iterator(buf));
4082 void handle_initial_request(unsigned char code)
4084 char buf[2];
4085 buf[0] = code;
4086 buf[1] = '\0';
4087 macro mac;
4088 mac.append(new token_node(tok));
4089 input_stack::push(new string_iterator(mac));
4090 input_stack::push(make_temp_iterator(buf));
4091 topdiv->begin_page();
4092 tok.next();
4095 void handle_initial_title()
4097 handle_initial_request(TITLE_REQUEST);
4100 // this should be local to define_macro, but cfront 1.2 doesn't support that
4101 static symbol dot_symbol(".");
4103 void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
4105 symbol nm, term;
4106 if (calling == CALLING_INDIRECT) {
4107 symbol temp1 = get_name(1);
4108 if (temp1.is_null()) {
4109 skip_line();
4110 return;
4112 symbol temp2 = get_name();
4113 input_stack::push(make_temp_iterator("\n"));
4114 if (!temp2.is_null()) {
4115 interpolate_string(temp2);
4116 input_stack::push(make_temp_iterator(" "));
4118 interpolate_string(temp1);
4119 input_stack::push(make_temp_iterator(" "));
4120 tok.next();
4122 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4123 nm = get_name(1);
4124 if (nm.is_null()) {
4125 skip_line();
4126 return;
4129 term = get_name(); // the request that terminates the definition
4130 if (term.is_null())
4131 term = dot_symbol;
4132 while (!tok.newline() && !tok.eof())
4133 tok.next();
4134 const char *start_filename;
4135 int start_lineno;
4136 int have_start_location = input_stack::get_location(0, &start_filename,
4137 &start_lineno);
4138 node *n;
4139 // doing this here makes the line numbers come out right
4140 int c = get_copy(&n, 1);
4141 macro mac;
4142 macro *mm = 0;
4143 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4144 request_or_macro *rm =
4145 (request_or_macro *)request_dictionary.lookup(nm);
4146 if (rm)
4147 mm = rm->to_macro();
4148 if (mm && mode == DEFINE_APPEND)
4149 mac = *mm;
4151 int bol = 1;
4152 if (comp == COMP_DISABLE)
4153 mac.append(COMPATIBLE_SAVE);
4154 for (;;) {
4155 while (c == ESCAPE_NEWLINE) {
4156 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
4157 mac.append(c);
4158 c = get_copy(&n, 1);
4160 if (bol && c == '.') {
4161 const char *s = term.contents();
4162 int d = 0;
4163 // see if it matches term
4164 int i = 0;
4165 if (s[0] != 0) {
4166 while ((d = get_copy(&n)) == ' ' || d == '\t')
4168 if ((unsigned char)s[0] == d) {
4169 for (i = 1; s[i] != 0; i++) {
4170 d = get_copy(&n);
4171 if ((unsigned char)s[i] != d)
4172 break;
4176 if (s[i] == 0
4177 && ((i == 2 && compatible_flag)
4178 || (d = get_copy(&n)) == ' '
4179 || d == '\n')) { // we found it
4180 if (d == '\n')
4181 tok.make_newline();
4182 else
4183 tok.make_space();
4184 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4185 if (!mm) {
4186 mm = new macro;
4187 request_dictionary.define(nm, mm);
4189 if (comp == COMP_DISABLE)
4190 mac.append(COMPATIBLE_RESTORE);
4191 *mm = mac;
4193 if (term != dot_symbol) {
4194 ignoring = 0;
4195 interpolate_macro(term);
4197 else
4198 skip_line();
4199 return;
4201 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4202 mac.append(c);
4203 for (int j = 0; j < i; j++)
4204 mac.append(s[j]);
4206 c = d;
4208 if (c == EOF) {
4209 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4210 if (have_start_location)
4211 error_with_file_and_line(start_filename, start_lineno,
4212 "end of file while defining macro `%1'",
4213 nm.contents());
4214 else
4215 error("end of file while defining macro `%1'", nm.contents());
4217 else {
4218 if (have_start_location)
4219 error_with_file_and_line(start_filename, start_lineno,
4220 "end of file while ignoring input lines");
4221 else
4222 error("end of file while ignoring input lines");
4224 tok.next();
4225 return;
4227 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4228 if (c == 0)
4229 mac.append(n);
4230 else
4231 mac.append(c);
4233 bol = (c == '\n');
4234 c = get_copy(&n, 1);
4238 void define_macro()
4240 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_IGNORE);
4243 void define_nocomp_macro()
4245 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
4248 void define_indirect_macro()
4250 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_IGNORE);
4253 void define_indirect_nocomp_macro()
4255 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
4258 void append_macro()
4260 do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_IGNORE);
4263 void append_nocomp_macro()
4265 do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
4268 void append_indirect_macro()
4270 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_IGNORE);
4273 void append_indirect_nocomp_macro()
4275 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
4278 void ignore()
4280 ignoring = 1;
4281 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
4282 ignoring = 0;
4285 void remove_macro()
4287 for (;;) {
4288 symbol s = get_name();
4289 if (s.is_null())
4290 break;
4291 request_dictionary.remove(s);
4293 skip_line();
4296 void rename_macro()
4298 symbol s1 = get_name(1);
4299 if (!s1.is_null()) {
4300 symbol s2 = get_name(1);
4301 if (!s2.is_null())
4302 request_dictionary.rename(s1, s2);
4304 skip_line();
4307 void alias_macro()
4309 symbol s1 = get_name(1);
4310 if (!s1.is_null()) {
4311 symbol s2 = get_name(1);
4312 if (!s2.is_null()) {
4313 if (!request_dictionary.alias(s1, s2))
4314 warning(WARN_MAC, "macro `%1' not defined", s2.contents());
4317 skip_line();
4320 void chop_macro()
4322 symbol s = get_name(1);
4323 if (!s.is_null()) {
4324 request_or_macro *p = lookup_request(s);
4325 macro *m = p->to_macro();
4326 if (!m)
4327 error("cannot chop request");
4328 else if (m->empty())
4329 error("cannot chop empty macro");
4330 else {
4331 int have_restore = 0;
4332 // we have to check for additional save/restore pairs which could be
4333 // there due to empty am1 requests.
4334 for (;;) {
4335 if (m->get(m->len - 1) != COMPATIBLE_RESTORE)
4336 break;
4337 have_restore = 1;
4338 m->len -= 1;
4339 if (m->get(m->len - 1) != COMPATIBLE_SAVE)
4340 break;
4341 have_restore = 0;
4342 m->len -= 1;
4343 if (m->len == 0)
4344 break;
4346 if (m->len == 0)
4347 error("cannot chop empty macro");
4348 else {
4349 if (have_restore)
4350 m->set(COMPATIBLE_RESTORE, m->len - 1);
4351 else
4352 m->len -= 1;
4356 skip_line();
4359 void substring_request()
4361 int start; // 0, 1, ..., n-1 or -1, -2, ...
4362 symbol s = get_name(1);
4363 if (!s.is_null() && get_integer(&start)) {
4364 request_or_macro *p = lookup_request(s);
4365 macro *m = p->to_macro();
4366 if (!m)
4367 error("cannot apply `substring' on a request");
4368 else {
4369 int end = -1;
4370 if (!has_arg() || get_integer(&end)) {
4371 int real_length = 0; // 1, 2, ..., n
4372 string_iterator iter1(*m);
4373 for (int l = 0; l < m->len; l++) {
4374 int c = iter1.get(0);
4375 if (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4376 continue;
4377 if (c == EOF)
4378 break;
4379 real_length++;
4381 if (start < 0)
4382 start += real_length;
4383 if (end < 0)
4384 end += real_length;
4385 if (start > end) {
4386 int tem = start;
4387 start = end;
4388 end = tem;
4390 if (start >= real_length || end < 0) {
4391 warning(WARN_RANGE,
4392 "start and end index of substring out of range");
4393 m->len = 0;
4394 if (m->p) {
4395 if (--(m->p->count) <= 0)
4396 delete m->p;
4397 m->p = 0;
4399 skip_line();
4400 return;
4402 if (start < 0) {
4403 warning(WARN_RANGE,
4404 "start index of substring out of range, set to 0");
4405 start = 0;
4407 if (end >= real_length) {
4408 warning(WARN_RANGE,
4409 "end index of substring out of range, set to string length");
4410 end = real_length - 1;
4412 // now extract the substring
4413 string_iterator iter(*m);
4414 int i;
4415 for (i = 0; i < start; i++) {
4416 int c = iter.get(0);
4417 while (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4418 c = iter.get(0);
4419 if (c == EOF)
4420 break;
4422 macro mac;
4423 for (; i <= end; i++) {
4424 node *nd;
4425 int c = iter.get(&nd);
4426 while (c == COMPATIBLE_SAVE || c == COMPATIBLE_RESTORE)
4427 c = iter.get(0);
4428 if (c == EOF)
4429 break;
4430 if (c == 0)
4431 mac.append(nd);
4432 else
4433 mac.append((unsigned char)c);
4435 *m = mac;
4439 skip_line();
4442 void length_request()
4444 symbol ret;
4445 ret = get_name(1);
4446 if (ret.is_null()) {
4447 skip_line();
4448 return;
4450 int c;
4451 node *n;
4452 if (tok.newline())
4453 c = '\n';
4454 else if (tok.tab())
4455 c = '\t';
4456 else if (!tok.space()) {
4457 error("bad string definition");
4458 skip_line();
4459 return;
4461 else
4462 c = get_copy(&n);
4463 while (c == ' ')
4464 c = get_copy(&n);
4465 if (c == '"')
4466 c = get_copy(&n);
4467 int len = 0;
4468 while (c != '\n' && c != EOF) {
4469 ++len;
4470 c = get_copy(&n);
4472 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4473 if (r)
4474 r->set_value(len);
4475 else
4476 set_number_reg(ret, len);
4477 tok.next();
4480 void asciify_macro()
4482 symbol s = get_name(1);
4483 if (!s.is_null()) {
4484 request_or_macro *p = lookup_request(s);
4485 macro *m = p->to_macro();
4486 if (!m)
4487 error("cannot asciify request");
4488 else {
4489 macro am;
4490 string_iterator iter(*m);
4491 for (;;) {
4492 node *nd;
4493 int c = iter.get(&nd);
4494 if (c == EOF)
4495 break;
4496 if (c != 0)
4497 am.append(c);
4498 else
4499 nd->asciify(&am);
4501 *m = am;
4504 skip_line();
4507 void unformat_macro()
4509 symbol s = get_name(1);
4510 if (!s.is_null()) {
4511 request_or_macro *p = lookup_request(s);
4512 macro *m = p->to_macro();
4513 if (!m)
4514 error("cannot unformat request");
4515 else {
4516 macro am;
4517 string_iterator iter(*m);
4518 for (;;) {
4519 node *nd;
4520 int c = iter.get(&nd);
4521 if (c == EOF)
4522 break;
4523 if (c != 0)
4524 am.append(c);
4525 else {
4526 if (nd->set_unformat_flag())
4527 am.append(nd);
4530 *m = am;
4533 skip_line();
4536 static void interpolate_environment_variable(symbol nm)
4538 const char *s = getenv(nm.contents());
4539 if (s && *s)
4540 input_stack::push(make_temp_iterator(s));
4543 void interpolate_number_reg(symbol nm, int inc)
4545 reg *r = lookup_number_reg(nm);
4546 if (inc < 0)
4547 r->decrement();
4548 else if (inc > 0)
4549 r->increment();
4550 input_stack::push(make_temp_iterator(r->get_string()));
4553 static void interpolate_number_format(symbol nm)
4555 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4556 if (r)
4557 input_stack::push(make_temp_iterator(r->get_format()));
4560 static int get_delim_number(units *n, int si, int prev_value)
4562 token start;
4563 start.next();
4564 if (start.delimiter(1)) {
4565 tok.next();
4566 if (get_number(n, si, prev_value)) {
4567 if (start != tok)
4568 warning(WARN_DELIM, "closing delimiter does not match");
4569 return 1;
4572 return 0;
4575 static int get_delim_number(units *n, int si)
4577 token start;
4578 start.next();
4579 if (start.delimiter(1)) {
4580 tok.next();
4581 if (get_number(n, si)) {
4582 if (start != tok)
4583 warning(WARN_DELIM, "closing delimiter does not match");
4584 return 1;
4587 return 0;
4590 static int get_line_arg(units *n, int si, charinfo **cp)
4592 token start;
4593 start.next();
4594 int start_level = input_stack::get_level();
4595 if (!start.delimiter(1))
4596 return 0;
4597 tok.next();
4598 if (get_number(n, si)) {
4599 if (tok.dummy() || tok.transparent_dummy())
4600 tok.next();
4601 if (!(start == tok && input_stack::get_level() == start_level)) {
4602 *cp = tok.get_char(1);
4603 tok.next();
4605 if (!(start == tok && input_stack::get_level() == start_level))
4606 warning(WARN_DELIM, "closing delimiter does not match");
4607 return 1;
4609 return 0;
4612 static int read_size(int *x)
4614 tok.next();
4615 int c = tok.ch();
4616 int inc = 0;
4617 if (c == '-') {
4618 inc = -1;
4619 tok.next();
4620 c = tok.ch();
4622 else if (c == '+') {
4623 inc = 1;
4624 tok.next();
4625 c = tok.ch();
4627 int val;
4628 int bad = 0;
4629 if (c == '(') {
4630 tok.next();
4631 c = tok.ch();
4632 if (!inc) {
4633 // allow an increment either before or after the left parenthesis
4634 if (c == '-') {
4635 inc = -1;
4636 tok.next();
4637 c = tok.ch();
4639 else if (c == '+') {
4640 inc = 1;
4641 tok.next();
4642 c = tok.ch();
4645 if (!csdigit(c))
4646 bad = 1;
4647 else {
4648 val = c - '0';
4649 tok.next();
4650 c = tok.ch();
4651 if (!csdigit(c))
4652 bad = 1;
4653 else {
4654 val = val*10 + (c - '0');
4655 val *= sizescale;
4659 else if (csdigit(c)) {
4660 val = c - '0';
4661 if (!inc && c != '0' && c < '4') {
4662 tok.next();
4663 c = tok.ch();
4664 if (!csdigit(c))
4665 bad = 1;
4666 else
4667 val = val*10 + (c - '0');
4669 val *= sizescale;
4671 else if (!tok.delimiter(1))
4672 return 0;
4673 else {
4674 token start(tok);
4675 tok.next();
4676 if (!(inc
4677 ? get_number(&val, 'z')
4678 : get_number(&val, 'z', curenv->get_requested_point_size())))
4679 return 0;
4680 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
4681 if (start.ch() == '[')
4682 error("missing `]'");
4683 else
4684 error("missing closing delimiter");
4685 return 0;
4688 if (!bad) {
4689 switch (inc) {
4690 case 0:
4691 if (val == 0) {
4692 // special case -- \s[0] and \s0 means to revert to previous size
4693 *x = 0;
4694 return 1;
4696 *x = val;
4697 break;
4698 case 1:
4699 *x = curenv->get_requested_point_size() + val;
4700 break;
4701 case -1:
4702 *x = curenv->get_requested_point_size() - val;
4703 break;
4704 default:
4705 assert(0);
4707 if (*x <= 0) {
4708 warning(WARN_RANGE,
4709 "\\s request results in non-positive point size; set to 1");
4710 *x = 1;
4712 return 1;
4714 else {
4715 error("bad digit in point size");
4716 return 0;
4720 static symbol get_delim_name()
4722 token start;
4723 start.next();
4724 if (start.eof()) {
4725 error("end of input at start of delimited name");
4726 return NULL_SYMBOL;
4728 if (start.newline()) {
4729 error("can't delimit name with a newline");
4730 return NULL_SYMBOL;
4732 int start_level = input_stack::get_level();
4733 char abuf[ABUF_SIZE];
4734 char *buf = abuf;
4735 int buf_size = ABUF_SIZE;
4736 int i = 0;
4737 for (;;) {
4738 if (i + 1 > buf_size) {
4739 if (buf == abuf) {
4740 buf = new char[ABUF_SIZE*2];
4741 memcpy(buf, abuf, buf_size);
4742 buf_size = ABUF_SIZE*2;
4744 else {
4745 char *old_buf = buf;
4746 buf = new char[buf_size*2];
4747 memcpy(buf, old_buf, buf_size);
4748 buf_size *= 2;
4749 a_delete old_buf;
4752 tok.next();
4753 if (tok == start
4754 && (compatible_flag || input_stack::get_level() == start_level))
4755 break;
4756 if ((buf[i] = tok.ch()) == 0) {
4757 error("missing delimiter (got %1)", tok.description());
4758 if (buf != abuf)
4759 a_delete buf;
4760 return NULL_SYMBOL;
4762 i++;
4764 buf[i] = '\0';
4765 if (buf == abuf) {
4766 if (i == 0) {
4767 error("empty delimited name");
4768 return NULL_SYMBOL;
4770 else
4771 return symbol(buf);
4773 else {
4774 symbol s(buf);
4775 a_delete buf;
4776 return s;
4780 // Implement \R
4782 static void do_register()
4784 token start;
4785 start.next();
4786 if (!start.delimiter(1))
4787 return;
4788 tok.next();
4789 symbol nm = get_long_name(1);
4790 if (nm.is_null())
4791 return;
4792 while (tok.space())
4793 tok.next();
4794 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4795 int prev_value;
4796 if (!r || !r->get_value(&prev_value))
4797 prev_value = 0;
4798 int val;
4799 if (!get_number(&val, 'u', prev_value))
4800 return;
4801 if (start != tok)
4802 warning(WARN_DELIM, "closing delimiter does not match");
4803 if (r)
4804 r->set_value(val);
4805 else
4806 set_number_reg(nm, val);
4809 // this implements the \w escape sequence
4811 static void do_width()
4813 token start;
4814 start.next();
4815 int start_level = input_stack::get_level();
4816 environment env(curenv);
4817 environment *oldenv = curenv;
4818 curenv = &env;
4819 for (;;) {
4820 tok.next();
4821 if (tok.eof()) {
4822 warning(WARN_DELIM, "missing closing delimiter");
4823 break;
4825 if (tok.newline()) {
4826 warning(WARN_DELIM, "missing closing delimiter");
4827 input_stack::push(make_temp_iterator("\n"));
4828 break;
4830 if (tok == start
4831 && (compatible_flag || input_stack::get_level() == start_level))
4832 break;
4833 tok.process();
4835 env.wrap_up_tab();
4836 units x = env.get_input_line_position().to_units();
4837 input_stack::push(make_temp_iterator(i_to_a(x)));
4838 env.width_registers();
4839 curenv = oldenv;
4840 have_input = 0;
4843 charinfo *page_character;
4845 void set_page_character()
4847 page_character = get_optional_char();
4848 skip_line();
4851 static const symbol percent_symbol("%");
4853 void read_title_parts(node **part, hunits *part_width)
4855 tok.skip();
4856 if (tok.newline() || tok.eof())
4857 return;
4858 token start(tok);
4859 int start_level = input_stack::get_level();
4860 tok.next();
4861 for (int i = 0; i < 3; i++) {
4862 while (!tok.newline() && !tok.eof()) {
4863 if (tok == start
4864 && (compatible_flag || input_stack::get_level() == start_level)) {
4865 tok.next();
4866 break;
4868 if (page_character != 0 && tok.get_char() == page_character)
4869 interpolate_number_reg(percent_symbol, 0);
4870 else
4871 tok.process();
4872 tok.next();
4874 curenv->wrap_up_tab();
4875 part_width[i] = curenv->get_input_line_position();
4876 part[i] = curenv->extract_output_line();
4878 while (!tok.newline() && !tok.eof())
4879 tok.next();
4882 class non_interpreted_node : public node {
4883 macro mac;
4884 public:
4885 non_interpreted_node(const macro &);
4886 int interpret(macro *);
4887 node *copy();
4888 int ends_sentence();
4889 int same(node *);
4890 const char *type();
4891 int force_tprint();
4894 non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
4898 int non_interpreted_node::ends_sentence()
4900 return 2;
4903 int non_interpreted_node::same(node *nd)
4905 return mac == ((non_interpreted_node *)nd)->mac;
4908 const char *non_interpreted_node::type()
4910 return "non_interpreted_node";
4913 int non_interpreted_node::force_tprint()
4915 return 0;
4918 node *non_interpreted_node::copy()
4920 return new non_interpreted_node(mac);
4923 int non_interpreted_node::interpret(macro *m)
4925 string_iterator si(mac);
4926 node *n;
4927 for (;;) {
4928 int c = si.get(&n);
4929 if (c == EOF)
4930 break;
4931 if (c == 0)
4932 m->append(n);
4933 else
4934 m->append(c);
4936 return 1;
4939 static node *do_non_interpreted()
4941 node *n;
4942 int c;
4943 macro mac;
4944 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
4945 if (c == 0)
4946 mac.append(n);
4947 else
4948 mac.append(c);
4949 if (c == EOF || c == '\n') {
4950 error("missing \\?");
4951 return 0;
4953 return new non_interpreted_node(mac);
4956 static void encode_char(macro *mac, char c)
4958 if (c == '\0') {
4959 if ((font::use_charnames_in_special) && tok.special()) {
4960 charinfo *ci = tok.get_char(1);
4961 const char *s = ci->get_symbol()->contents();
4962 if (s[0] != (char)0) {
4963 mac->append('\\');
4964 mac->append('(');
4965 int i = 0;
4966 while (s[i] != (char)0) {
4967 mac->append(s[i]);
4968 i++;
4970 mac->append('\\');
4971 mac->append(')');
4974 else if (tok.stretchable_space()
4975 || tok.unstretchable_space())
4976 mac->append(' ');
4977 else if (!(tok.hyphen_indicator()
4978 || tok.dummy()
4979 || tok.transparent_dummy()
4980 || tok.zero_width_break()))
4981 error("%1 is invalid within \\X", tok.description());
4983 else {
4984 if ((font::use_charnames_in_special) && (c == '\\')) {
4986 * add escape escape sequence
4988 mac->append(c);
4990 mac->append(c);
4994 node *do_special()
4996 token start;
4997 start.next();
4998 int start_level = input_stack::get_level();
4999 macro mac;
5000 for (tok.next();
5001 tok != start || input_stack::get_level() != start_level;
5002 tok.next()) {
5003 if (tok.eof()) {
5004 warning(WARN_DELIM, "missing closing delimiter");
5005 return 0;
5007 if (tok.newline()) {
5008 input_stack::push(make_temp_iterator("\n"));
5009 warning(WARN_DELIM, "missing closing delimiter");
5010 break;
5012 unsigned char c;
5013 if (tok.space())
5014 c = ' ';
5015 else if (tok.tab())
5016 c = '\t';
5017 else if (tok.leader())
5018 c = '\001';
5019 else if (tok.backspace())
5020 c = '\b';
5021 else
5022 c = tok.ch();
5023 encode_char(&mac, c);
5025 return new special_node(mac);
5028 void output_request()
5030 if (!tok.newline() && !tok.eof()) {
5031 int c;
5032 for (;;) {
5033 c = get_copy(0);
5034 if (c == '"') {
5035 c = get_copy(0);
5036 break;
5038 if (c != ' ' && c != '\t')
5039 break;
5041 for (; c != '\n' && c != EOF; c = get_copy(0))
5042 topdiv->transparent_output(c);
5043 topdiv->transparent_output('\n');
5045 tok.next();
5048 extern int image_no; // from node.cpp
5050 static node *do_suppress(symbol nm)
5052 if (nm.is_null() || nm.is_empty()) {
5053 error("expecting an argument to escape \\O");
5054 return 0;
5056 const char *s = nm.contents();
5057 switch (*s) {
5058 case '0':
5059 if (begin_level == 0)
5060 // suppress generation of glyphs
5061 return new suppress_node(0, 0);
5062 break;
5063 case '1':
5064 if (begin_level == 0)
5065 // enable generation of glyphs
5066 return new suppress_node(1, 0);
5067 break;
5068 case '2':
5069 if (begin_level == 0)
5070 return new suppress_node(1, 1);
5071 break;
5072 case '3':
5073 begin_level++;
5074 break;
5075 case '4':
5076 begin_level--;
5077 break;
5078 case '5':
5080 s++; // move over '5'
5081 char position = *s;
5082 if (*s == (char)0) {
5083 error("missing position and filename in \\O");
5084 return 0;
5086 if (!(position == 'l'
5087 || position == 'r'
5088 || position == 'c'
5089 || position == 'i')) {
5090 error("l, r, c, or i position expected (got %1 in \\O)", position);
5091 return 0;
5093 s++; // onto image name
5094 if (s == (char *)0) {
5095 error("missing image name for \\O");
5096 return 0;
5098 image_no++;
5099 if (begin_level == 0)
5100 return new suppress_node(symbol(s), position, image_no);
5102 break;
5103 default:
5104 error("`%1' is an invalid argument to \\O", *s);
5106 return 0;
5109 void special_node::tprint(troff_output_file *out)
5111 tprint_start(out);
5112 string_iterator iter(mac);
5113 for (;;) {
5114 int c = iter.get(0);
5115 if (c == EOF)
5116 break;
5117 for (const char *s = ::asciify(c); *s; s++)
5118 tprint_char(out, *s);
5120 tprint_end(out);
5123 int get_file_line(const char **filename, int *lineno)
5125 return input_stack::get_location(0, filename, lineno);
5128 void line_file()
5130 int n;
5131 if (get_integer(&n)) {
5132 const char *filename = 0;
5133 if (has_arg()) {
5134 symbol s = get_long_name();
5135 filename = s.contents();
5137 (void)input_stack::set_location(filename, n-1);
5139 skip_line();
5142 static int nroff_mode = 0;
5144 static void nroff_request()
5146 nroff_mode = 1;
5147 skip_line();
5150 static void troff_request()
5152 nroff_mode = 0;
5153 skip_line();
5156 static void skip_alternative()
5158 int level = 0;
5159 // ensure that ``.if 0\{'' works as expected
5160 if (tok.left_brace())
5161 level++;
5162 int c;
5163 for (;;) {
5164 c = input_stack::get(0);
5165 if (c == EOF)
5166 break;
5167 if (c == ESCAPE_LEFT_BRACE)
5168 ++level;
5169 else if (c == ESCAPE_RIGHT_BRACE)
5170 --level;
5171 else if (c == escape_char && escape_char > 0)
5172 switch(input_stack::get(0)) {
5173 case '{':
5174 ++level;
5175 break;
5176 case '}':
5177 --level;
5178 break;
5179 case '"':
5180 while ((c = input_stack::get(0)) != '\n' && c != EOF)
5184 Note that the level can properly be < 0, eg
5186 .if 1 \{\
5187 .if 0 \{\
5188 .\}\}
5190 So don't give an error message in this case.
5192 if (level <= 0 && c == '\n')
5193 break;
5195 tok.next();
5198 static void begin_alternative()
5200 while (tok.space() || tok.left_brace())
5201 tok.next();
5204 void nop_request()
5206 while (tok.space())
5207 tok.next();
5210 static int_stack if_else_stack;
5212 int do_if_request()
5214 int invert = 0;
5215 while (tok.space())
5216 tok.next();
5217 while (tok.ch() == '!') {
5218 tok.next();
5219 invert = !invert;
5221 int result;
5222 unsigned char c = tok.ch();
5223 if (c == 't') {
5224 tok.next();
5225 result = !nroff_mode;
5227 else if (c == 'n') {
5228 tok.next();
5229 result = nroff_mode;
5231 else if (c == 'v') {
5232 tok.next();
5233 result = 0;
5235 else if (c == 'o') {
5236 result = (topdiv->get_page_number() & 1);
5237 tok.next();
5239 else if (c == 'e') {
5240 result = !(topdiv->get_page_number() & 1);
5241 tok.next();
5243 else if (c == 'd' || c == 'r') {
5244 tok.next();
5245 symbol nm = get_name(1);
5246 if (nm.is_null()) {
5247 skip_alternative();
5248 return 0;
5250 result = (c == 'd'
5251 ? request_dictionary.lookup(nm) != 0
5252 : number_reg_dictionary.lookup(nm) != 0);
5254 else if (c == 'm') {
5255 tok.next();
5256 symbol nm = get_long_name(1);
5257 if (nm.is_null()) {
5258 skip_alternative();
5259 return 0;
5261 result = (nm == default_symbol
5262 || color_dictionary.lookup(nm) != 0);
5264 else if (c == 'c') {
5265 tok.next();
5266 tok.skip();
5267 charinfo *ci = tok.get_char(1);
5268 if (ci == 0) {
5269 skip_alternative();
5270 return 0;
5272 result = character_exists(ci, curenv);
5273 tok.next();
5275 else if (tok.space())
5276 result = 0;
5277 else if (tok.delimiter()) {
5278 token delim = tok;
5279 int delim_level = input_stack::get_level();
5280 environment env1(curenv);
5281 environment env2(curenv);
5282 environment *oldenv = curenv;
5283 curenv = &env1;
5284 for (int i = 0; i < 2; i++) {
5285 for (;;) {
5286 tok.next();
5287 if (tok.newline() || tok.eof()) {
5288 warning(WARN_DELIM, "missing closing delimiter");
5289 tok.next();
5290 curenv = oldenv;
5291 return 0;
5293 if (tok == delim
5294 && (compatible_flag || input_stack::get_level() == delim_level))
5295 break;
5296 tok.process();
5298 curenv = &env2;
5300 node *n1 = env1.extract_output_line();
5301 node *n2 = env2.extract_output_line();
5302 result = same_node_list(n1, n2);
5303 delete_node_list(n1);
5304 delete_node_list(n2);
5305 curenv = oldenv;
5306 have_input = 0;
5307 tok.next();
5309 else {
5310 units n;
5311 if (!get_number(&n, 'u')) {
5312 skip_alternative();
5313 return 0;
5315 else
5316 result = n > 0;
5318 if (invert)
5319 result = !result;
5320 if (result)
5321 begin_alternative();
5322 else
5323 skip_alternative();
5324 return result;
5327 void if_else_request()
5329 if_else_stack.push(do_if_request());
5332 void if_request()
5334 do_if_request();
5337 void else_request()
5339 if (if_else_stack.is_empty()) {
5340 warning(WARN_EL, "unbalanced .el request");
5341 skip_alternative();
5343 else {
5344 if (if_else_stack.pop())
5345 skip_alternative();
5346 else
5347 begin_alternative();
5351 static int while_depth = 0;
5352 static int while_break_flag = 0;
5354 void while_request()
5356 macro mac;
5357 int escaped = 0;
5358 int level = 0;
5359 mac.append(new token_node(tok));
5360 for (;;) {
5361 node *n;
5362 int c = input_stack::get(&n);
5363 if (c == EOF)
5364 break;
5365 if (c == 0) {
5366 escaped = 0;
5367 mac.append(n);
5369 else if (escaped) {
5370 if (c == '{')
5371 level += 1;
5372 else if (c == '}')
5373 level -= 1;
5374 escaped = 0;
5375 mac.append(c);
5377 else {
5378 if (c == ESCAPE_LEFT_BRACE)
5379 level += 1;
5380 else if (c == ESCAPE_RIGHT_BRACE)
5381 level -= 1;
5382 else if (c == escape_char)
5383 escaped = 1;
5384 mac.append(c);
5385 if (c == '\n' && level <= 0)
5386 break;
5389 if (level != 0)
5390 error("unbalanced \\{ \\}");
5391 else {
5392 while_depth++;
5393 input_stack::add_boundary();
5394 for (;;) {
5395 input_stack::push(new string_iterator(mac, "while loop"));
5396 tok.next();
5397 if (!do_if_request()) {
5398 while (input_stack::get(0) != EOF)
5400 break;
5402 process_input_stack();
5403 if (while_break_flag || input_stack::is_return_boundary()) {
5404 while_break_flag = 0;
5405 break;
5408 input_stack::remove_boundary();
5409 while_depth--;
5411 tok.next();
5414 void while_break_request()
5416 if (!while_depth) {
5417 error("no while loop");
5418 skip_line();
5420 else {
5421 while_break_flag = 1;
5422 while (input_stack::get(0) != EOF)
5424 tok.next();
5428 void while_continue_request()
5430 if (!while_depth) {
5431 error("no while loop");
5432 skip_line();
5434 else {
5435 while (input_stack::get(0) != EOF)
5437 tok.next();
5441 // .so
5443 void source()
5445 symbol nm = get_long_name(1);
5446 if (nm.is_null())
5447 skip_line();
5448 else {
5449 while (!tok.newline() && !tok.eof())
5450 tok.next();
5451 errno = 0;
5452 FILE *fp = include_search_path.open_file_cautious(nm.contents());
5453 if (fp)
5454 input_stack::push(new file_iterator(fp, nm.contents()));
5455 else
5456 error("can't open `%1': %2", nm.contents(), strerror(errno));
5457 tok.next();
5461 // like .so but use popen()
5463 void pipe_source()
5465 if (safer_flag) {
5466 error(".pso request not allowed in safer mode");
5467 skip_line();
5469 else {
5470 #ifdef POPEN_MISSING
5471 error("pipes not available on this system");
5472 skip_line();
5473 #else /* not POPEN_MISSING */
5474 if (tok.newline() || tok.eof())
5475 error("missing command");
5476 else {
5477 int c;
5478 while ((c = get_copy(0)) == ' ' || c == '\t')
5480 int buf_size = 24;
5481 char *buf = new char[buf_size];
5482 int buf_used = 0;
5483 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5484 const char *s = asciify(c);
5485 int slen = strlen(s);
5486 if (buf_used + slen + 1> buf_size) {
5487 char *old_buf = buf;
5488 int old_buf_size = buf_size;
5489 buf_size *= 2;
5490 buf = new char[buf_size];
5491 memcpy(buf, old_buf, old_buf_size);
5492 a_delete old_buf;
5494 strcpy(buf + buf_used, s);
5495 buf_used += slen;
5497 buf[buf_used] = '\0';
5498 errno = 0;
5499 FILE *fp = popen(buf, POPEN_RT);
5500 if (fp)
5501 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5502 else
5503 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5504 a_delete buf;
5506 tok.next();
5507 #endif /* not POPEN_MISSING */
5511 // .psbb
5513 static int llx_reg_contents = 0;
5514 static int lly_reg_contents = 0;
5515 static int urx_reg_contents = 0;
5516 static int ury_reg_contents = 0;
5518 struct bounding_box {
5519 int llx, lly, urx, ury;
5522 /* Parse the argument to a %%BoundingBox comment. Return 1 if it
5523 contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5525 int parse_bounding_box(char *p, bounding_box *bb)
5527 if (sscanf(p, "%d %d %d %d",
5528 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5529 return 1;
5530 else {
5531 /* The Document Structuring Conventions say that the numbers
5532 should be integers. Unfortunately some broken applications
5533 get this wrong. */
5534 double x1, x2, x3, x4;
5535 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5536 bb->llx = (int)x1;
5537 bb->lly = (int)x2;
5538 bb->urx = (int)x3;
5539 bb->ury = (int)x4;
5540 return 1;
5542 else {
5543 for (; *p == ' ' || *p == '\t'; p++)
5545 if (strncmp(p, "(atend)", 7) == 0) {
5546 return 2;
5550 bb->llx = bb->lly = bb->urx = bb->ury = 0;
5551 return 0;
5554 // This version is taken from psrm.cpp
5556 #define PS_LINE_MAX 255
5557 cset white_space("\n\r \t");
5559 int ps_get_line(char *buf, FILE *fp, const char* filename)
5561 int c = getc(fp);
5562 if (c == EOF) {
5563 buf[0] = '\0';
5564 return 0;
5566 int i = 0;
5567 int err = 0;
5568 while (c != '\r' && c != '\n' && c != EOF) {
5569 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
5570 error("invalid input character code %1 in `%2'", int(c), filename);
5571 else if (i < PS_LINE_MAX)
5572 buf[i++] = c;
5573 else if (!err) {
5574 err = 1;
5575 error("PostScript file `%1' is non-conforming "
5576 "because length of line exceeds 255", filename);
5578 c = getc(fp);
5580 buf[i++] = '\n';
5581 buf[i] = '\0';
5582 if (c == '\r') {
5583 c = getc(fp);
5584 if (c != EOF && c != '\n')
5585 ungetc(c, fp);
5587 return 1;
5590 inline void assign_registers(int llx, int lly, int urx, int ury)
5592 llx_reg_contents = llx;
5593 lly_reg_contents = lly;
5594 urx_reg_contents = urx;
5595 ury_reg_contents = ury;
5598 void do_ps_file(FILE *fp, const char* filename)
5600 bounding_box bb;
5601 int bb_at_end = 0;
5602 char buf[PS_LINE_MAX];
5603 llx_reg_contents = lly_reg_contents =
5604 urx_reg_contents = ury_reg_contents = 0;
5605 if (!ps_get_line(buf, fp, filename)) {
5606 error("`%1' is empty", filename);
5607 return;
5609 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
5610 error("`%1' is not conforming to the Document Structuring Conventions",
5611 filename);
5612 return;
5614 while (ps_get_line(buf, fp, filename) != 0) {
5615 if (buf[0] != '%' || buf[1] != '%'
5616 || strncmp(buf + 2, "EndComments", 11) == 0)
5617 break;
5618 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5619 int res = parse_bounding_box(buf + 14, &bb);
5620 if (res == 1) {
5621 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5622 return;
5624 else if (res == 2) {
5625 bb_at_end = 1;
5626 break;
5628 else {
5629 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5630 filename);
5631 return;
5635 if (bb_at_end) {
5636 long offset;
5637 int last_try = 0;
5638 /* in the trailer, the last BoundingBox comment is significant */
5639 for (offset = 512; !last_try; offset *= 2) {
5640 int had_trailer = 0;
5641 int got_bb = 0;
5642 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
5643 last_try = 1;
5644 if (fseek(fp, 0L, 0) == -1)
5645 break;
5647 while (ps_get_line(buf, fp, filename) != 0) {
5648 if (buf[0] == '%' && buf[1] == '%') {
5649 if (!had_trailer) {
5650 if (strncmp(buf + 2, "Trailer", 7) == 0)
5651 had_trailer = 1;
5653 else {
5654 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
5655 int res = parse_bounding_box(buf + 14, &bb);
5656 if (res == 1)
5657 got_bb = 1;
5658 else if (res == 2) {
5659 error("`(atend)' not allowed in trailer of `%1'", filename);
5660 return;
5662 else {
5663 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
5664 filename);
5665 return;
5671 if (got_bb) {
5672 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
5673 return;
5677 error("%%%%BoundingBox comment not found in `%1'", filename);
5680 void ps_bbox_request()
5682 symbol nm = get_long_name(1);
5683 if (nm.is_null())
5684 skip_line();
5685 else {
5686 while (!tok.newline() && !tok.eof())
5687 tok.next();
5688 errno = 0;
5689 // PS files might contain non-printable characters, such as ^Z
5690 // and CRs not followed by an LF, so open them in binary mode.
5691 FILE *fp = include_search_path.open_file_cautious(nm.contents(),
5692 0, FOPEN_RB);
5693 if (fp) {
5694 do_ps_file(fp, nm.contents());
5695 fclose(fp);
5697 else
5698 error("can't open `%1': %2", nm.contents(), strerror(errno));
5699 tok.next();
5703 const char *asciify(int c)
5705 static char buf[3];
5706 buf[0] = escape_char == '\0' ? '\\' : escape_char;
5707 buf[1] = buf[2] = '\0';
5708 switch (c) {
5709 case ESCAPE_QUESTION:
5710 buf[1] = '?';
5711 break;
5712 case ESCAPE_AMPERSAND:
5713 buf[1] = '&';
5714 break;
5715 case ESCAPE_RIGHT_PARENTHESIS:
5716 buf[1] = ')';
5717 break;
5718 case ESCAPE_UNDERSCORE:
5719 buf[1] = '_';
5720 break;
5721 case ESCAPE_BAR:
5722 buf[1] = '|';
5723 break;
5724 case ESCAPE_CIRCUMFLEX:
5725 buf[1] = '^';
5726 break;
5727 case ESCAPE_LEFT_BRACE:
5728 buf[1] = '{';
5729 break;
5730 case ESCAPE_RIGHT_BRACE:
5731 buf[1] = '}';
5732 break;
5733 case ESCAPE_LEFT_QUOTE:
5734 buf[1] = '`';
5735 break;
5736 case ESCAPE_RIGHT_QUOTE:
5737 buf[1] = '\'';
5738 break;
5739 case ESCAPE_HYPHEN:
5740 buf[1] = '-';
5741 break;
5742 case ESCAPE_BANG:
5743 buf[1] = '!';
5744 break;
5745 case ESCAPE_c:
5746 buf[1] = 'c';
5747 break;
5748 case ESCAPE_e:
5749 buf[1] = 'e';
5750 break;
5751 case ESCAPE_E:
5752 buf[1] = 'E';
5753 break;
5754 case ESCAPE_PERCENT:
5755 buf[1] = '%';
5756 break;
5757 case ESCAPE_SPACE:
5758 buf[1] = ' ';
5759 break;
5760 case ESCAPE_TILDE:
5761 buf[1] = '~';
5762 break;
5763 case ESCAPE_COLON:
5764 buf[1] = ':';
5765 break;
5766 case COMPATIBLE_SAVE:
5767 case COMPATIBLE_RESTORE:
5768 buf[0] = '\0';
5769 break;
5770 default:
5771 if (invalid_input_char(c))
5772 buf[0] = '\0';
5773 else
5774 buf[0] = c;
5775 break;
5777 return buf;
5780 const char *input_char_description(int c)
5782 switch (c) {
5783 case '\n':
5784 return "a newline character";
5785 case '\b':
5786 return "a backspace character";
5787 case '\001':
5788 return "a leader character";
5789 case '\t':
5790 return "a tab character";
5791 case ' ':
5792 return "a space character";
5793 case '\0':
5794 return "a node";
5796 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
5797 if (invalid_input_char(c)) {
5798 const char *s = asciify(c);
5799 if (*s) {
5800 buf[0] = '`';
5801 strcpy(buf + 1, s);
5802 strcat(buf, "'");
5803 return buf;
5805 sprintf(buf, "magic character code %d", c);
5806 return buf;
5808 if (csprint(c)) {
5809 buf[0] = '`';
5810 buf[1] = c;
5811 buf[2] = '\'';
5812 return buf;
5814 sprintf(buf, "character code %d", c);
5815 return buf;
5818 // .tm, .tm1, and .tmc
5820 void do_terminal(int newline, int string_like)
5822 if (!tok.newline() && !tok.eof()) {
5823 int c;
5824 for (;;) {
5825 c = get_copy(0);
5826 if (string_like && c == '"') {
5827 c = get_copy(0);
5828 break;
5830 if (c != ' ' && c != '\t')
5831 break;
5833 for (; c != '\n' && c != EOF; c = get_copy(0))
5834 fputs(asciify(c), stderr);
5836 if (newline)
5837 fputc('\n', stderr);
5838 fflush(stderr);
5839 tok.next();
5842 void terminal()
5844 do_terminal(1, 0);
5847 void terminal1()
5849 do_terminal(1, 1);
5852 void terminal_continue()
5854 do_terminal(0, 1);
5857 dictionary stream_dictionary(20);
5859 void do_open(int append)
5861 symbol stream = get_name(1);
5862 if (!stream.is_null()) {
5863 symbol filename = get_long_name(1);
5864 if (!filename.is_null()) {
5865 errno = 0;
5866 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
5867 if (!fp) {
5868 error("can't open `%1' for %2: %3",
5869 filename.contents(),
5870 append ? "appending" : "writing",
5871 strerror(errno));
5872 fp = (FILE *)stream_dictionary.remove(stream);
5874 else
5875 fp = (FILE *)stream_dictionary.lookup(stream, fp);
5876 if (fp)
5877 fclose(fp);
5880 skip_line();
5883 void open_request()
5885 if (safer_flag) {
5886 error(".open request not allowed in safer mode");
5887 skip_line();
5889 else
5890 do_open(0);
5893 void opena_request()
5895 if (safer_flag) {
5896 error(".opena request not allowed in safer mode");
5897 skip_line();
5899 else
5900 do_open(1);
5903 void close_request()
5905 symbol stream = get_name(1);
5906 if (!stream.is_null()) {
5907 FILE *fp = (FILE *)stream_dictionary.remove(stream);
5908 if (!fp)
5909 error("no stream named `%1'", stream.contents());
5910 else
5911 fclose(fp);
5913 skip_line();
5916 // .write and .writec
5918 void do_write_request(int newline)
5920 symbol stream = get_name(1);
5921 if (stream.is_null()) {
5922 skip_line();
5923 return;
5925 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5926 if (!fp) {
5927 error("no stream named `%1'", stream.contents());
5928 skip_line();
5929 return;
5931 int c;
5932 while ((c = get_copy(0)) == ' ')
5934 if (c == '"')
5935 c = get_copy(0);
5936 for (; c != '\n' && c != EOF; c = get_copy(0))
5937 fputs(asciify(c), fp);
5938 if (newline)
5939 fputc('\n', fp);
5940 fflush(fp);
5941 tok.next();
5944 void write_request()
5946 do_write_request(1);
5949 void write_request_continue()
5951 do_write_request(0);
5954 void write_macro_request()
5956 symbol stream = get_name(1);
5957 if (stream.is_null()) {
5958 skip_line();
5959 return;
5961 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
5962 if (!fp) {
5963 error("no stream named `%1'", stream.contents());
5964 skip_line();
5965 return;
5967 symbol s = get_name(1);
5968 if (s.is_null()) {
5969 skip_line();
5970 return;
5972 request_or_macro *p = lookup_request(s);
5973 macro *m = p->to_macro();
5974 if (!m)
5975 error("cannot write request");
5976 else {
5977 string_iterator iter(*m);
5978 for (;;) {
5979 int c = iter.get(0);
5980 if (c == EOF)
5981 break;
5982 fputs(asciify(c), fp);
5984 fflush(fp);
5986 skip_line();
5989 void warnscale_request()
5991 if (has_arg()) {
5992 char c = tok.ch();
5993 if (c == 'u')
5994 warn_scale = 1.0;
5995 else if (c == 'i')
5996 warn_scale = (double)units_per_inch;
5997 else if (c == 'c')
5998 warn_scale = (double)units_per_inch / 2.54;
5999 else if (c == 'p')
6000 warn_scale = (double)units_per_inch / 72.0;
6001 else if (c == 'P')
6002 warn_scale = (double)units_per_inch / 6.0;
6003 else {
6004 warning(WARN_SCALE,
6005 "invalid scaling indicator `%1', using `i' instead", c);
6006 c = 'i';
6008 warn_scaling_indicator = c;
6010 skip_line();
6013 void spreadwarn_request()
6015 hunits n;
6016 if (has_arg() && get_hunits(&n, 'm')) {
6017 if (n < 0)
6018 n = 0;
6019 hunits em = curenv->get_size();
6020 spread_limit = (double)n.to_units()
6021 / (em.is_zero() ? hresolution : em.to_units());
6023 else
6024 spread_limit = -spread_limit - 1; // no arg toggles on/off without
6025 // changing value; we mirror at
6026 // -0.5 to make zero a valid value
6027 skip_line();
6030 static void init_charset_table()
6032 char buf[16];
6033 strcpy(buf, "char");
6034 for (int i = 0; i < 256; i++) {
6035 strcpy(buf + 4, i_to_a(i));
6036 charset_table[i] = get_charinfo(symbol(buf));
6037 charset_table[i]->set_ascii_code(i);
6038 if (csalpha(i))
6039 charset_table[i]->set_hyphenation_code(cmlower(i));
6041 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
6042 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
6043 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
6044 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
6045 charset_table['"']->set_flags(charinfo::TRANSPARENT);
6046 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
6047 charset_table[')']->set_flags(charinfo::TRANSPARENT);
6048 charset_table[']']->set_flags(charinfo::TRANSPARENT);
6049 charset_table['*']->set_flags(charinfo::TRANSPARENT);
6050 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
6051 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
6052 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
6053 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6054 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6055 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6056 get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6057 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6058 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
6059 page_character = charset_table['%'];
6062 static void init_hpf_code_table()
6064 for (int i = 0; i < 256; i++)
6065 hpf_code_table[i] = i;
6068 static void do_translate(int translate_transparent, int translate_input)
6070 tok.skip();
6071 while (!tok.newline() && !tok.eof()) {
6072 if (tok.space()) {
6073 // This is a really bizarre troff feature.
6074 tok.next();
6075 translate_space_to_dummy = tok.dummy();
6076 if (tok.newline() || tok.eof())
6077 break;
6078 tok.next();
6079 continue;
6081 charinfo *ci1 = tok.get_char(1);
6082 if (ci1 == 0)
6083 break;
6084 tok.next();
6085 if (tok.newline() || tok.eof()) {
6086 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6087 translate_transparent);
6088 break;
6090 if (tok.space())
6091 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6092 translate_transparent);
6093 else if (tok.stretchable_space())
6094 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
6095 translate_transparent);
6096 else if (tok.dummy())
6097 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
6098 translate_transparent);
6099 else if (tok.hyphen_indicator())
6100 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
6101 translate_transparent);
6102 else {
6103 charinfo *ci2 = tok.get_char(1);
6104 if (ci2 == 0)
6105 break;
6106 if (ci1 == ci2)
6107 ci1->set_translation(0, translate_transparent, translate_input);
6108 else
6109 ci1->set_translation(ci2, translate_transparent, translate_input);
6111 tok.next();
6113 skip_line();
6116 void translate()
6118 do_translate(1, 0);
6121 void translate_no_transparent()
6123 do_translate(0, 0);
6126 void translate_input()
6128 do_translate(1, 1);
6131 void char_flags()
6133 int flags;
6134 if (get_integer(&flags))
6135 while (has_arg()) {
6136 charinfo *ci = tok.get_char(1);
6137 if (ci) {
6138 charinfo *tem = ci->get_translation();
6139 if (tem)
6140 ci = tem;
6141 ci->set_flags(flags);
6143 tok.next();
6145 skip_line();
6148 void hyphenation_code()
6150 tok.skip();
6151 while (!tok.newline() && !tok.eof()) {
6152 charinfo *ci = tok.get_char(1);
6153 if (ci == 0)
6154 break;
6155 tok.next();
6156 tok.skip();
6157 unsigned char c = tok.ch();
6158 if (c == 0) {
6159 error("hyphenation code must be ordinary character");
6160 break;
6162 if (csdigit(c)) {
6163 error("hyphenation code cannot be digit");
6164 break;
6166 ci->set_hyphenation_code(c);
6167 if (ci->get_translation()
6168 && ci->get_translation()->get_translation_input())
6169 ci->get_translation()->set_hyphenation_code(c);
6170 tok.next();
6171 tok.skip();
6173 skip_line();
6176 void hyphenation_patterns_file_code()
6178 tok.skip();
6179 while (!tok.newline() && !tok.eof()) {
6180 int n1, n2;
6181 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
6182 if (!has_arg()) {
6183 error("missing output hyphenation code");
6184 break;
6186 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
6187 hpf_code_table[n1] = n2;
6188 tok.skip();
6190 else {
6191 error("output hyphenation code must be integer in the range 0..255");
6192 break;
6195 else {
6196 error("input hyphenation code must be integer in the range 0..255");
6197 break;
6200 skip_line();
6203 charinfo *token::get_char(int required)
6205 if (type == TOKEN_CHAR)
6206 return charset_table[c];
6207 if (type == TOKEN_SPECIAL)
6208 return get_charinfo(nm);
6209 if (type == TOKEN_NUMBERED_CHAR)
6210 return get_charinfo_by_number(val);
6211 if (type == TOKEN_ESCAPE) {
6212 if (escape_char != 0)
6213 return charset_table[escape_char];
6214 else {
6215 error("`\\e' used while no current escape character");
6216 return 0;
6219 if (required) {
6220 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6221 warning(WARN_MISSING, "missing normal or special character");
6222 else
6223 error("normal or special character expected (got %1)", description());
6225 return 0;
6228 charinfo *get_optional_char()
6230 while (tok.space())
6231 tok.next();
6232 charinfo *ci = tok.get_char();
6233 if (!ci)
6234 check_missing_character();
6235 else
6236 tok.next();
6237 return ci;
6240 void check_missing_character()
6242 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6243 error("normal or special character expected (got %1): "
6244 "treated as missing",
6245 tok.description());
6248 // this is for \Z
6250 int token::add_to_node_list(node **pp)
6252 hunits w;
6253 int s;
6254 node *n = 0;
6255 switch (type) {
6256 case TOKEN_CHAR:
6257 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6258 break;
6259 case TOKEN_DUMMY:
6260 n = new dummy_node;
6261 break;
6262 case TOKEN_ESCAPE:
6263 if (escape_char != 0)
6264 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6265 break;
6266 case TOKEN_HYPHEN_INDICATOR:
6267 *pp = (*pp)->add_discretionary_hyphen();
6268 break;
6269 case TOKEN_ITALIC_CORRECTION:
6270 *pp = (*pp)->add_italic_correction(&w);
6271 break;
6272 case TOKEN_LEFT_BRACE:
6273 break;
6274 case TOKEN_MARK_INPUT:
6275 set_number_reg(nm, curenv->get_input_line_position().to_units());
6276 break;
6277 case TOKEN_NODE:
6278 n = nd;
6279 nd = 0;
6280 break;
6281 case TOKEN_NUMBERED_CHAR:
6282 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6283 break;
6284 case TOKEN_RIGHT_BRACE:
6285 break;
6286 case TOKEN_SPACE:
6287 n = new hmotion_node(curenv->get_space_width(),
6288 curenv->get_fill_color());
6289 break;
6290 case TOKEN_SPECIAL:
6291 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6292 break;
6293 case TOKEN_STRETCHABLE_SPACE:
6294 n = new unbreakable_space_node(curenv->get_space_width(),
6295 curenv->get_fill_color());
6296 break;
6297 case TOKEN_UNSTRETCHABLE_SPACE:
6298 n = new space_char_hmotion_node(curenv->get_space_width(),
6299 curenv->get_fill_color());
6300 break;
6301 case TOKEN_TRANSPARENT_DUMMY:
6302 n = new transparent_dummy_node;
6303 break;
6304 case TOKEN_ZERO_WIDTH_BREAK:
6305 n = new space_node(H0, curenv->get_fill_color());
6306 n->freeze_space();
6307 n->is_escape_colon();
6308 break;
6309 default:
6310 return 0;
6312 if (n) {
6313 n->next = *pp;
6314 *pp = n;
6316 return 1;
6319 void token::process()
6321 if (possibly_handle_first_page_transition())
6322 return;
6323 switch (type) {
6324 case TOKEN_BACKSPACE:
6325 curenv->add_node(new hmotion_node(-curenv->get_space_width(),
6326 curenv->get_fill_color()));
6327 break;
6328 case TOKEN_CHAR:
6329 curenv->add_char(charset_table[c]);
6330 break;
6331 case TOKEN_DUMMY:
6332 curenv->add_node(new dummy_node);
6333 break;
6334 case TOKEN_EMPTY:
6335 assert(0);
6336 break;
6337 case TOKEN_EOF:
6338 assert(0);
6339 break;
6340 case TOKEN_ESCAPE:
6341 if (escape_char != 0)
6342 curenv->add_char(charset_table[escape_char]);
6343 break;
6344 case TOKEN_BEGIN_TRAP:
6345 case TOKEN_END_TRAP:
6346 case TOKEN_PAGE_EJECTOR:
6347 // these are all handled in process_input_stack()
6348 break;
6349 case TOKEN_HYPHEN_INDICATOR:
6350 curenv->add_hyphen_indicator();
6351 break;
6352 case TOKEN_INTERRUPT:
6353 curenv->interrupt();
6354 break;
6355 case TOKEN_ITALIC_CORRECTION:
6356 curenv->add_italic_correction();
6357 break;
6358 case TOKEN_LEADER:
6359 curenv->handle_tab(1);
6360 break;
6361 case TOKEN_LEFT_BRACE:
6362 break;
6363 case TOKEN_MARK_INPUT:
6364 set_number_reg(nm, curenv->get_input_line_position().to_units());
6365 break;
6366 case TOKEN_NEWLINE:
6367 curenv->newline();
6368 break;
6369 case TOKEN_NODE:
6370 curenv->add_node(nd);
6371 nd = 0;
6372 break;
6373 case TOKEN_NUMBERED_CHAR:
6374 curenv->add_char(get_charinfo_by_number(val));
6375 break;
6376 case TOKEN_REQUEST:
6377 // handled in process_input_stack()
6378 break;
6379 case TOKEN_RIGHT_BRACE:
6380 break;
6381 case TOKEN_SPACE:
6382 curenv->space();
6383 break;
6384 case TOKEN_SPECIAL:
6385 curenv->add_char(get_charinfo(nm));
6386 break;
6387 case TOKEN_SPREAD:
6388 curenv->spread();
6389 break;
6390 case TOKEN_STRETCHABLE_SPACE:
6391 curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
6392 curenv->get_fill_color()));
6393 break;
6394 case TOKEN_UNSTRETCHABLE_SPACE:
6395 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
6396 curenv->get_fill_color()));
6397 break;
6398 case TOKEN_TAB:
6399 curenv->handle_tab(0);
6400 break;
6401 case TOKEN_TRANSPARENT:
6402 break;
6403 case TOKEN_TRANSPARENT_DUMMY:
6404 curenv->add_node(new transparent_dummy_node);
6405 break;
6406 case TOKEN_ZERO_WIDTH_BREAK:
6408 node *tmp = new space_node(H0, curenv->get_fill_color());
6409 tmp->freeze_space();
6410 tmp->is_escape_colon();
6411 curenv->add_node(tmp);
6412 break;
6414 default:
6415 assert(0);
6419 class nargs_reg : public reg {
6420 public:
6421 const char *get_string();
6424 const char *nargs_reg::get_string()
6426 return i_to_a(input_stack::nargs());
6429 class lineno_reg : public reg {
6430 public:
6431 const char *get_string();
6434 const char *lineno_reg::get_string()
6436 int line;
6437 const char *file;
6438 if (!input_stack::get_location(0, &file, &line))
6439 line = 0;
6440 return i_to_a(line);
6443 class writable_lineno_reg : public general_reg {
6444 public:
6445 writable_lineno_reg();
6446 void set_value(units);
6447 int get_value(units *);
6450 writable_lineno_reg::writable_lineno_reg()
6454 int writable_lineno_reg::get_value(units *res)
6456 int line;
6457 const char *file;
6458 if (!input_stack::get_location(0, &file, &line))
6459 return 0;
6460 *res = line;
6461 return 1;
6464 void writable_lineno_reg::set_value(units n)
6466 input_stack::set_location(0, n);
6469 class filename_reg : public reg {
6470 public:
6471 const char *get_string();
6474 const char *filename_reg::get_string()
6476 int line;
6477 const char *file;
6478 if (input_stack::get_location(0, &file, &line))
6479 return file;
6480 else
6481 return 0;
6484 class constant_reg : public reg {
6485 const char *s;
6486 public:
6487 constant_reg(const char *);
6488 const char *get_string();
6491 constant_reg::constant_reg(const char *p) : s(p)
6495 const char *constant_reg::get_string()
6497 return s;
6500 constant_int_reg::constant_int_reg(int *q) : p(q)
6504 const char *constant_int_reg::get_string()
6506 return i_to_a(*p);
6509 void abort_request()
6511 int c;
6512 if (tok.eof())
6513 c = EOF;
6514 else if (tok.newline())
6515 c = '\n';
6516 else {
6517 while ((c = get_copy(0)) == ' ')
6520 if (c == EOF || c == '\n')
6521 fputs("User Abort.", stderr);
6522 else {
6523 for (; c != '\n' && c != EOF; c = get_copy(0))
6524 fputs(asciify(c), stderr);
6526 fputc('\n', stderr);
6527 cleanup_and_exit(1);
6530 char *read_string()
6532 int len = 256;
6533 char *s = new char[len];
6534 int c;
6535 while ((c = get_copy(0)) == ' ')
6537 int i = 0;
6538 while (c != '\n' && c != EOF) {
6539 if (!invalid_input_char(c)) {
6540 if (i + 2 > len) {
6541 char *tem = s;
6542 s = new char[len*2];
6543 memcpy(s, tem, len);
6544 len *= 2;
6545 a_delete tem;
6547 s[i++] = c;
6549 c = get_copy(0);
6551 s[i] = '\0';
6552 tok.next();
6553 if (i == 0) {
6554 a_delete s;
6555 return 0;
6557 return s;
6560 void pipe_output()
6562 if (safer_flag) {
6563 error(".pi request not allowed in safer mode");
6564 skip_line();
6566 else {
6567 #ifdef POPEN_MISSING
6568 error("pipes not available on this system");
6569 skip_line();
6570 #else /* not POPEN_MISSING */
6571 if (the_output) {
6572 error("can't pipe: output already started");
6573 skip_line();
6575 else {
6576 char *pc;
6577 if ((pc = read_string()) == 0)
6578 error("can't pipe to empty command");
6579 if (pipe_command) {
6580 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
6581 strcpy(s, pipe_command);
6582 strcat(s, "|");
6583 strcat(s, pc);
6584 a_delete pipe_command;
6585 a_delete pc;
6586 pipe_command = s;
6588 else
6589 pipe_command = pc;
6591 #endif /* not POPEN_MISSING */
6595 static int system_status;
6597 void system_request()
6599 if (safer_flag) {
6600 error(".sy request not allowed in safer mode");
6601 skip_line();
6603 else {
6604 char *command = read_string();
6605 if (!command)
6606 error("empty command");
6607 else {
6608 system_status = system(command);
6609 a_delete command;
6614 void copy_file()
6616 if (curdiv == topdiv && topdiv->before_first_page) {
6617 handle_initial_request(COPY_FILE_REQUEST);
6618 return;
6620 symbol filename = get_long_name(1);
6621 while (!tok.newline() && !tok.eof())
6622 tok.next();
6623 if (break_flag)
6624 curenv->do_break();
6625 if (!filename.is_null())
6626 curdiv->copy_file(filename.contents());
6627 tok.next();
6630 #ifdef COLUMN
6632 void vjustify()
6634 if (curdiv == topdiv && topdiv->before_first_page) {
6635 handle_initial_request(VJUSTIFY_REQUEST);
6636 return;
6638 symbol type = get_long_name(1);
6639 if (!type.is_null())
6640 curdiv->vjustify(type);
6641 skip_line();
6644 #endif /* COLUMN */
6646 void transparent_file()
6648 if (curdiv == topdiv && topdiv->before_first_page) {
6649 handle_initial_request(TRANSPARENT_FILE_REQUEST);
6650 return;
6652 symbol filename = get_long_name(1);
6653 while (!tok.newline() && !tok.eof())
6654 tok.next();
6655 if (break_flag)
6656 curenv->do_break();
6657 if (!filename.is_null()) {
6658 errno = 0;
6659 FILE *fp = include_search_path.open_file_cautious(filename.contents());
6660 if (!fp)
6661 error("can't open `%1': %2", filename.contents(), strerror(errno));
6662 else {
6663 int bol = 1;
6664 for (;;) {
6665 int c = getc(fp);
6666 if (c == EOF)
6667 break;
6668 if (invalid_input_char(c))
6669 warning(WARN_INPUT, "invalid input character code %1", int(c));
6670 else {
6671 curdiv->transparent_output(c);
6672 bol = c == '\n';
6675 if (!bol)
6676 curdiv->transparent_output('\n');
6677 fclose(fp);
6680 tok.next();
6683 class page_range {
6684 int first;
6685 int last;
6686 public:
6687 page_range *next;
6688 page_range(int, int, page_range *);
6689 int contains(int n);
6692 page_range::page_range(int i, int j, page_range *p)
6693 : first(i), last(j), next(p)
6697 int page_range::contains(int n)
6699 return n >= first && (last <= 0 || n <= last);
6702 page_range *output_page_list = 0;
6704 int in_output_page_list(int n)
6706 if (!output_page_list)
6707 return 1;
6708 for (page_range *p = output_page_list; p; p = p->next)
6709 if (p->contains(n))
6710 return 1;
6711 return 0;
6714 static void parse_output_page_list(char *p)
6716 for (;;) {
6717 int i;
6718 if (*p == '-')
6719 i = 1;
6720 else if (csdigit(*p)) {
6721 i = 0;
6723 i = i*10 + *p++ - '0';
6724 while (csdigit(*p));
6726 else
6727 break;
6728 int j;
6729 if (*p == '-') {
6730 p++;
6731 j = 0;
6732 if (csdigit(*p)) {
6734 j = j*10 + *p++ - '0';
6735 while (csdigit(*p));
6738 else
6739 j = i;
6740 if (j == 0)
6741 last_page_number = -1;
6742 else if (last_page_number >= 0 && j > last_page_number)
6743 last_page_number = j;
6744 output_page_list = new page_range(i, j, output_page_list);
6745 if (*p != ',')
6746 break;
6747 ++p;
6749 if (*p != '\0') {
6750 error("bad output page list");
6751 output_page_list = 0;
6755 static FILE *open_mac_file(const char *mac, char **path)
6757 // Try first FOOBAR.tmac, then tmac.FOOBAR
6758 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
6759 strcpy(s1, mac);
6760 strcat(s1, MACRO_POSTFIX);
6761 FILE *fp = mac_path->open_file(s1, path);
6762 a_delete s1;
6763 if (!fp) {
6764 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
6765 strcpy(s2, MACRO_PREFIX);
6766 strcat(s2, mac);
6767 fp = mac_path->open_file(s2, path);
6768 a_delete s2;
6770 return fp;
6773 static void process_macro_file(const char *mac)
6775 char *path;
6776 FILE *fp = open_mac_file(mac, &path);
6777 if (!fp)
6778 fatal("can't find macro file %1", mac);
6779 const char *s = symbol(path).contents();
6780 a_delete path;
6781 input_stack::push(new file_iterator(fp, s));
6782 tok.next();
6783 process_input_stack();
6786 static void process_startup_file(const char *filename)
6788 char *path;
6789 search_path *orig_mac_path = mac_path;
6790 mac_path = &config_macro_path;
6791 FILE *fp = mac_path->open_file(filename, &path);
6792 if (fp) {
6793 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6794 a_delete path;
6795 tok.next();
6796 process_input_stack();
6798 mac_path = orig_mac_path;
6801 void macro_source()
6803 symbol nm = get_long_name(1);
6804 if (nm.is_null())
6805 skip_line();
6806 else {
6807 while (!tok.newline() && !tok.eof())
6808 tok.next();
6809 char *path;
6810 FILE *fp = mac_path->open_file(nm.contents(), &path);
6811 // .mso doesn't (and cannot) go through open_mac_file, so we
6812 // need to do it here manually: If we have tmac.FOOBAR, try
6813 // FOOBAR.tmac and vice versa
6814 if (!fp) {
6815 const char *fn = nm.contents();
6816 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
6817 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
6818 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
6819 strcat(s, MACRO_POSTFIX);
6820 fp = mac_path->open_file(s, &path);
6821 a_delete s;
6823 if (!fp) {
6824 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
6825 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
6826 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
6827 strcpy(s, MACRO_PREFIX);
6828 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
6829 fp = mac_path->open_file(s, &path);
6830 a_delete s;
6834 if (fp) {
6835 input_stack::push(new file_iterator(fp, symbol(path).contents()));
6836 a_delete path;
6838 else
6839 error("can't find macro file `%1'", nm.contents());
6840 tok.next();
6844 static void process_input_file(const char *name)
6846 FILE *fp;
6847 if (strcmp(name, "-") == 0) {
6848 clearerr(stdin);
6849 fp = stdin;
6851 else {
6852 errno = 0;
6853 fp = include_search_path.open_file_cautious(name);
6854 if (!fp)
6855 fatal("can't open `%1': %2", name, strerror(errno));
6857 input_stack::push(new file_iterator(fp, name));
6858 tok.next();
6859 process_input_stack();
6862 // make sure the_input is empty before calling this
6864 static int evaluate_expression(const char *expr, units *res)
6866 input_stack::push(make_temp_iterator(expr));
6867 tok.next();
6868 int success = get_number(res, 'u');
6869 while (input_stack::get(0) != EOF)
6871 return success;
6874 static void do_register_assignment(const char *s)
6876 const char *p = strchr(s, '=');
6877 if (!p) {
6878 char buf[2];
6879 buf[0] = s[0];
6880 buf[1] = 0;
6881 units n;
6882 if (evaluate_expression(s + 1, &n))
6883 set_number_reg(buf, n);
6885 else {
6886 char *buf = new char[p - s + 1];
6887 memcpy(buf, s, p - s);
6888 buf[p - s] = 0;
6889 units n;
6890 if (evaluate_expression(p + 1, &n))
6891 set_number_reg(buf, n);
6892 a_delete buf;
6896 static void set_string(const char *name, const char *value)
6898 macro *m = new macro;
6899 for (const char *p = value; *p; p++)
6900 if (!invalid_input_char((unsigned char)*p))
6901 m->append(*p);
6902 request_dictionary.define(name, m);
6905 static void do_string_assignment(const char *s)
6907 const char *p = strchr(s, '=');
6908 if (!p) {
6909 char buf[2];
6910 buf[0] = s[0];
6911 buf[1] = 0;
6912 set_string(buf, s + 1);
6914 else {
6915 char *buf = new char[p - s + 1];
6916 memcpy(buf, s, p - s);
6917 buf[p - s] = 0;
6918 set_string(buf, p + 1);
6919 a_delete buf;
6923 struct string_list {
6924 const char *s;
6925 string_list *next;
6926 string_list(const char *ss) : s(ss), next(0) {}
6929 #if 0
6930 static void prepend_string(const char *s, string_list **p)
6932 string_list *l = new string_list(s);
6933 l->next = *p;
6934 *p = l;
6936 #endif
6938 static void add_string(const char *s, string_list **p)
6940 while (*p)
6941 p = &((*p)->next);
6942 *p = new string_list(s);
6945 void usage(FILE *stream, const char *prog)
6947 fprintf(stream,
6948 "usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
6949 " -rcn -Tname -Fdir -Idir -Mdir [files...]\n",
6950 prog);
6953 int main(int argc, char **argv)
6955 program_name = argv[0];
6956 static char stderr_buf[BUFSIZ];
6957 setbuf(stderr, stderr_buf);
6958 int c;
6959 string_list *macros = 0;
6960 string_list *register_assignments = 0;
6961 string_list *string_assignments = 0;
6962 int iflag = 0;
6963 int tflag = 0;
6964 int fflag = 0;
6965 int nflag = 0;
6966 int no_rc = 0; // don't process troffrc and troffrc-end
6967 int next_page_number;
6968 opterr = 0;
6969 hresolution = vresolution = 1;
6970 // restore $PATH if called from groff
6971 char* groff_path = getenv("GROFF_PATH__");
6972 if (groff_path) {
6973 string e = "PATH";
6974 e += '=';
6975 if (*groff_path)
6976 e += groff_path;
6977 e += '\0';
6978 if (putenv(strsave(e.contents())))
6979 fatal("putenv failed");
6981 static const struct option long_options[] = {
6982 { "help", no_argument, 0, CHAR_MAX + 1 },
6983 { "version", no_argument, 0, 'v' },
6984 { 0, 0, 0, 0 }
6986 while ((c = getopt_long(argc, argv, "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU",
6987 long_options, 0))
6988 != EOF)
6989 switch(c) {
6990 case 'v':
6992 printf("GNU troff (groff) version %s\n", Version_string);
6993 exit(0);
6994 break;
6996 case 'I':
6997 // Search path for .psbb files
6998 // and most other non-system input files.
6999 include_search_path.command_line_dir(optarg);
7000 break;
7001 case 'T':
7002 device = optarg;
7003 tflag = 1;
7004 is_html = (strcmp(device, "html") == 0);
7005 break;
7006 case 'C':
7007 compatible_flag = 1;
7008 // fall through
7009 case 'c':
7010 color_flag = 0;
7011 break;
7012 case 'M':
7013 macro_path.command_line_dir(optarg);
7014 safer_macro_path.command_line_dir(optarg);
7015 config_macro_path.command_line_dir(optarg);
7016 break;
7017 case 'F':
7018 font::command_line_font_dir(optarg);
7019 break;
7020 case 'm':
7021 add_string(optarg, &macros);
7022 break;
7023 case 'E':
7024 inhibit_errors = 1;
7025 break;
7026 case 'R':
7027 no_rc = 1;
7028 break;
7029 case 'w':
7030 enable_warning(optarg);
7031 break;
7032 case 'W':
7033 disable_warning(optarg);
7034 break;
7035 case 'i':
7036 iflag = 1;
7037 break;
7038 case 'b':
7039 backtrace_flag = 1;
7040 break;
7041 case 'a':
7042 ascii_output_flag = 1;
7043 break;
7044 case 'z':
7045 suppress_output_flag = 1;
7046 break;
7047 case 'n':
7048 if (sscanf(optarg, "%d", &next_page_number) == 1)
7049 nflag++;
7050 else
7051 error("bad page number");
7052 break;
7053 case 'o':
7054 parse_output_page_list(optarg);
7055 break;
7056 case 'd':
7057 if (*optarg == '\0')
7058 error("`-d' requires non-empty argument");
7059 else
7060 add_string(optarg, &string_assignments);
7061 break;
7062 case 'r':
7063 if (*optarg == '\0')
7064 error("`-r' requires non-empty argument");
7065 else
7066 add_string(optarg, &register_assignments);
7067 break;
7068 case 'f':
7069 default_family = symbol(optarg);
7070 fflag = 1;
7071 break;
7072 case 'q':
7073 case 's':
7074 case 't':
7075 // silently ignore these
7076 break;
7077 case 'U':
7078 safer_flag = 0; // unsafe behaviour
7079 break;
7080 case CHAR_MAX + 1: // --help
7081 usage(stdout, argv[0]);
7082 exit(0);
7083 break;
7084 case '?':
7085 usage(stderr, argv[0]);
7086 exit(1);
7087 break; // never reached
7088 default:
7089 assert(0);
7091 if (!safer_flag)
7092 mac_path = &macro_path;
7093 set_string(".T", device);
7094 init_charset_table();
7095 init_hpf_code_table();
7096 if (!font::load_desc())
7097 fatal("sorry, I can't continue");
7098 units_per_inch = font::res;
7099 hresolution = font::hor;
7100 vresolution = font::vert;
7101 sizescale = font::sizescale;
7102 tcommand_flag = font::tcommand;
7103 warn_scale = (double)units_per_inch;
7104 warn_scaling_indicator = 'i';
7105 if (!fflag && font::family != 0 && *font::family != '\0')
7106 default_family = symbol(font::family);
7107 font_size::init_size_table(font::sizes);
7108 int i;
7109 int j = 1;
7110 if (font::style_table) {
7111 for (i = 0; font::style_table[i]; i++)
7112 mount_style(j++, symbol(font::style_table[i]));
7114 for (i = 0; font::font_name_table[i]; i++, j++)
7115 // In the DESC file a font name of 0 (zero) means leave this
7116 // position empty.
7117 if (strcmp(font::font_name_table[i], "0") != 0)
7118 mount_font(j, symbol(font::font_name_table[i]));
7119 curdiv = topdiv = new top_level_diversion;
7120 if (nflag)
7121 topdiv->set_next_page_number(next_page_number);
7122 init_input_requests();
7123 init_env_requests();
7124 init_div_requests();
7125 #ifdef COLUMN
7126 init_column_requests();
7127 #endif /* COLUMN */
7128 init_node_requests();
7129 number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0"));
7130 init_registers();
7131 init_reg_requests();
7132 init_hyphen_requests();
7133 init_environments();
7134 while (string_assignments) {
7135 do_string_assignment(string_assignments->s);
7136 string_list *tem = string_assignments;
7137 string_assignments = string_assignments->next;
7138 delete tem;
7140 while (register_assignments) {
7141 do_register_assignment(register_assignments->s);
7142 string_list *tem = register_assignments;
7143 register_assignments = register_assignments->next;
7144 delete tem;
7146 if (!no_rc)
7147 process_startup_file(INITIAL_STARTUP_FILE);
7148 while (macros) {
7149 process_macro_file(macros->s);
7150 string_list *tem = macros;
7151 macros = macros->next;
7152 delete tem;
7154 if (!no_rc)
7155 process_startup_file(FINAL_STARTUP_FILE);
7156 for (i = optind; i < argc; i++)
7157 process_input_file(argv[i]);
7158 if (optind >= argc || iflag)
7159 process_input_file("-");
7160 exit_troff();
7161 return 0; // not reached
7164 void warn_request()
7166 int n;
7167 if (has_arg() && get_integer(&n)) {
7168 if (n & ~WARN_TOTAL) {
7169 warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
7170 n &= WARN_TOTAL;
7172 warning_mask = n;
7174 else
7175 warning_mask = WARN_TOTAL;
7176 skip_line();
7179 static void init_registers()
7181 #ifdef LONG_FOR_TIME_T
7182 long
7183 #else /* not LONG_FOR_TIME_T */
7184 time_t
7185 #endif /* not LONG_FOR_TIME_T */
7186 t = time(0);
7187 // Use struct here to work around misfeature in old versions of g++.
7188 struct tm *tt = localtime(&t);
7189 set_number_reg("seconds", int(tt->tm_sec));
7190 set_number_reg("minutes", int(tt->tm_min));
7191 set_number_reg("hours", int(tt->tm_hour));
7192 set_number_reg("dw", int(tt->tm_wday + 1));
7193 set_number_reg("dy", int(tt->tm_mday));
7194 set_number_reg("mo", int(tt->tm_mon + 1));
7195 set_number_reg("year", int(1900 + tt->tm_year));
7196 set_number_reg("yr", int(tt->tm_year));
7197 set_number_reg("$$", getpid());
7198 number_reg_dictionary.define(".A",
7199 new constant_reg(ascii_output_flag
7200 ? "1"
7201 : "0"));
7205 * registers associated with \O
7208 static int output_reg_minx_contents = -1;
7209 static int output_reg_miny_contents = -1;
7210 static int output_reg_maxx_contents = -1;
7211 static int output_reg_maxy_contents = -1;
7213 void check_output_limits(int x, int y)
7215 if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
7216 output_reg_minx_contents = x;
7217 if (x > output_reg_maxx_contents)
7218 output_reg_maxx_contents = x;
7219 if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
7220 output_reg_miny_contents = y;
7221 if (y > output_reg_maxy_contents)
7222 output_reg_maxy_contents = y;
7225 void reset_output_registers()
7227 output_reg_minx_contents = -1;
7228 output_reg_miny_contents = -1;
7229 output_reg_maxx_contents = -1;
7230 output_reg_maxy_contents = -1;
7233 void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
7235 *minx = output_reg_minx_contents;
7236 *miny = output_reg_miny_contents;
7237 *maxx = output_reg_maxx_contents;
7238 *maxy = output_reg_maxy_contents;
7241 void init_input_requests()
7243 init_request("ab", abort_request);
7244 init_request("als", alias_macro);
7245 init_request("am", append_macro);
7246 init_request("am1", append_nocomp_macro);
7247 init_request("ami", append_indirect_macro);
7248 init_request("ami1", append_indirect_nocomp_macro);
7249 init_request("as", append_string);
7250 init_request("as1", append_nocomp_string);
7251 init_request("asciify", asciify_macro);
7252 init_request("backtrace", backtrace_request);
7253 init_request("blm", blank_line_macro);
7254 init_request("break", while_break_request);
7255 init_request("cf", copy_file);
7256 init_request("cflags", char_flags);
7257 init_request("char", define_character);
7258 init_request("chop", chop_macro);
7259 init_request("close", close_request);
7260 init_request("color", activate_color);
7261 init_request("composite", composite_request);
7262 init_request("continue", while_continue_request);
7263 init_request("cp", compatible);
7264 init_request("de", define_macro);
7265 init_request("de1", define_nocomp_macro);
7266 init_request("defcolor", define_color);
7267 init_request("dei", define_indirect_macro);
7268 init_request("dei1", define_indirect_nocomp_macro);
7269 init_request("do", do_request);
7270 init_request("ds", define_string);
7271 init_request("ds1", define_nocomp_string);
7272 init_request("ec", set_escape_char);
7273 init_request("ecr", restore_escape_char);
7274 init_request("ecs", save_escape_char);
7275 init_request("el", else_request);
7276 init_request("em", end_macro);
7277 init_request("eo", escape_off);
7278 init_request("ex", exit_request);
7279 init_request("fchar", define_fallback_character);
7280 #ifdef WIDOW_CONTROL
7281 init_request("fpl", flush_pending_lines);
7282 #endif /* WIDOW_CONTROL */
7283 init_request("hcode", hyphenation_code);
7284 init_request("hpfcode", hyphenation_patterns_file_code);
7285 init_request("ie", if_else_request);
7286 init_request("if", if_request);
7287 init_request("ig", ignore);
7288 init_request("length", length_request);
7289 init_request("lf", line_file);
7290 init_request("mso", macro_source);
7291 init_request("nop", nop_request);
7292 init_request("nroff", nroff_request);
7293 init_request("nx", next_file);
7294 init_request("open", open_request);
7295 init_request("opena", opena_request);
7296 init_request("output", output_request);
7297 init_request("pc", set_page_character);
7298 init_request("pi", pipe_output);
7299 init_request("pm", print_macros);
7300 init_request("psbb", ps_bbox_request);
7301 #ifndef POPEN_MISSING
7302 init_request("pso", pipe_source);
7303 #endif /* not POPEN_MISSING */
7304 init_request("rchar", remove_character);
7305 init_request("rd", read_request);
7306 init_request("return", return_macro_request);
7307 init_request("rm", remove_macro);
7308 init_request("rn", rename_macro);
7309 init_request("schar", define_special_character);
7310 init_request("shift", shift);
7311 init_request("so", source);
7312 init_request("spreadwarn", spreadwarn_request);
7313 init_request("substring", substring_request);
7314 init_request("sy", system_request);
7315 init_request("tm", terminal);
7316 init_request("tm1", terminal1);
7317 init_request("tmc", terminal_continue);
7318 init_request("tr", translate);
7319 init_request("trf", transparent_file);
7320 init_request("trin", translate_input);
7321 init_request("trnt", translate_no_transparent);
7322 init_request("troff", troff_request);
7323 init_request("unformat", unformat_macro);
7324 #ifdef COLUMN
7325 init_request("vj", vjustify);
7326 #endif /* COLUMN */
7327 init_request("warn", warn_request);
7328 init_request("warnscale", warnscale_request);
7329 init_request("while", while_request);
7330 init_request("write", write_request);
7331 init_request("writec", write_request_continue);
7332 init_request("writem", write_macro_request);
7333 number_reg_dictionary.define(".$", new nargs_reg);
7334 number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag));
7335 number_reg_dictionary.define(".c", new lineno_reg);
7336 number_reg_dictionary.define(".color", new constant_int_reg(&color_flag));
7337 number_reg_dictionary.define(".F", new filename_reg);
7338 number_reg_dictionary.define(".g", new constant_reg("1"));
7339 number_reg_dictionary.define(".H", new constant_int_reg(&hresolution));
7340 number_reg_dictionary.define(".R", new constant_reg("10000"));
7341 number_reg_dictionary.define(".V", new constant_int_reg(&vresolution));
7342 number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask));
7343 extern const char *major_version;
7344 number_reg_dictionary.define(".x", new constant_reg(major_version));
7345 extern const char *revision;
7346 number_reg_dictionary.define(".Y", new constant_reg(revision));
7347 extern const char *minor_version;
7348 number_reg_dictionary.define(".y", new constant_reg(minor_version));
7349 number_reg_dictionary.define("c.", new writable_lineno_reg);
7350 number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents));
7351 number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents));
7352 number_reg_dictionary.define("opmaxx",
7353 new variable_reg(&output_reg_maxx_contents));
7354 number_reg_dictionary.define("opmaxy",
7355 new variable_reg(&output_reg_maxy_contents));
7356 number_reg_dictionary.define("opminx",
7357 new variable_reg(&output_reg_minx_contents));
7358 number_reg_dictionary.define("opminy",
7359 new variable_reg(&output_reg_miny_contents));
7360 number_reg_dictionary.define("slimit",
7361 new variable_reg(&input_stack::limit));
7362 number_reg_dictionary.define("systat", new variable_reg(&system_status));
7363 number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents));
7364 number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents));
7367 object_dictionary request_dictionary(501);
7369 void init_request(const char *s, REQUEST_FUNCP f)
7371 request_dictionary.define(s, new request(f));
7374 static request_or_macro *lookup_request(symbol nm)
7376 assert(!nm.is_null());
7377 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
7378 if (p == 0) {
7379 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
7380 p = new macro;
7381 request_dictionary.define(nm, p);
7383 return p;
7386 node *charinfo_to_node_list(charinfo *ci, const environment *envp)
7388 // Don't interpret character definitions in compatible mode.
7389 int old_compatible_flag = compatible_flag;
7390 compatible_flag = 0;
7391 int old_escape_char = escape_char;
7392 escape_char = '\\';
7393 macro *mac = ci->set_macro(0);
7394 assert(mac != 0);
7395 environment *oldenv = curenv;
7396 environment env(envp);
7397 curenv = &env;
7398 curenv->set_composite();
7399 token old_tok = tok;
7400 input_stack::add_boundary();
7401 string_iterator *si =
7402 new string_iterator(*mac, "composite character", ci->nm);
7403 input_stack::push(si);
7404 // we don't use process_input_stack, because we don't want to recognise
7405 // requests
7406 for (;;) {
7407 tok.next();
7408 if (tok.eof())
7409 break;
7410 if (tok.newline()) {
7411 error("composite character mustn't contain newline");
7412 while (!tok.eof())
7413 tok.next();
7414 break;
7416 else
7417 tok.process();
7419 node *n = curenv->extract_output_line();
7420 input_stack::remove_boundary();
7421 ci->set_macro(mac);
7422 tok = old_tok;
7423 curenv = oldenv;
7424 compatible_flag = old_compatible_flag;
7425 escape_char = old_escape_char;
7426 have_input = 0;
7427 return n;
7430 static node *read_draw_node()
7432 token start;
7433 start.next();
7434 if (!start.delimiter(1)){
7435 do {
7436 tok.next();
7437 } while (tok != start && !tok.newline() && !tok.eof());
7439 else {
7440 tok.next();
7441 if (tok == start)
7442 error("missing argument");
7443 else {
7444 unsigned char type = tok.ch();
7445 if (type == 'F') {
7446 read_color_draw_node(start);
7447 return 0;
7449 tok.next();
7450 int maxpoints = 10;
7451 hvpair *point = new hvpair[maxpoints];
7452 int npoints = 0;
7453 int no_last_v = 0;
7454 int err = 0;
7455 int i;
7456 for (i = 0; tok != start; i++) {
7457 if (i == maxpoints) {
7458 hvpair *oldpoint = point;
7459 point = new hvpair[maxpoints*2];
7460 for (int j = 0; j < maxpoints; j++)
7461 point[j] = oldpoint[j];
7462 maxpoints *= 2;
7463 a_delete oldpoint;
7465 if (!get_hunits(&point[i].h,
7466 type == 'f' || type == 't' ? 'u' : 'm')) {
7467 err = 1;
7468 break;
7470 ++npoints;
7471 tok.skip();
7472 point[i].v = V0;
7473 if (tok == start) {
7474 no_last_v = 1;
7475 break;
7477 if (!get_vunits(&point[i].v, 'v')) {
7478 err = 1;
7479 break;
7481 tok.skip();
7483 while (tok != start && !tok.newline() && !tok.eof())
7484 tok.next();
7485 if (!err) {
7486 switch (type) {
7487 case 'l':
7488 if (npoints != 1 || no_last_v) {
7489 error("two arguments needed for line");
7490 npoints = 1;
7492 break;
7493 case 'c':
7494 if (npoints != 1 || !no_last_v) {
7495 error("one argument needed for circle");
7496 npoints = 1;
7497 point[0].v = V0;
7499 break;
7500 case 'e':
7501 if (npoints != 1 || no_last_v) {
7502 error("two arguments needed for ellipse");
7503 npoints = 1;
7505 break;
7506 case 'a':
7507 if (npoints != 2 || no_last_v) {
7508 error("four arguments needed for arc");
7509 npoints = 2;
7511 break;
7512 case '~':
7513 if (no_last_v)
7514 error("even number of arguments needed for spline");
7515 break;
7516 case 'f':
7517 if (npoints != 1 || !no_last_v) {
7518 error("one argument needed for gray shade");
7519 npoints = 1;
7520 point[0].v = V0;
7522 default:
7523 // silently pass it through
7524 break;
7526 draw_node *dn = new draw_node(type, point, npoints,
7527 curenv->get_font_size(),
7528 curenv->get_glyph_color(),
7529 curenv->get_fill_color());
7530 a_delete point;
7531 return dn;
7533 else {
7534 a_delete point;
7538 return 0;
7541 static void read_color_draw_node(token &start)
7543 tok.next();
7544 if (tok == start) {
7545 error("missing color scheme");
7546 return;
7548 unsigned char scheme = tok.ch();
7549 tok.next();
7550 color *col;
7551 char end = start.ch();
7552 switch (scheme) {
7553 case 'c':
7554 col = read_cmy(end);
7555 break;
7556 case 'd':
7557 col = &default_color;
7558 break;
7559 case 'g':
7560 col = read_gray(end);
7561 break;
7562 case 'k':
7563 col = read_cmyk(end);
7564 break;
7565 case 'r':
7566 col = read_rgb(end);
7567 break;
7569 if (col)
7570 curenv->set_fill_color(col);
7571 while (tok != start) {
7572 if (tok.newline() || tok.eof()) {
7573 warning(WARN_DELIM, "missing closing delimiter");
7574 input_stack::push(make_temp_iterator("\n"));
7575 break;
7577 tok.next();
7579 have_input = 1;
7582 static struct {
7583 const char *name;
7584 int mask;
7585 } warning_table[] = {
7586 { "char", WARN_CHAR },
7587 { "range", WARN_RANGE },
7588 { "break", WARN_BREAK },
7589 { "delim", WARN_DELIM },
7590 { "el", WARN_EL },
7591 { "scale", WARN_SCALE },
7592 { "number", WARN_NUMBER },
7593 { "syntax", WARN_SYNTAX },
7594 { "tab", WARN_TAB },
7595 { "right-brace", WARN_RIGHT_BRACE },
7596 { "missing", WARN_MISSING },
7597 { "input", WARN_INPUT },
7598 { "escape", WARN_ESCAPE },
7599 { "space", WARN_SPACE },
7600 { "font", WARN_FONT },
7601 { "di", WARN_DI },
7602 { "mac", WARN_MAC },
7603 { "reg", WARN_REG },
7604 { "ig", WARN_IG },
7605 { "color", WARN_COLOR },
7606 { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
7607 { "w", WARN_TOTAL },
7608 { "default", DEFAULT_WARNING_MASK },
7611 static int lookup_warning(const char *name)
7613 for (unsigned int i = 0;
7614 i < sizeof(warning_table)/sizeof(warning_table[0]);
7615 i++)
7616 if (strcmp(name, warning_table[i].name) == 0)
7617 return warning_table[i].mask;
7618 return 0;
7621 static void enable_warning(const char *name)
7623 int mask = lookup_warning(name);
7624 if (mask)
7625 warning_mask |= mask;
7626 else
7627 error("unknown warning `%1'", name);
7630 static void disable_warning(const char *name)
7632 int mask = lookup_warning(name);
7633 if (mask)
7634 warning_mask &= ~mask;
7635 else
7636 error("unknown warning `%1'", name);
7639 static void copy_mode_error(const char *format,
7640 const errarg &arg1,
7641 const errarg &arg2,
7642 const errarg &arg3)
7644 if (ignoring) {
7645 static const char prefix[] = "(in ignored input) ";
7646 char *s = new char[sizeof(prefix) + strlen(format)];
7647 strcpy(s, prefix);
7648 strcat(s, format);
7649 warning(WARN_IG, s, arg1, arg2, arg3);
7650 a_delete s;
7652 else
7653 error(format, arg1, arg2, arg3);
7656 enum error_type { WARNING, OUTPUT_WARNING, ERROR, FATAL };
7658 static void do_error(error_type type,
7659 const char *format,
7660 const errarg &arg1,
7661 const errarg &arg2,
7662 const errarg &arg3)
7664 const char *filename;
7665 int lineno;
7666 if (inhibit_errors && type < FATAL)
7667 return;
7668 if (backtrace_flag)
7669 input_stack::backtrace();
7670 if (!get_file_line(&filename, &lineno))
7671 filename = 0;
7672 if (filename)
7673 errprint("%1:%2: ", filename, lineno);
7674 else if (program_name)
7675 fprintf(stderr, "%s: ", program_name);
7676 switch (type) {
7677 case FATAL:
7678 fputs("fatal error: ", stderr);
7679 break;
7680 case ERROR:
7681 break;
7682 case WARNING:
7683 fputs("warning: ", stderr);
7684 break;
7685 case OUTPUT_WARNING:
7686 double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
7687 fprintf(stderr, "warning [p %d, %.1f%c",
7688 topdiv->get_page_number(), fromtop, warn_scaling_indicator);
7689 if (topdiv != curdiv) {
7690 double fromtop1 = curdiv->get_vertical_position().to_units()
7691 / warn_scale;
7692 fprintf(stderr, ", div `%s', %.1f%c",
7693 curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
7695 fprintf(stderr, "]: ");
7696 break;
7698 errprint(format, arg1, arg2, arg3);
7699 fputc('\n', stderr);
7700 fflush(stderr);
7701 if (type == FATAL)
7702 cleanup_and_exit(1);
7705 int warning(warning_type t,
7706 const char *format,
7707 const errarg &arg1,
7708 const errarg &arg2,
7709 const errarg &arg3)
7711 if ((t & warning_mask) != 0) {
7712 do_error(WARNING, format, arg1, arg2, arg3);
7713 return 1;
7715 else
7716 return 0;
7719 int output_warning(warning_type t,
7720 const char *format,
7721 const errarg &arg1,
7722 const errarg &arg2,
7723 const errarg &arg3)
7725 if ((t & warning_mask) != 0) {
7726 do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
7727 return 1;
7729 else
7730 return 0;
7733 void error(const char *format,
7734 const errarg &arg1,
7735 const errarg &arg2,
7736 const errarg &arg3)
7738 do_error(ERROR, format, arg1, arg2, arg3);
7741 void fatal(const char *format,
7742 const errarg &arg1,
7743 const errarg &arg2,
7744 const errarg &arg3)
7746 do_error(FATAL, format, arg1, arg2, arg3);
7749 void fatal_with_file_and_line(const char *filename, int lineno,
7750 const char *format,
7751 const errarg &arg1,
7752 const errarg &arg2,
7753 const errarg &arg3)
7755 fprintf(stderr, "%s:%d: fatal error: ", filename, lineno);
7756 errprint(format, arg1, arg2, arg3);
7757 fputc('\n', stderr);
7758 fflush(stderr);
7759 cleanup_and_exit(1);
7762 void error_with_file_and_line(const char *filename, int lineno,
7763 const char *format,
7764 const errarg &arg1,
7765 const errarg &arg2,
7766 const errarg &arg3)
7768 fprintf(stderr, "%s:%d: error: ", filename, lineno);
7769 errprint(format, arg1, arg2, arg3);
7770 fputc('\n', stderr);
7771 fflush(stderr);
7774 dictionary charinfo_dictionary(501);
7776 charinfo *get_charinfo(symbol nm)
7778 void *p = charinfo_dictionary.lookup(nm);
7779 if (p != 0)
7780 return (charinfo *)p;
7781 charinfo *cp = new charinfo(nm);
7782 (void)charinfo_dictionary.lookup(nm, cp);
7783 return cp;
7786 int charinfo::next_index = 0;
7788 charinfo::charinfo(symbol s)
7789 : translation(0), mac(0), special_translation(TRANSLATE_NONE),
7790 hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
7791 not_found(0), transparent_translate(1), translate_input(0),
7792 mode(CHAR_NORMAL), nm(s)
7794 index = next_index++;
7797 void charinfo::set_hyphenation_code(unsigned char c)
7799 hyphenation_code = c;
7802 void charinfo::set_translation(charinfo *ci, int tt, int ti)
7804 translation = ci;
7805 if (ci && ti) {
7806 if (hyphenation_code != 0)
7807 ci->set_hyphenation_code(hyphenation_code);
7808 if (asciify_code != 0)
7809 ci->set_asciify_code(asciify_code);
7810 else if (ascii_code != 0)
7811 ci->set_asciify_code(ascii_code);
7812 ci->set_translation_input();
7814 special_translation = TRANSLATE_NONE;
7815 transparent_translate = tt;
7818 void charinfo::set_special_translation(int c, int tt)
7820 special_translation = c;
7821 translation = 0;
7822 transparent_translate = tt;
7825 void charinfo::set_ascii_code(unsigned char c)
7827 ascii_code = c;
7830 void charinfo::set_asciify_code(unsigned char c)
7832 asciify_code = c;
7835 macro *charinfo::set_macro(macro *m)
7837 macro *tem = mac;
7838 mac = m;
7839 return tem;
7842 macro *charinfo::setx_macro(macro *m, char_mode cm)
7844 macro *tem = mac;
7845 mac = m;
7846 mode = cm;
7847 return tem;
7850 void charinfo::set_number(int n)
7852 number = n;
7853 flags |= NUMBERED;
7856 int charinfo::get_number()
7858 assert(flags & NUMBERED);
7859 return number;
7862 symbol UNNAMED_SYMBOL("---");
7864 // For numbered characters not between 0 and 255, we make a symbol out
7865 // of the number and store them in this dictionary.
7867 dictionary numbered_charinfo_dictionary(11);
7869 charinfo *get_charinfo_by_number(int n)
7871 static charinfo *number_table[256];
7873 if (n >= 0 && n < 256) {
7874 charinfo *ci = number_table[n];
7875 if (!ci) {
7876 ci = new charinfo(UNNAMED_SYMBOL);
7877 ci->set_number(n);
7878 number_table[n] = ci;
7880 return ci;
7882 else {
7883 symbol ns(i_to_a(n));
7884 charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
7885 if (!ci) {
7886 ci = new charinfo(UNNAMED_SYMBOL);
7887 ci->set_number(n);
7888 (void)numbered_charinfo_dictionary.lookup(ns, ci);
7890 return ci;
7894 int font::name_to_index(const char *nm)
7896 charinfo *ci;
7897 if (nm[1] == 0)
7898 ci = charset_table[nm[0] & 0xff];
7899 else if (nm[0] == '\\' && nm[2] == 0)
7900 ci = get_charinfo(symbol(nm + 1));
7901 else
7902 ci = get_charinfo(symbol(nm));
7903 if (ci == 0)
7904 return -1;
7905 else
7906 return ci->get_index();
7909 int font::number_to_index(int n)
7911 return get_charinfo_by_number(n)->get_index();