groff: update vendor branch to v1.20.1
[dragonfly.git] / contrib / groff / src / roff / troff / input.cpp
CommitLineData
92d0a6a6 1// -*- C++ -*-
4d3e9548
JL
2/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006, 2007, 2008, 2009
92d0a6a6
JR
4 Free Software Foundation, Inc.
5 Written by James Clark (jjc@jclark.com)
6
7This file is part of groff.
8
9groff is free software; you can redistribute it and/or modify it under
10the terms of the GNU General Public License as published by the Free
4d3e9548
JL
11Software Foundation, either version 3 of the License, or
12(at your option) any later version.
92d0a6a6
JR
13
14groff is distributed in the hope that it will be useful, but WITHOUT ANY
15WARRANTY; without even the implied warranty of MERCHANTABILITY or
16FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17for more details.
18
4d3e9548
JL
19You should have received a copy of the GNU General Public License
20along with this program. If not, see <http://www.gnu.org/licenses/>. */
465b256c
JR
21
22#define DEBUGGING
92d0a6a6
JR
23
24#include "troff.h"
25#include "dictionary.h"
26#include "hvunits.h"
465b256c
JR
27#include "stringclass.h"
28#include "mtsm.h"
92d0a6a6
JR
29#include "env.h"
30#include "request.h"
31#include "node.h"
92d0a6a6
JR
32#include "token.h"
33#include "div.h"
465b256c 34#include "reg.h"
4d3e9548 35#include "font.h"
92d0a6a6 36#include "charinfo.h"
92d0a6a6 37#include "macropath.h"
92d0a6a6 38#include "input.h"
465b256c 39#include "defs.h"
92d0a6a6
JR
40#include "unicode.h"
41
42// Needed for getpid() and isatty()
43#include "posix.h"
44
45#include "nonposix.h"
46
47#ifdef NEED_DECLARATION_PUTENV
48extern "C" {
49 int putenv(const char *);
50}
51#endif /* NEED_DECLARATION_PUTENV */
52
53#define MACRO_PREFIX "tmac."
54#define MACRO_POSTFIX ".tmac"
55#define INITIAL_STARTUP_FILE "troffrc"
56#define FINAL_STARTUP_FILE "troffrc-end"
57#define DEFAULT_INPUT_STACK_LIMIT 1000
58
59#ifndef DEFAULT_WARNING_MASK
60// warnings that are enabled by default
61#define DEFAULT_WARNING_MASK \
62 (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT)
63#endif
64
65// initial size of buffer for reading names; expanded as necessary
66#define ABUF_SIZE 16
67
68extern "C" const char *program_name;
69extern "C" const char *Version_string;
70
71#ifdef COLUMN
72void init_column_requests();
73#endif /* COLUMN */
74
75static node *read_draw_node();
76static void read_color_draw_node(token &);
77static void push_token(const token &);
78void copy_file();
79#ifdef COLUMN
80void vjustify();
81#endif /* COLUMN */
82void transparent_file();
83
84token tok;
85int break_flag = 0;
86int color_flag = 1; // colors are on by default
87static int backtrace_flag = 0;
88#ifndef POPEN_MISSING
89char *pipe_command = 0;
90#endif
91charinfo *charset_table[256];
92unsigned char hpf_code_table[256];
93
94static int warning_mask = DEFAULT_WARNING_MASK;
95static int inhibit_errors = 0;
96static int ignoring = 0;
97
98static void enable_warning(const char *);
99static void disable_warning(const char *);
100
101static int escape_char = '\\';
102static symbol end_macro_name;
103static symbol blank_line_macro_name;
104static int compatible_flag = 0;
105int ascii_output_flag = 0;
106int suppress_output_flag = 0;
107int is_html = 0;
465b256c 108int begin_level = 0; // number of nested \O escapes
92d0a6a6
JR
109
110int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
4d3e9548
JL
111 // \O[345], \R, \s, or \S has been processed
112 // in token::next()
92d0a6a6
JR
113int old_have_input = 0; // value of have_input right before \n
114int tcommand_flag = 0;
4d3e9548 115int unsafe_flag = 0; // safer by default
92d0a6a6
JR
116
117int have_string_arg = 0; // whether we have \*[foo bar...]
118
119double spread_limit = -3.0 - 1.0; // negative means deactivated
120
121double warn_scale;
122char warn_scaling_indicator;
465b256c 123int debug_state = 0; // turns on debugging of the html troff state
92d0a6a6
JR
124
125search_path *mac_path = &safer_macro_path;
126
127// Defaults to the current directory.
128search_path include_search_path(0, 0, 0, 1);
129
4d3e9548 130static int get_copy(node**, int = 0, int = 0);
92d0a6a6
JR
131static void copy_mode_error(const char *,
132 const errarg & = empty_errarg,
133 const errarg & = empty_errarg,
134 const errarg & = empty_errarg);
135
136enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
4d3e9548
JL
137static symbol read_escape_name(read_mode = NO_ARGS);
138static symbol read_long_escape_name(read_mode = NO_ARGS);
92d0a6a6
JR
139static void interpolate_string(symbol);
140static void interpolate_string_with_args(symbol);
4d3e9548 141static void interpolate_macro(symbol, int = 0);
92d0a6a6
JR
142static void interpolate_number_format(symbol);
143static void interpolate_environment_variable(symbol);
144
145static symbol composite_glyph_name(symbol);
146static void interpolate_arg(symbol);
147static request_or_macro *lookup_request(symbol);
148static int get_delim_number(units *, unsigned char);
149static int get_delim_number(units *, unsigned char, units);
150static symbol do_get_long_name(int, char);
151static int get_line_arg(units *res, unsigned char si, charinfo **cp);
152static int read_size(int *);
153static symbol get_delim_name();
154static void init_registers();
155static void trapping_blank_line();
156
157class input_iterator;
158input_iterator *make_temp_iterator(const char *);
159const char *input_char_description(int);
160
465b256c
JR
161void process_input_stack();
162void chop_macro(); // declare to avoid friend name injection
163
92d0a6a6
JR
164
165void set_escape_char()
166{
167 if (has_arg()) {
168 if (tok.ch() == 0) {
169 error("bad escape character");
170 escape_char = '\\';
171 }
172 else
173 escape_char = tok.ch();
174 }
175 else
176 escape_char = '\\';
177 skip_line();
178}
179
180void escape_off()
181{
182 escape_char = 0;
183 skip_line();
184}
185
186static int saved_escape_char = '\\';
187
188void save_escape_char()
189{
190 saved_escape_char = escape_char;
191 skip_line();
192}
193
194void restore_escape_char()
195{
196 escape_char = saved_escape_char;
197 skip_line();
198}
199
4d3e9548
JL
200struct arg_list;
201
92d0a6a6
JR
202class input_iterator {
203public:
204 input_iterator();
465b256c 205 input_iterator(int is_div);
92d0a6a6
JR
206 virtual ~input_iterator() {}
207 int get(node **);
208 friend class input_stack;
465b256c
JR
209 int is_diversion;
210 statem *diversion_state;
92d0a6a6
JR
211protected:
212 const unsigned char *ptr;
213 const unsigned char *eptr;
214 input_iterator *next;
215private:
216 virtual int fill(node **);
217 virtual int peek();
218 virtual int has_args() { return 0; }
219 virtual int nargs() { return 0; }
220 virtual input_iterator *get_arg(int) { return 0; }
4d3e9548
JL
221 virtual arg_list *get_arg_list() { return 0; }
222 virtual symbol get_macro_name() { return NULL_SYMBOL; }
223 virtual int space_follows_arg(int) { return 0; }
224 virtual int get_break_flag() { return 0; }
92d0a6a6
JR
225 virtual int get_location(int, const char **, int *) { return 0; }
226 virtual void backtrace() {}
227 virtual int set_location(const char *, int) { return 0; }
228 virtual int next_file(FILE *, const char *) { return 0; }
229 virtual void shift(int) {}
230 virtual int is_boundary() {return 0; }
92d0a6a6
JR
231 virtual int is_file() { return 0; }
232 virtual int is_macro() { return 0; }
233 virtual void save_compatible_flag(int) {}
234 virtual int get_compatible_flag() { return 0; }
235};
236
237input_iterator::input_iterator()
465b256c
JR
238: is_diversion(0), ptr(0), eptr(0)
239{
240}
241
242input_iterator::input_iterator(int is_div)
243: is_diversion(is_div), ptr(0), eptr(0)
92d0a6a6
JR
244{
245}
246
247int input_iterator::fill(node **)
248{
249 return EOF;
250}
251
252int input_iterator::peek()
253{
254 return EOF;
255}
256
257inline int input_iterator::get(node **p)
258{
259 return ptr < eptr ? *ptr++ : fill(p);
260}
261
262class input_boundary : public input_iterator {
263public:
264 int is_boundary() { return 1; }
265};
266
267class input_return_boundary : public input_iterator {
268public:
269 int is_boundary() { return 2; }
270};
271
272class file_iterator : public input_iterator {
273 FILE *fp;
274 int lineno;
275 const char *filename;
276 int popened;
277 int newline_flag;
278 int seen_escape;
279 enum { BUF_SIZE = 512 };
280 unsigned char buf[BUF_SIZE];
281 void close();
282public:
283 file_iterator(FILE *, const char *, int = 0);
284 ~file_iterator();
285 int fill(node **);
286 int peek();
287 int get_location(int, const char **, int *);
288 void backtrace();
289 int set_location(const char *, int);
290 int next_file(FILE *, const char *);
291 int is_file();
292};
293
294file_iterator::file_iterator(FILE *f, const char *fn, int po)
295: fp(f), lineno(1), filename(fn), popened(po),
296 newline_flag(0), seen_escape(0)
297{
298 if ((font::use_charnames_in_special) && (fn != 0)) {
299 if (!the_output)
300 init_output();
4d3e9548 301 the_output->put_filename(fn, po);
92d0a6a6
JR
302 }
303}
304
305file_iterator::~file_iterator()
306{
307 close();
308}
309
310void file_iterator::close()
311{
312 if (fp == stdin)
313 clearerr(stdin);
314#ifndef POPEN_MISSING
315 else if (popened)
316 pclose(fp);
317#endif /* not POPEN_MISSING */
318 else
319 fclose(fp);
320}
321
322int file_iterator::is_file()
323{
324 return 1;
325}
326
327int file_iterator::next_file(FILE *f, const char *s)
328{
329 close();
330 filename = s;
331 fp = f;
332 lineno = 1;
333 newline_flag = 0;
334 seen_escape = 0;
335 popened = 0;
336 ptr = 0;
337 eptr = 0;
338 return 1;
339}
340
341int file_iterator::fill(node **)
342{
343 if (newline_flag)
344 lineno++;
345 newline_flag = 0;
346 unsigned char *p = buf;
347 ptr = p;
348 unsigned char *e = p + BUF_SIZE;
349 while (p < e) {
350 int c = getc(fp);
351 if (c == EOF)
352 break;
353 if (invalid_input_char(c))
354 warning(WARN_INPUT, "invalid input character code %1", int(c));
355 else {
356 *p++ = c;
357 if (c == '\n') {
358 seen_escape = 0;
359 newline_flag = 1;
360 break;
361 }
362 seen_escape = (c == '\\');
363 }
364 }
365 if (p > buf) {
366 eptr = p;
367 return *ptr++;
368 }
369 else {
370 eptr = p;
371 return EOF;
372 }
373}
374
375int file_iterator::peek()
376{
377 int c = getc(fp);
378 while (invalid_input_char(c)) {
379 warning(WARN_INPUT, "invalid input character code %1", int(c));
380 c = getc(fp);
381 }
382 if (c != EOF)
383 ungetc(c, fp);
384 return c;
385}
386
387int file_iterator::get_location(int /*allow_macro*/,
388 const char **filenamep, int *linenop)
389{
390 *linenop = lineno;
391 if (filename != 0 && strcmp(filename, "-") == 0)
392 *filenamep = "<standard input>";
393 else
394 *filenamep = filename;
395 return 1;
396}
397
398void file_iterator::backtrace()
399{
400 errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno,
401 popened ? "process" : "file");
402}
403
404int file_iterator::set_location(const char *f, int ln)
405{
406 if (f) {
407 filename = f;
408 if (!the_output)
409 init_output();
4d3e9548 410 the_output->put_filename(f, 0);
92d0a6a6
JR
411 }
412 lineno = ln;
413 return 1;
414}
415
416input_iterator nil_iterator;
417
418class input_stack {
419public:
420 static int get(node **);
421 static int peek();
422 static void push(input_iterator *);
423 static input_iterator *get_arg(int);
4d3e9548
JL
424 static arg_list *get_arg_list();
425 static symbol get_macro_name();
426 static int space_follows_arg(int);
427 static int get_break_flag();
92d0a6a6
JR
428 static int nargs();
429 static int get_location(int, const char **, int *);
430 static int set_location(const char *, int);
431 static void backtrace();
432 static void backtrace_all();
433 static void next_file(FILE *, const char *);
434 static void end_file();
435 static void shift(int n);
436 static void add_boundary();
437 static void add_return_boundary();
438 static int is_return_boundary();
439 static void remove_boundary();
440 static int get_level();
465b256c
JR
441 static int get_div_level();
442 static void increase_level();
443 static void decrease_level();
92d0a6a6
JR
444 static void clear();
445 static void pop_macro();
446 static void save_compatible_flag(int);
447 static int get_compatible_flag();
465b256c
JR
448 static statem *get_diversion_state();
449 static void check_end_diversion(input_iterator *t);
92d0a6a6 450 static int limit;
465b256c
JR
451 static int div_level;
452 static statem *diversion_state;
92d0a6a6
JR
453private:
454 static input_iterator *top;
455 static int level;
92d0a6a6
JR
456 static int finish_get(node **);
457 static int finish_peek();
458};
459
460input_iterator *input_stack::top = &nil_iterator;
461int input_stack::level = 0;
462int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
465b256c
JR
463int input_stack::div_level = 0;
464statem *input_stack::diversion_state = NULL;
465int suppress_push=0;
466
92d0a6a6
JR
467
468inline int input_stack::get_level()
469{
465b256c
JR
470 return level;
471}
472
473inline void input_stack::increase_level()
474{
475 level++;
476}
477
478inline void input_stack::decrease_level()
479{
480 level--;
481}
482
483inline int input_stack::get_div_level()
484{
485 return div_level;
92d0a6a6
JR
486}
487
488inline int input_stack::get(node **np)
489{
490 int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
491 if (res == '\n') {
492 old_have_input = have_input;
493 have_input = 0;
494 }
495 return res;
496}
497
498int input_stack::finish_get(node **np)
499{
500 for (;;) {
501 int c = top->fill(np);
502 if (c != EOF || top->is_boundary())
503 return c;
504 if (top == &nil_iterator)
505 break;
506 input_iterator *tem = top;
465b256c
JR
507 check_end_diversion(tem);
508#if defined(DEBUGGING)
4d3e9548
JL
509 if (debug_state)
510 if (tem->is_diversion)
511 fprintf(stderr,
512 "in diversion level = %d\n", input_stack::get_div_level());
465b256c 513#endif
92d0a6a6
JR
514 top = top->next;
515 level--;
516 delete tem;
517 if (top->ptr < top->eptr)
518 return *top->ptr++;
519 }
520 assert(level == 0);
521 return EOF;
522}
523
524inline int input_stack::peek()
525{
526 return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
527}
528
465b256c
JR
529void input_stack::check_end_diversion(input_iterator *t)
530{
531 if (t->is_diversion) {
532 div_level--;
533 diversion_state = t->diversion_state;
534 }
535}
536
92d0a6a6
JR
537int input_stack::finish_peek()
538{
539 for (;;) {
540 int c = top->peek();
541 if (c != EOF || top->is_boundary())
542 return c;
543 if (top == &nil_iterator)
544 break;
545 input_iterator *tem = top;
465b256c 546 check_end_diversion(tem);
92d0a6a6
JR
547 top = top->next;
548 level--;
549 delete tem;
550 if (top->ptr < top->eptr)
551 return *top->ptr;
552 }
553 assert(level == 0);
554 return EOF;
555}
556
557void input_stack::add_boundary()
558{
559 push(new input_boundary);
560}
561
562void input_stack::add_return_boundary()
563{
564 push(new input_return_boundary);
565}
566
567int input_stack::is_return_boundary()
568{
569 return top->is_boundary() == 2;
570}
571
572void input_stack::remove_boundary()
573{
574 assert(top->is_boundary());
575 input_iterator *temp = top->next;
465b256c
JR
576 check_end_diversion(top);
577
92d0a6a6
JR
578 delete top;
579 top = temp;
580 level--;
581}
582
583void input_stack::push(input_iterator *in)
584{
585 if (in == 0)
586 return;
587 if (++level > limit && limit > 0)
588 fatal("input stack limit exceeded (probable infinite loop)");
589 in->next = top;
590 top = in;
465b256c
JR
591 if (top->is_diversion) {
592 div_level++;
593 in->diversion_state = diversion_state;
594 diversion_state = curenv->construct_state(0);
595#if defined(DEBUGGING)
596 if (debug_state) {
597 curenv->dump_troff_state();
598 fflush(stderr);
599 }
600#endif
601 }
602#if defined(DEBUGGING)
603 if (debug_state)
604 if (top->is_diversion) {
605 fprintf(stderr,
606 "in diversion level = %d\n", input_stack::get_div_level());
607 fflush(stderr);
608 }
609#endif
610}
611
612statem *get_diversion_state()
613{
614 return input_stack::get_diversion_state();
615}
616
617statem *input_stack::get_diversion_state()
618{
619 if (diversion_state == NULL)
620 return NULL;
621 else
622 return new statem(diversion_state);
92d0a6a6
JR
623}
624
625input_iterator *input_stack::get_arg(int i)
626{
627 input_iterator *p;
628 for (p = top; p != 0; p = p->next)
629 if (p->has_args())
630 return p->get_arg(i);
631 return 0;
632}
633
4d3e9548
JL
634arg_list *input_stack::get_arg_list()
635{
636 input_iterator *p;
637 for (p = top; p != 0; p = p->next)
638 if (p->has_args())
639 return p->get_arg_list();
640 return 0;
641}
642
643symbol input_stack::get_macro_name()
644{
645 input_iterator *p;
646 for (p = top; p != 0; p = p->next)
647 if (p->has_args())
648 return p->get_macro_name();
649 return NULL_SYMBOL;
650}
651
652int input_stack::space_follows_arg(int i)
653{
654 input_iterator *p;
655 for (p = top; p != 0; p = p->next)
656 if (p->has_args())
657 return p->space_follows_arg(i);
658 return 0;
659}
660
661int input_stack::get_break_flag()
662{
663 return top->get_break_flag();
664}
665
92d0a6a6
JR
666void input_stack::shift(int n)
667{
668 for (input_iterator *p = top; p; p = p->next)
669 if (p->has_args()) {
670 p->shift(n);
671 return;
672 }
673}
674
675int input_stack::nargs()
676{
677 for (input_iterator *p =top; p != 0; p = p->next)
678 if (p->has_args())
679 return p->nargs();
680 return 0;
681}
682
683int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
684{
685 for (input_iterator *p = top; p; p = p->next)
686 if (p->get_location(allow_macro, filenamep, linenop))
687 return 1;
688 return 0;
689}
690
691void input_stack::backtrace()
692{
693 const char *f;
694 int n;
695 // only backtrace down to (not including) the topmost file
696 for (input_iterator *p = top;
697 p && !p->get_location(0, &f, &n);
698 p = p->next)
699 p->backtrace();
700}
701
702void input_stack::backtrace_all()
703{
704 for (input_iterator *p = top; p; p = p->next)
705 p->backtrace();
706}
707
708int input_stack::set_location(const char *filename, int lineno)
709{
710 for (input_iterator *p = top; p; p = p->next)
711 if (p->set_location(filename, lineno))
712 return 1;
713 return 0;
714}
715
716void input_stack::next_file(FILE *fp, const char *s)
717{
718 input_iterator **pp;
719 for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
720 if ((*pp)->next_file(fp, s))
721 return;
722 if (++level > limit && limit > 0)
723 fatal("input stack limit exceeded");
724 *pp = new file_iterator(fp, s);
725 (*pp)->next = &nil_iterator;
726}
727
728void input_stack::end_file()
729{
730 for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
731 if ((*pp)->is_file()) {
732 input_iterator *tem = *pp;
465b256c 733 check_end_diversion(tem);
92d0a6a6
JR
734 *pp = (*pp)->next;
735 delete tem;
736 level--;
737 return;
738 }
739}
740
741void input_stack::clear()
742{
743 int nboundaries = 0;
744 while (top != &nil_iterator) {
745 if (top->is_boundary())
746 nboundaries++;
747 input_iterator *tem = top;
465b256c 748 check_end_diversion(tem);
92d0a6a6
JR
749 top = top->next;
750 level--;
751 delete tem;
752 }
753 // Keep while_request happy.
754 for (; nboundaries > 0; --nboundaries)
755 add_return_boundary();
756}
757
758void input_stack::pop_macro()
759{
760 int nboundaries = 0;
761 int is_macro = 0;
762 do {
763 if (top->next == &nil_iterator)
764 break;
765 if (top->is_boundary())
766 nboundaries++;
767 is_macro = top->is_macro();
768 input_iterator *tem = top;
465b256c 769 check_end_diversion(tem);
92d0a6a6
JR
770 top = top->next;
771 level--;
772 delete tem;
773 } while (!is_macro);
774 // Keep while_request happy.
775 for (; nboundaries > 0; --nboundaries)
776 add_return_boundary();
777}
778
779inline void input_stack::save_compatible_flag(int f)
780{
781 top->save_compatible_flag(f);
782}
783
784inline int input_stack::get_compatible_flag()
785{
786 return top->get_compatible_flag();
787}
788
789void backtrace_request()
790{
791 input_stack::backtrace_all();
792 fflush(stderr);
793 skip_line();
794}
795
796void next_file()
797{
798 symbol nm = get_long_name();
799 while (!tok.newline() && !tok.eof())
800 tok.next();
801 if (nm.is_null())
802 input_stack::end_file();
803 else {
804 errno = 0;
805 FILE *fp = include_search_path.open_file_cautious(nm.contents());
806 if (!fp)
807 error("can't open `%1': %2", nm.contents(), strerror(errno));
808 else
809 input_stack::next_file(fp, nm.contents());
810 }
811 tok.next();
812}
813
814void shift()
815{
816 int n;
817 if (!has_arg() || !get_integer(&n))
818 n = 1;
819 input_stack::shift(n);
820 skip_line();
821}
822
823static char get_char_for_escape_name(int allow_space = 0)
824{
4d3e9548 825 int c = get_copy(0, 0, 1);
92d0a6a6
JR
826 switch (c) {
827 case EOF:
828 copy_mode_error("end of input in escape name");
829 return '\0';
830 default:
831 if (!invalid_input_char(c))
832 break;
833 // fall through
834 case '\n':
835 if (c == '\n')
836 input_stack::push(make_temp_iterator("\n"));
837 // fall through
838 case ' ':
839 if (c == ' ' && allow_space)
840 break;
841 // fall through
842 case '\t':
843 case '\001':
844 case '\b':
845 copy_mode_error("%1 is not allowed in an escape name",
846 input_char_description(c));
847 return '\0';
848 }
849 return c;
850}
851
852static symbol read_two_char_escape_name()
853{
854 char buf[3];
855 buf[0] = get_char_for_escape_name();
856 if (buf[0] != '\0') {
857 buf[1] = get_char_for_escape_name();
858 if (buf[1] == '\0')
859 buf[0] = 0;
860 else
861 buf[2] = 0;
862 }
863 return symbol(buf);
864}
865
866static symbol read_long_escape_name(read_mode mode)
867{
868 int start_level = input_stack::get_level();
869 char abuf[ABUF_SIZE];
870 char *buf = abuf;
871 int buf_size = ABUF_SIZE;
872 int i = 0;
873 char c;
874 int have_char = 0;
875 for (;;) {
876 c = get_char_for_escape_name(have_char && mode == WITH_ARGS);
877 if (c == 0) {
878 if (buf != abuf)
879 a_delete buf;
880 return NULL_SYMBOL;
881 }
882 have_char = 1;
883 if (mode == WITH_ARGS && c == ' ')
884 break;
885 if (i + 2 > buf_size) {
886 if (buf == abuf) {
887 buf = new char[ABUF_SIZE*2];
888 memcpy(buf, abuf, buf_size);
889 buf_size = ABUF_SIZE*2;
890 }
891 else {
892 char *old_buf = buf;
893 buf = new char[buf_size*2];
894 memcpy(buf, old_buf, buf_size);
895 buf_size *= 2;
896 a_delete old_buf;
897 }
898 }
899 if (c == ']' && input_stack::get_level() == start_level)
900 break;
901 buf[i++] = c;
902 }
903 buf[i] = 0;
904 if (c == ' ')
905 have_string_arg = 1;
906 if (buf == abuf) {
907 if (i == 0) {
908 if (mode != ALLOW_EMPTY)
4d3e9548 909 copy_mode_error("empty escape name");
92d0a6a6
JR
910 return EMPTY_SYMBOL;
911 }
912 return symbol(abuf);
913 }
914 else {
915 symbol s(buf);
916 a_delete buf;
917 return s;
918 }
919}
920
921static symbol read_escape_name(read_mode mode)
922{
923 char c = get_char_for_escape_name();
924 if (c == 0)
925 return NULL_SYMBOL;
926 if (c == '(')
927 return read_two_char_escape_name();
928 if (c == '[' && !compatible_flag)
929 return read_long_escape_name(mode);
930 char buf[2];
931 buf[0] = c;
932 buf[1] = '\0';
933 return symbol(buf);
934}
935
936static symbol read_increment_and_escape_name(int *incp)
937{
938 char c = get_char_for_escape_name();
939 switch (c) {
940 case 0:
941 *incp = 0;
942 return NULL_SYMBOL;
943 case '(':
944 *incp = 0;
945 return read_two_char_escape_name();
946 case '+':
947 *incp = 1;
948 return read_escape_name();
949 case '-':
950 *incp = -1;
951 return read_escape_name();
952 case '[':
953 if (!compatible_flag) {
954 *incp = 0;
955 return read_long_escape_name();
956 }
957 break;
958 }
959 *incp = 0;
960 char buf[2];
961 buf[0] = c;
962 buf[1] = '\0';
963 return symbol(buf);
964}
965
4d3e9548 966static int get_copy(node **nd, int defining, int handle_escape_E)
92d0a6a6
JR
967{
968 for (;;) {
969 int c = input_stack::get(nd);
465b256c
JR
970 if (c == PUSH_GROFF_MODE) {
971 input_stack::save_compatible_flag(compatible_flag);
972 compatible_flag = 0;
973 continue;
974 }
975 if (c == PUSH_COMP_MODE) {
976 input_stack::save_compatible_flag(compatible_flag);
977 compatible_flag = 1;
978 continue;
979 }
980 if (c == POP_GROFFCOMP_MODE) {
981 compatible_flag = input_stack::get_compatible_flag();
982 continue;
983 }
984 if (c == BEGIN_QUOTE) {
985 input_stack::increase_level();
986 continue;
987 }
988 if (c == END_QUOTE) {
989 input_stack::decrease_level();
990 continue;
991 }
4d3e9548
JL
992 if (c == DOUBLE_QUOTE)
993 continue;
994 if (c == ESCAPE_E && handle_escape_E)
995 c = escape_char;
92d0a6a6
JR
996 if (c == ESCAPE_NEWLINE) {
997 if (defining)
998 return c;
999 do {
1000 c = input_stack::get(nd);
1001 } while (c == ESCAPE_NEWLINE);
1002 }
1003 if (c != escape_char || escape_char <= 0)
1004 return c;
4d3e9548 1005 again:
92d0a6a6
JR
1006 c = input_stack::peek();
1007 switch(c) {
1008 case 0:
1009 return escape_char;
1010 case '"':
1011 (void)input_stack::get(0);
1012 while ((c = input_stack::get(0)) != '\n' && c != EOF)
1013 ;
1014 return c;
1015 case '#': // Like \" but newline is ignored.
1016 (void)input_stack::get(0);
1017 while ((c = input_stack::get(0)) != '\n')
1018 if (c == EOF)
1019 return EOF;
1020 break;
1021 case '$':
1022 {
1023 (void)input_stack::get(0);
1024 symbol s = read_escape_name();
1025 if (!(s.is_null() || s.is_empty()))
1026 interpolate_arg(s);
1027 break;
1028 }
1029 case '*':
1030 {
1031 (void)input_stack::get(0);
1032 symbol s = read_escape_name(WITH_ARGS);
1033 if (!(s.is_null() || s.is_empty())) {
1034 if (have_string_arg) {
1035 have_string_arg = 0;
1036 interpolate_string_with_args(s);
1037 }
1038 else
1039 interpolate_string(s);
1040 }
1041 break;
1042 }
1043 case 'a':
1044 (void)input_stack::get(0);
1045 return '\001';
1046 case 'e':
1047 (void)input_stack::get(0);
1048 return ESCAPE_e;
1049 case 'E':
1050 (void)input_stack::get(0);
4d3e9548
JL
1051 if (handle_escape_E)
1052 goto again;
92d0a6a6
JR
1053 return ESCAPE_E;
1054 case 'n':
1055 {
1056 (void)input_stack::get(0);
1057 int inc;
1058 symbol s = read_increment_and_escape_name(&inc);
1059 if (!(s.is_null() || s.is_empty()))
1060 interpolate_number_reg(s, inc);
1061 break;
1062 }
1063 case 'g':
1064 {
1065 (void)input_stack::get(0);
1066 symbol s = read_escape_name();
1067 if (!(s.is_null() || s.is_empty()))
1068 interpolate_number_format(s);
1069 break;
1070 }
1071 case 't':
1072 (void)input_stack::get(0);
1073 return '\t';
1074 case 'V':
1075 {
1076 (void)input_stack::get(0);
1077 symbol s = read_escape_name();
1078 if (!(s.is_null() || s.is_empty()))
1079 interpolate_environment_variable(s);
1080 break;
1081 }
1082 case '\n':
1083 (void)input_stack::get(0);
1084 if (defining)
1085 return ESCAPE_NEWLINE;
1086 break;
1087 case ' ':
1088 (void)input_stack::get(0);
1089 return ESCAPE_SPACE;
1090 case '~':
1091 (void)input_stack::get(0);
1092 return ESCAPE_TILDE;
1093 case ':':
1094 (void)input_stack::get(0);
1095 return ESCAPE_COLON;
1096 case '|':
1097 (void)input_stack::get(0);
1098 return ESCAPE_BAR;
1099 case '^':
1100 (void)input_stack::get(0);
1101 return ESCAPE_CIRCUMFLEX;
1102 case '{':
1103 (void)input_stack::get(0);
1104 return ESCAPE_LEFT_BRACE;
1105 case '}':
1106 (void)input_stack::get(0);
1107 return ESCAPE_RIGHT_BRACE;
1108 case '`':
1109 (void)input_stack::get(0);
1110 return ESCAPE_LEFT_QUOTE;
1111 case '\'':
1112 (void)input_stack::get(0);
1113 return ESCAPE_RIGHT_QUOTE;
1114 case '-':
1115 (void)input_stack::get(0);
1116 return ESCAPE_HYPHEN;
1117 case '_':
1118 (void)input_stack::get(0);
1119 return ESCAPE_UNDERSCORE;
1120 case 'c':
1121 (void)input_stack::get(0);
1122 return ESCAPE_c;
1123 case '!':
1124 (void)input_stack::get(0);
1125 return ESCAPE_BANG;
1126 case '?':
1127 (void)input_stack::get(0);
1128 return ESCAPE_QUESTION;
1129 case '&':
1130 (void)input_stack::get(0);
1131 return ESCAPE_AMPERSAND;
1132 case ')':
1133 (void)input_stack::get(0);
1134 return ESCAPE_RIGHT_PARENTHESIS;
1135 case '.':
1136 (void)input_stack::get(0);
1137 return c;
1138 case '%':
1139 (void)input_stack::get(0);
1140 return ESCAPE_PERCENT;
1141 default:
1142 if (c == escape_char) {
1143 (void)input_stack::get(0);
1144 return c;
1145 }
1146 else
1147 return escape_char;
1148 }
1149 }
1150}
1151
1152class non_interpreted_char_node : public node {
1153 unsigned char c;
1154public:
1155 non_interpreted_char_node(unsigned char);
1156 node *copy();
1157 int interpret(macro *);
1158 int same(node *);
1159 const char *type();
1160 int force_tprint();
465b256c 1161 int is_tag();
92d0a6a6
JR
1162};
1163
1164int non_interpreted_char_node::same(node *nd)
1165{
1166 return c == ((non_interpreted_char_node *)nd)->c;
1167}
1168
1169const char *non_interpreted_char_node::type()
1170{
1171 return "non_interpreted_char_node";
1172}
1173
1174int non_interpreted_char_node::force_tprint()
1175{
1176 return 0;
1177}
1178
465b256c
JR
1179int non_interpreted_char_node::is_tag()
1180{
1181 return 0;
1182}
1183
92d0a6a6
JR
1184non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
1185{
1186 assert(n != 0);
1187}
1188
1189node *non_interpreted_char_node::copy()
1190{
1191 return new non_interpreted_char_node(c);
1192}
1193
1194int non_interpreted_char_node::interpret(macro *mac)
1195{
1196 mac->append(c);
1197 return 1;
1198}
1199
1200static void do_width();
1201static node *do_non_interpreted();
1202static node *do_special();
1203static node *do_suppress(symbol nm);
1204static void do_register();
1205
1206dictionary color_dictionary(501);
1207
1208static color *lookup_color(symbol nm)
1209{
1210 assert(!nm.is_null());
1211 if (nm == default_symbol)
1212 return &default_color;
1213 color *c = (color *)color_dictionary.lookup(nm);
1214 if (c == 0)
1215 warning(WARN_COLOR, "color `%1' not defined", nm.contents());
1216 return c;
1217}
1218
1219void do_glyph_color(symbol nm)
1220{
1221 if (nm.is_null())
1222 return;
1223 if (nm.is_empty())
1224 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1225 else {
1226 color *tem = lookup_color(nm);
1227 if (tem)
1228 curenv->set_glyph_color(tem);
1229 else
1230 (void)color_dictionary.lookup(nm, new color(nm));
1231 }
1232}
1233
1234void do_fill_color(symbol nm)
1235{
1236 if (nm.is_null())
1237 return;
1238 if (nm.is_empty())
1239 curenv->set_fill_color(curenv->get_prev_fill_color());
1240 else {
1241 color *tem = lookup_color(nm);
1242 if (tem)
1243 curenv->set_fill_color(tem);
1244 else
1245 (void)color_dictionary.lookup(nm, new color(nm));
1246 }
1247}
1248
1249static unsigned int get_color_element(const char *scheme, const char *col)
1250{
1251 units val;
1252 if (!get_number(&val, 'f')) {
1253 warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
1254 tok.next();
1255 return 0;
1256 }
1257 if (val < 0) {
1258 warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
1259 return 0;
1260 }
1261 if (val > color::MAX_COLOR_VAL+1) {
1262 warning(WARN_RANGE, "%1 cannot be greater than 1", col);
1263 // we change 0x10000 to 0xffff
1264 return color::MAX_COLOR_VAL;
1265 }
1266 return (unsigned int)val;
1267}
1268
1269static color *read_rgb(char end = 0)
1270{
1271 symbol component = do_get_long_name(0, end);
1272 if (component.is_null()) {
1273 warning(WARN_COLOR, "missing rgb color values");
1274 return 0;
1275 }
1276 const char *s = component.contents();
1277 color *col = new color;
1278 if (*s == '#') {
1279 if (!col->read_rgb(s)) {
1280 warning(WARN_COLOR, "expecting rgb color definition not `%1'", s);
1281 delete col;
1282 return 0;
1283 }
1284 }
1285 else {
1286 if (!end)
1287 input_stack::push(make_temp_iterator(" "));
1288 input_stack::push(make_temp_iterator(s));
1289 tok.next();
1290 unsigned int r = get_color_element("rgb color", "red component");
1291 unsigned int g = get_color_element("rgb color", "green component");
1292 unsigned int b = get_color_element("rgb color", "blue component");
1293 col->set_rgb(r, g, b);
1294 }
1295 return col;
1296}
1297
1298static color *read_cmy(char end = 0)
1299{
1300 symbol component = do_get_long_name(0, end);
1301 if (component.is_null()) {
1302 warning(WARN_COLOR, "missing cmy color values");
1303 return 0;
1304 }
1305 const char *s = component.contents();
1306 color *col = new color;
1307 if (*s == '#') {
1308 if (!col->read_cmy(s)) {
1309 warning(WARN_COLOR, "expecting cmy color definition not `%1'", s);
1310 delete col;
1311 return 0;
1312 }
1313 }
1314 else {
1315 if (!end)
1316 input_stack::push(make_temp_iterator(" "));
1317 input_stack::push(make_temp_iterator(s));
1318 tok.next();
1319 unsigned int c = get_color_element("cmy color", "cyan component");
1320 unsigned int m = get_color_element("cmy color", "magenta component");
1321 unsigned int y = get_color_element("cmy color", "yellow component");
1322 col->set_cmy(c, m, y);
1323 }
1324 return col;
1325}
1326
1327static color *read_cmyk(char end = 0)
1328{
1329 symbol component = do_get_long_name(0, end);
1330 if (component.is_null()) {
1331 warning(WARN_COLOR, "missing cmyk color values");
1332 return 0;
1333 }
1334 const char *s = component.contents();
1335 color *col = new color;
1336 if (*s == '#') {
1337 if (!col->read_cmyk(s)) {
1338 warning(WARN_COLOR, "`expecting a cmyk color definition not `%1'", s);
1339 delete col;
1340 return 0;
1341 }
1342 }
1343 else {
1344 if (!end)
1345 input_stack::push(make_temp_iterator(" "));
1346 input_stack::push(make_temp_iterator(s));
1347 tok.next();
1348 unsigned int c = get_color_element("cmyk color", "cyan component");
1349 unsigned int m = get_color_element("cmyk color", "magenta component");
1350 unsigned int y = get_color_element("cmyk color", "yellow component");
1351 unsigned int k = get_color_element("cmyk color", "black component");
1352 col->set_cmyk(c, m, y, k);
1353 }
1354 return col;
1355}
1356
1357static color *read_gray(char end = 0)
1358{
1359 symbol component = do_get_long_name(0, end);
1360 if (component.is_null()) {
1361 warning(WARN_COLOR, "missing gray values");
1362 return 0;
1363 }
1364 const char *s = component.contents();
1365 color *col = new color;
1366 if (*s == '#') {
1367 if (!col->read_gray(s)) {
1368 warning(WARN_COLOR, "`expecting a gray definition not `%1'", s);
1369 delete col;
1370 return 0;
1371 }
1372 }
1373 else {
1374 if (!end)
1375 input_stack::push(make_temp_iterator("\n"));
1376 input_stack::push(make_temp_iterator(s));
1377 tok.next();
1378 unsigned int g = get_color_element("gray", "gray value");
1379 col->set_gray(g);
1380 }
1381 return col;
1382}
1383
1384static void activate_color()
1385{
1386 int n;
1387 if (has_arg() && get_integer(&n))
1388 color_flag = n != 0;
1389 else
1390 color_flag = 1;
1391 skip_line();
1392}
1393
1394static void define_color()
1395{
1396 symbol color_name = get_long_name(1);
1397 if (color_name.is_null()) {
1398 skip_line();
1399 return;
1400 }
1401 if (color_name == default_symbol) {
1402 warning(WARN_COLOR, "default color can't be redefined");
1403 skip_line();
1404 return;
1405 }
1406 symbol style = get_long_name(1);
1407 if (style.is_null()) {
1408 skip_line();
1409 return;
1410 }
1411 color *col;
1412 if (strcmp(style.contents(), "rgb") == 0)
1413 col = read_rgb();
1414 else if (strcmp(style.contents(), "cmyk") == 0)
1415 col = read_cmyk();
1416 else if (strcmp(style.contents(), "gray") == 0)
1417 col = read_gray();
1418 else if (strcmp(style.contents(), "grey") == 0)
1419 col = read_gray();
1420 else if (strcmp(style.contents(), "cmy") == 0)
1421 col = read_cmy();
1422 else {
1423 warning(WARN_COLOR,
1424 "unknown color space `%1'; use rgb, cmyk, gray or cmy",
1425 style.contents());
1426 skip_line();
1427 return;
1428 }
1429 if (col) {
1430 col->nm = color_name;
1431 (void)color_dictionary.lookup(color_name, col);
1432 }
1433 skip_line();
1434}
1435
1436static node *do_overstrike()
1437{
1438 token start;
1439 overstrike_node *on = new overstrike_node;
1440 int start_level = input_stack::get_level();
1441 start.next();
1442 for (;;) {
1443 tok.next();
1444 if (tok.newline() || tok.eof()) {
1445 warning(WARN_DELIM, "missing closing delimiter");
1446 input_stack::push(make_temp_iterator("\n"));
1447 break;
1448 }
1449 if (tok == start
1450 && (compatible_flag || input_stack::get_level() == start_level))
1451 break;
1452 charinfo *ci = tok.get_char(1);
1453 if (ci) {
1454 node *n = curenv->make_char_node(ci);
1455 if (n)
1456 on->overstrike(n);
1457 }
1458 }
1459 return on;
1460}
1461
1462static node *do_bracket()
1463{
1464 token start;
1465 bracket_node *bn = new bracket_node;
1466 start.next();
1467 int start_level = input_stack::get_level();
1468 for (;;) {
1469 tok.next();
1470 if (tok.eof()) {
1471 warning(WARN_DELIM, "missing closing delimiter");
1472 break;
1473 }
1474 if (tok.newline()) {
1475 warning(WARN_DELIM, "missing closing delimiter");
1476 input_stack::push(make_temp_iterator("\n"));
1477 break;
1478 }
1479 if (tok == start
1480 && (compatible_flag || input_stack::get_level() == start_level))
1481 break;
1482 charinfo *ci = tok.get_char(1);
1483 if (ci) {
1484 node *n = curenv->make_char_node(ci);
1485 if (n)
1486 bn->bracket(n);
1487 }
1488 }
1489 return bn;
1490}
1491
1492static int do_name_test()
1493{
1494 token start;
1495 start.next();
1496 int start_level = input_stack::get_level();
1497 int bad_char = 0;
1498 int some_char = 0;
1499 for (;;) {
1500 tok.next();
1501 if (tok.newline() || tok.eof()) {
1502 warning(WARN_DELIM, "missing closing delimiter");
1503 input_stack::push(make_temp_iterator("\n"));
1504 break;
1505 }
1506 if (tok == start
1507 && (compatible_flag || input_stack::get_level() == start_level))
1508 break;
1509 if (!tok.ch())
1510 bad_char = 1;
1511 some_char = 1;
1512 }
1513 return some_char && !bad_char;
1514}
1515
1516static int do_expr_test()
1517{
1518 token start;
1519 start.next();
1520 int start_level = input_stack::get_level();
1521 if (!start.delimiter(1))
1522 return 0;
1523 tok.next();
1524 // disable all warning and error messages temporarily
1525 int saved_warning_mask = warning_mask;
1526 int saved_inhibit_errors = inhibit_errors;
1527 warning_mask = 0;
1528 inhibit_errors = 1;
1529 int dummy;
1530 int result = get_number_rigidly(&dummy, 'u');
1531 warning_mask = saved_warning_mask;
1532 inhibit_errors = saved_inhibit_errors;
1533 if (tok == start && input_stack::get_level() == start_level)
1534 return result;
1535 // ignore everything up to the delimiter in case we aren't right there
1536 for (;;) {
1537 tok.next();
1538 if (tok.newline() || tok.eof()) {
1539 warning(WARN_DELIM, "missing closing delimiter");
1540 input_stack::push(make_temp_iterator("\n"));
1541 break;
1542 }
1543 if (tok == start && input_stack::get_level() == start_level)
1544 break;
1545 }
1546 return 0;
1547}
1548
1549#if 0
1550static node *do_zero_width()
1551{
1552 token start;
1553 start.next();
1554 int start_level = input_stack::get_level();
1555 environment env(curenv);
1556 environment *oldenv = curenv;
1557 curenv = &env;
1558 for (;;) {
1559 tok.next();
1560 if (tok.newline() || tok.eof()) {
1561 error("missing closing delimiter");
1562 break;
1563 }
1564 if (tok == start
1565 && (compatible_flag || input_stack::get_level() == start_level))
1566 break;
1567 tok.process();
1568 }
1569 curenv = oldenv;
1570 node *rev = env.extract_output_line();
1571 node *n = 0;
1572 while (rev) {
1573 node *tem = rev;
1574 rev = rev->next;
1575 tem->next = n;
1576 n = tem;
1577 }
1578 return new zero_width_node(n);
1579}
1580
1581#else
1582
1583// It's undesirable for \Z to change environments, because then
1584// \n(.w won't work as expected.
1585
1586static node *do_zero_width()
1587{
1588 node *rev = new dummy_node;
1589 token start;
1590 start.next();
1591 int start_level = input_stack::get_level();
1592 for (;;) {
1593 tok.next();
1594 if (tok.newline() || tok.eof()) {
1595 warning(WARN_DELIM, "missing closing delimiter");
1596 input_stack::push(make_temp_iterator("\n"));
1597 break;
1598 }
1599 if (tok == start
1600 && (compatible_flag || input_stack::get_level() == start_level))
1601 break;
1602 if (!tok.add_to_node_list(&rev))
1603 error("invalid token in argument to \\Z");
1604 }
1605 node *n = 0;
1606 while (rev) {
1607 node *tem = rev;
1608 rev = rev->next;
1609 tem->next = n;
1610 n = tem;
1611 }
1612 return new zero_width_node(n);
1613}
1614
1615#endif
1616
1617token_node *node::get_token_node()
1618{
1619 return 0;
1620}
1621
1622class token_node : public node {
1623public:
1624 token tk;
1625 token_node(const token &t);
1626 node *copy();
1627 token_node *get_token_node();
1628 int same(node *);
1629 const char *type();
1630 int force_tprint();
465b256c 1631 int is_tag();
92d0a6a6
JR
1632};
1633
1634token_node::token_node(const token &t) : tk(t)
1635{
1636}
1637
1638node *token_node::copy()
1639{
1640 return new token_node(tk);
1641}
1642
1643token_node *token_node::get_token_node()
1644{
1645 return this;
1646}
1647
1648int token_node::same(node *nd)
1649{
1650 return tk == ((token_node *)nd)->tk;
1651}
1652
1653const char *token_node::type()
1654{
1655 return "token_node";
1656}
1657
1658int token_node::force_tprint()
1659{
1660 return 0;
1661}
1662
465b256c
JR
1663int token_node::is_tag()
1664{
1665 return 0;
1666}
1667
92d0a6a6
JR
1668token::token() : nd(0), type(TOKEN_EMPTY)
1669{
1670}
1671
1672token::~token()
1673{
1674 delete nd;
1675}
1676
1677token::token(const token &t)
1678: nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
1679{
1680 // Use two statements to work around bug in SGI C++.
1681 node *tem = t.nd;
1682 nd = tem ? tem->copy() : 0;
1683}
1684
1685void token::operator=(const token &t)
1686{
1687 delete nd;
1688 nm = t.nm;
1689 // Use two statements to work around bug in SGI C++.
1690 node *tem = t.nd;
1691 nd = tem ? tem->copy() : 0;
1692 c = t.c;
1693 val = t.val;
1694 dim = t.dim;
1695 type = t.type;
1696}
1697
1698void token::skip()
1699{
1700 while (space())
1701 next();
1702}
1703
1704int has_arg()
1705{
1706 while (tok.space())
1707 tok.next();
1708 return !tok.newline();
1709}
1710
1711void token::make_space()
1712{
1713 type = TOKEN_SPACE;
1714}
1715
1716void token::make_newline()
1717{
1718 type = TOKEN_NEWLINE;
1719}
1720
1721void token::next()
1722{
1723 if (nd) {
1724 delete nd;
1725 nd = 0;
1726 }
1727 units x;
1728 for (;;) {
1729 node *n = 0;
1730 int cc = input_stack::get(&n);
1731 if (cc != escape_char || escape_char == 0) {
1732 handle_normal_char:
1733 switch(cc) {
465b256c 1734 case PUSH_GROFF_MODE:
92d0a6a6
JR
1735 input_stack::save_compatible_flag(compatible_flag);
1736 compatible_flag = 0;
1737 continue;
465b256c
JR
1738 case PUSH_COMP_MODE:
1739 input_stack::save_compatible_flag(compatible_flag);
1740 compatible_flag = 1;
1741 continue;
1742 case POP_GROFFCOMP_MODE:
92d0a6a6
JR
1743 compatible_flag = input_stack::get_compatible_flag();
1744 continue;
465b256c
JR
1745 case BEGIN_QUOTE:
1746 input_stack::increase_level();
1747 continue;
1748 case END_QUOTE:
1749 input_stack::decrease_level();
1750 continue;
4d3e9548
JL
1751 case DOUBLE_QUOTE:
1752 continue;
92d0a6a6
JR
1753 case EOF:
1754 type = TOKEN_EOF;
1755 return;
1756 case TRANSPARENT_FILE_REQUEST:
1757 case TITLE_REQUEST:
1758 case COPY_FILE_REQUEST:
1759#ifdef COLUMN
1760 case VJUSTIFY_REQUEST:
1761#endif /* COLUMN */
1762 type = TOKEN_REQUEST;
1763 c = cc;
1764 return;
1765 case BEGIN_TRAP:
1766 type = TOKEN_BEGIN_TRAP;
1767 return;
1768 case END_TRAP:
1769 type = TOKEN_END_TRAP;
1770 return;
1771 case LAST_PAGE_EJECTOR:
1772 seen_last_page_ejector = 1;
1773 // fall through
1774 case PAGE_EJECTOR:
1775 type = TOKEN_PAGE_EJECTOR;
1776 return;
1777 case ESCAPE_PERCENT:
1778 ESCAPE_PERCENT:
1779 type = TOKEN_HYPHEN_INDICATOR;
1780 return;
1781 case ESCAPE_SPACE:
1782 ESCAPE_SPACE:
1783 type = TOKEN_UNSTRETCHABLE_SPACE;
1784 return;
1785 case ESCAPE_TILDE:
1786 ESCAPE_TILDE:
1787 type = TOKEN_STRETCHABLE_SPACE;
1788 return;
1789 case ESCAPE_COLON:
1790 ESCAPE_COLON:
1791 type = TOKEN_ZERO_WIDTH_BREAK;
1792 return;
1793 case ESCAPE_e:
1794 ESCAPE_e:
1795 type = TOKEN_ESCAPE;
1796 return;
1797 case ESCAPE_E:
1798 goto handle_escape_char;
1799 case ESCAPE_BAR:
1800 ESCAPE_BAR:
1801 type = TOKEN_NODE;
1802 nd = new hmotion_node(curenv->get_narrow_space_width(),
1803 curenv->get_fill_color());
1804 return;
1805 case ESCAPE_CIRCUMFLEX:
1806 ESCAPE_CIRCUMFLEX:
1807 type = TOKEN_NODE;
1808 nd = new hmotion_node(curenv->get_half_narrow_space_width(),
1809 curenv->get_fill_color());
1810 return;
1811 case ESCAPE_NEWLINE:
1812 have_input = 0;
1813 break;
1814 case ESCAPE_LEFT_BRACE:
1815 ESCAPE_LEFT_BRACE:
1816 type = TOKEN_LEFT_BRACE;
1817 return;
1818 case ESCAPE_RIGHT_BRACE:
1819 ESCAPE_RIGHT_BRACE:
1820 type = TOKEN_RIGHT_BRACE;
1821 return;
1822 case ESCAPE_LEFT_QUOTE:
1823 ESCAPE_LEFT_QUOTE:
1824 type = TOKEN_SPECIAL;
1825 nm = symbol("ga");
1826 return;
1827 case ESCAPE_RIGHT_QUOTE:
1828 ESCAPE_RIGHT_QUOTE:
1829 type = TOKEN_SPECIAL;
1830 nm = symbol("aa");
1831 return;
1832 case ESCAPE_HYPHEN:
1833 ESCAPE_HYPHEN:
1834 type = TOKEN_SPECIAL;
1835 nm = symbol("-");
1836 return;
1837 case ESCAPE_UNDERSCORE:
1838 ESCAPE_UNDERSCORE:
1839 type = TOKEN_SPECIAL;
1840 nm = symbol("ul");
1841 return;
1842 case ESCAPE_c:
1843 ESCAPE_c:
1844 type = TOKEN_INTERRUPT;
1845 return;
1846 case ESCAPE_BANG:
1847 ESCAPE_BANG:
1848 type = TOKEN_TRANSPARENT;
1849 return;
1850 case ESCAPE_QUESTION:
1851 ESCAPE_QUESTION:
1852 nd = do_non_interpreted();
1853 if (nd) {
1854 type = TOKEN_NODE;
1855 return;
1856 }
1857 break;
1858 case ESCAPE_AMPERSAND:
1859 ESCAPE_AMPERSAND:
1860 type = TOKEN_DUMMY;
1861 return;
1862 case ESCAPE_RIGHT_PARENTHESIS:
1863 ESCAPE_RIGHT_PARENTHESIS:
1864 type = TOKEN_TRANSPARENT_DUMMY;
1865 return;
1866 case '\b':
1867 type = TOKEN_BACKSPACE;
1868 return;
1869 case ' ':
1870 type = TOKEN_SPACE;
1871 return;
1872 case '\t':
1873 type = TOKEN_TAB;
1874 return;
1875 case '\n':
1876 type = TOKEN_NEWLINE;
1877 return;
1878 case '\001':
1879 type = TOKEN_LEADER;
1880 return;
1881 case 0:
1882 {
1883 assert(n != 0);
1884 token_node *tn = n->get_token_node();
1885 if (tn) {
1886 *this = tn->tk;
1887 delete tn;
1888 }
1889 else {
1890 nd = n;
1891 type = TOKEN_NODE;
1892 }
1893 }
1894 return;
1895 default:
1896 type = TOKEN_CHAR;
1897 c = cc;
1898 return;
1899 }
1900 }
1901 else {
1902 handle_escape_char:
1903 cc = input_stack::get(&n);
1904 switch(cc) {
1905 case '(':
1906 nm = read_two_char_escape_name();
1907 type = TOKEN_SPECIAL;
1908 return;
1909 case EOF:
1910 type = TOKEN_EOF;
1911 error("end of input after escape character");
1912 return;
1913 case '`':
1914 goto ESCAPE_LEFT_QUOTE;
1915 case '\'':
1916 goto ESCAPE_RIGHT_QUOTE;
1917 case '-':
1918 goto ESCAPE_HYPHEN;
1919 case '_':
1920 goto ESCAPE_UNDERSCORE;
1921 case '%':
1922 goto ESCAPE_PERCENT;
1923 case ' ':
1924 goto ESCAPE_SPACE;
1925 case '0':
1926 nd = new hmotion_node(curenv->get_digit_width(),
1927 curenv->get_fill_color());
1928 type = TOKEN_NODE;
1929 return;
1930 case '|':
1931 goto ESCAPE_BAR;
1932 case '^':
1933 goto ESCAPE_CIRCUMFLEX;
1934 case '/':
1935 type = TOKEN_ITALIC_CORRECTION;
1936 return;
1937 case ',':
1938 type = TOKEN_NODE;
1939 nd = new left_italic_corrected_node;
1940 return;
1941 case '&':
1942 goto ESCAPE_AMPERSAND;
1943 case ')':
1944 goto ESCAPE_RIGHT_PARENTHESIS;
1945 case '!':
1946 goto ESCAPE_BANG;
1947 case '?':
1948 goto ESCAPE_QUESTION;
1949 case '~':
1950 goto ESCAPE_TILDE;
1951 case ':':
1952 goto ESCAPE_COLON;
1953 case '"':
1954 while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
1955 ;
1956 if (cc == '\n')
1957 type = TOKEN_NEWLINE;
1958 else
1959 type = TOKEN_EOF;
1960 return;
1961 case '#': // Like \" but newline is ignored.
1962 while ((cc = input_stack::get(0)) != '\n')
1963 if (cc == EOF) {
1964 type = TOKEN_EOF;
1965 return;
1966 }
1967 break;
1968 case '$':
1969 {
1970 symbol s = read_escape_name();
1971 if (!(s.is_null() || s.is_empty()))
1972 interpolate_arg(s);
1973 break;
1974 }
1975 case '*':
1976 {
1977 symbol s = read_escape_name(WITH_ARGS);
1978 if (!(s.is_null() || s.is_empty())) {
1979 if (have_string_arg) {
1980 have_string_arg = 0;
1981 interpolate_string_with_args(s);
1982 }
1983 else
1984 interpolate_string(s);
1985 }
1986 break;
1987 }
1988 case 'a':
1989 nd = new non_interpreted_char_node('\001');
1990 type = TOKEN_NODE;
1991 return;
1992 case 'A':
1993 c = '0' + do_name_test();
1994 type = TOKEN_CHAR;
1995 return;
1996 case 'b':
1997 nd = do_bracket();
1998 type = TOKEN_NODE;
1999 return;
2000 case 'B':
2001 c = '0' + do_expr_test();
2002 type = TOKEN_CHAR;
2003 return;
2004 case 'c':
2005 goto ESCAPE_c;
2006 case 'C':
2007 nm = get_delim_name();
2008 if (nm.is_null())
2009 break;
2010 type = TOKEN_SPECIAL;
2011 return;
2012 case 'd':
2013 type = TOKEN_NODE;
2014 nd = new vmotion_node(curenv->get_size() / 2,
2015 curenv->get_fill_color());
2016 return;
2017 case 'D':
2018 nd = read_draw_node();
2019 if (!nd)
2020 break;
2021 type = TOKEN_NODE;
2022 return;
2023 case 'e':
2024 goto ESCAPE_e;
2025 case 'E':
2026 goto handle_escape_char;
2027 case 'f':
2028 {
2029 symbol s = read_escape_name(ALLOW_EMPTY);
2030 if (s.is_null())
2031 break;
2032 const char *p;
2033 for (p = s.contents(); *p != '\0'; p++)
2034 if (!csdigit(*p))
2035 break;
2036 if (*p || s.is_empty())
2037 curenv->set_font(s);
2038 else
2039 curenv->set_font(atoi(s.contents()));
2040 if (!compatible_flag)
2041 have_input = 1;
2042 break;
2043 }
2044 case 'F':
2045 {
2046 symbol s = read_escape_name(ALLOW_EMPTY);
2047 if (s.is_null())
2048 break;
2049 curenv->set_family(s);
2050 have_input = 1;
2051 break;
2052 }
2053 case 'g':
2054 {
2055 symbol s = read_escape_name();
2056 if (!(s.is_null() || s.is_empty()))
2057 interpolate_number_format(s);
2058 break;
2059 }
2060 case 'h':
2061 if (!get_delim_number(&x, 'm'))
2062 break;
2063 type = TOKEN_NODE;
2064 nd = new hmotion_node(x, curenv->get_fill_color());
2065 return;
2066 case 'H':
2067 // don't take height increments relative to previous height if
2068 // in compatibility mode
4d3e9548 2069 if (!compatible_flag && curenv->get_char_height()) {
92d0a6a6
JR
2070 if (get_delim_number(&x, 'z', curenv->get_char_height()))
2071 curenv->set_char_height(x);
2072 }
4d3e9548 2073 else {
92d0a6a6
JR
2074 if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
2075 curenv->set_char_height(x);
2076 }
2077 if (!compatible_flag)
2078 have_input = 1;
2079 break;
2080 case 'k':
2081 nm = read_escape_name();
2082 if (nm.is_null() || nm.is_empty())
2083 break;
2084 type = TOKEN_MARK_INPUT;
2085 return;
2086 case 'l':
2087 case 'L':
2088 {
2089 charinfo *s = 0;
2090 if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
2091 break;
2092 if (s == 0)
2093 s = get_charinfo(cc == 'l' ? "ru" : "br");
2094 type = TOKEN_NODE;
2095 node *char_node = curenv->make_char_node(s);
2096 if (cc == 'l')
2097 nd = new hline_node(x, char_node);
2098 else
2099 nd = new vline_node(x, char_node);
2100 return;
2101 }
2102 case 'm':
2103 do_glyph_color(read_escape_name(ALLOW_EMPTY));
2104 if (!compatible_flag)
2105 have_input = 1;
2106 break;
2107 case 'M':
2108 do_fill_color(read_escape_name(ALLOW_EMPTY));
2109 if (!compatible_flag)
2110 have_input = 1;
2111 break;
2112 case 'n':
2113 {
2114 int inc;
2115 symbol s = read_increment_and_escape_name(&inc);
2116 if (!(s.is_null() || s.is_empty()))
2117 interpolate_number_reg(s, inc);
2118 break;
2119 }
2120 case 'N':
2121 if (!get_delim_number(&val, 0))
2122 break;
4d3e9548
JL
2123 if (val < 0) {
2124 warning(WARN_CHAR, "invalid numbered character %1", val);
2125 break;
2126 }
92d0a6a6
JR
2127 type = TOKEN_NUMBERED_CHAR;
2128 return;
2129 case 'o':
2130 nd = do_overstrike();
2131 type = TOKEN_NODE;
2132 return;
2133 case 'O':
2134 nd = do_suppress(read_escape_name());
2135 if (!nd)
2136 break;
2137 type = TOKEN_NODE;
2138 return;
2139 case 'p':
2140 type = TOKEN_SPREAD;
2141 return;
2142 case 'r':
2143 type = TOKEN_NODE;
2144 nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
2145 return;
2146 case 'R':
2147 do_register();
2148 if (!compatible_flag)
2149 have_input = 1;
2150 break;
2151 case 's':
2152 if (read_size(&x))
2153 curenv->set_size(x);
2154 if (!compatible_flag)
2155 have_input = 1;
2156 break;
2157 case 'S':
2158 if (get_delim_number(&x, 0))
2159 curenv->set_char_slant(x);
2160 if (!compatible_flag)
2161 have_input = 1;
2162 break;
2163 case 't':
2164 type = TOKEN_NODE;
2165 nd = new non_interpreted_char_node('\t');
2166 return;
2167 case 'u':
2168 type = TOKEN_NODE;
2169 nd = new vmotion_node(-curenv->get_size() / 2,
2170 curenv->get_fill_color());
2171 return;
2172 case 'v':
2173 if (!get_delim_number(&x, 'v'))
2174 break;
2175 type = TOKEN_NODE;
2176 nd = new vmotion_node(x, curenv->get_fill_color());
2177 return;
2178 case 'V':
2179 {
2180 symbol s = read_escape_name();
2181 if (!(s.is_null() || s.is_empty()))
2182 interpolate_environment_variable(s);
2183 break;
2184 }
2185 case 'w':
2186 do_width();
2187 break;
2188 case 'x':
2189 if (!get_delim_number(&x, 'v'))
2190 break;
2191 type = TOKEN_NODE;
2192 nd = new extra_size_node(x);
2193 return;
2194 case 'X':
2195 nd = do_special();
2196 if (!nd)
2197 break;
2198 type = TOKEN_NODE;
2199 return;
2200 case 'Y':
2201 {
2202 symbol s = read_escape_name();
2203 if (s.is_null() || s.is_empty())
2204 break;
2205 request_or_macro *p = lookup_request(s);
2206 macro *m = p->to_macro();
2207 if (!m) {
2208 error("can't transparently throughput a request");
2209 break;
2210 }
2211 nd = new special_node(*m);
2212 type = TOKEN_NODE;
2213 return;
2214 }
2215 case 'z':
2216 {
2217 next();
2218 if (type == TOKEN_NODE)
2219 nd = new zero_width_node(nd);
2220 else {
2221 charinfo *ci = get_char(1);
2222 if (ci == 0)
2223 break;
2224 node *gn = curenv->make_char_node(ci);
2225 if (gn == 0)
2226 break;
2227 nd = new zero_width_node(gn);
2228 type = TOKEN_NODE;
2229 }
2230 return;
2231 }
2232 case 'Z':
2233 nd = do_zero_width();
2234 if (nd == 0)
2235 break;
2236 type = TOKEN_NODE;
2237 return;
2238 case '{':
2239 goto ESCAPE_LEFT_BRACE;
2240 case '}':
2241 goto ESCAPE_RIGHT_BRACE;
2242 case '\n':
2243 break;
2244 case '[':
2245 if (!compatible_flag) {
2246 symbol s = read_long_escape_name(WITH_ARGS);
2247 if (s.is_null() || s.is_empty())
2248 break;
2249 if (have_string_arg) {
2250 have_string_arg = 0;
2251 nm = composite_glyph_name(s);
2252 }
2253 else {
2254 const char *gn = check_unicode_name(s.contents());
2255 if (gn) {
2256 const char *gn_decomposed = decompose_unicode(gn);
2257 if (gn_decomposed)
2258 gn = &gn_decomposed[1];
2259 const char *groff_gn = unicode_to_glyph_name(gn);
2260 if (groff_gn)
2261 nm = symbol(groff_gn);
2262 else {
2263 char *buf = new char[strlen(gn) + 1 + 1];
2264 strcpy(buf, "u");
2265 strcat(buf, gn);
2266 nm = symbol(buf);
2267 a_delete buf;
2268 }
2269 }
2270 else
2271 nm = symbol(s.contents());
2272 }
2273 type = TOKEN_SPECIAL;
2274 return;
2275 }
2276 goto handle_normal_char;
2277 default:
2278 if (cc != escape_char && cc != '.')
2279 warning(WARN_ESCAPE, "escape character ignored before %1",
2280 input_char_description(cc));
2281 goto handle_normal_char;
2282 }
2283 }
2284 }
2285}
2286
2287int token::operator==(const token &t)
2288{
2289 if (type != t.type)
2290 return 0;
2291 switch(type) {
2292 case TOKEN_CHAR:
2293 return c == t.c;
2294 case TOKEN_SPECIAL:
2295 return nm == t.nm;
2296 case TOKEN_NUMBERED_CHAR:
2297 return val == t.val;
2298 default:
2299 return 1;
2300 }
2301}
2302
2303int token::operator!=(const token &t)
2304{
2305 return !(*this == t);
2306}
2307
2308// is token a suitable delimiter (like ')?
2309
2310int token::delimiter(int err)
2311{
2312 switch(type) {
2313 case TOKEN_CHAR:
2314 switch(c) {
2315 case '0':
2316 case '1':
2317 case '2':
2318 case '3':
2319 case '4':
2320 case '5':
2321 case '6':
2322 case '7':
2323 case '8':
2324 case '9':
2325 case '+':
2326 case '-':
2327 case '/':
2328 case '*':
2329 case '%':
2330 case '<':
2331 case '>':
2332 case '=':
2333 case '&':
2334 case ':':
2335 case '(':
2336 case ')':
2337 case '.':
2338 if (err)
2339 error("cannot use character `%1' as a starting delimiter", char(c));
2340 return 0;
2341 default:
2342 return 1;
2343 }
2344 case TOKEN_NODE:
2345 case TOKEN_SPACE:
2346 case TOKEN_STRETCHABLE_SPACE:
2347 case TOKEN_UNSTRETCHABLE_SPACE:
2348 case TOKEN_TAB:
2349 case TOKEN_NEWLINE:
2350 if (err)
2351 error("cannot use %1 as a starting delimiter", description());
2352 return 0;
2353 default:
2354 return 1;
2355 }
2356}
2357
2358const char *token::description()
2359{
2360 static char buf[4];
2361 switch (type) {
2362 case TOKEN_BACKSPACE:
2363 return "a backspace character";
2364 case TOKEN_CHAR:
2365 buf[0] = '`';
2366 buf[1] = c;
2367 buf[2] = '\'';
2368 buf[3] = '\0';
2369 return buf;
2370 case TOKEN_DUMMY:
2371 return "`\\&'";
2372 case TOKEN_ESCAPE:
2373 return "`\\e'";
2374 case TOKEN_HYPHEN_INDICATOR:
2375 return "`\\%'";
2376 case TOKEN_INTERRUPT:
2377 return "`\\c'";
2378 case TOKEN_ITALIC_CORRECTION:
2379 return "`\\/'";
2380 case TOKEN_LEADER:
2381 return "a leader character";
2382 case TOKEN_LEFT_BRACE:
2383 return "`\\{'";
2384 case TOKEN_MARK_INPUT:
2385 return "`\\k'";
2386 case TOKEN_NEWLINE:
2387 return "newline";
2388 case TOKEN_NODE:
2389 return "a node";
2390 case TOKEN_NUMBERED_CHAR:
2391 return "`\\N'";
2392 case TOKEN_RIGHT_BRACE:
2393 return "`\\}'";
2394 case TOKEN_SPACE:
2395 return "a space";
2396 case TOKEN_SPECIAL:
2397 return "a special character";
2398 case TOKEN_SPREAD:
2399 return "`\\p'";
2400 case TOKEN_STRETCHABLE_SPACE:
2401 return "`\\~'";
2402 case TOKEN_UNSTRETCHABLE_SPACE:
2403 return "`\\ '";
2404 case TOKEN_TAB:
2405 return "a tab character";
2406 case TOKEN_TRANSPARENT:
2407 return "`\\!'";
2408 case TOKEN_TRANSPARENT_DUMMY:
2409 return "`\\)'";
2410 case TOKEN_ZERO_WIDTH_BREAK:
2411 return "`\\:'";
2412 case TOKEN_EOF:
2413 return "end of input";
2414 default:
2415 break;
2416 }
2417 return "a magic token";
2418}
2419
2420void skip_line()
2421{
2422 while (!tok.newline())
2423 if (tok.eof())
2424 return;
2425 else
2426 tok.next();
2427 tok.next();
2428}
2429
2430void compatible()
2431{
2432 int n;
2433 if (has_arg() && get_integer(&n))
2434 compatible_flag = n != 0;
2435 else
2436 compatible_flag = 1;
2437 skip_line();
2438}
2439
2440static void empty_name_warning(int required)
2441{
2442 if (tok.newline() || tok.eof()) {
2443 if (required)
2444 warning(WARN_MISSING, "missing name");
2445 }
2446 else if (tok.right_brace() || tok.tab()) {
2447 const char *start = tok.description();
2448 do {
2449 tok.next();
2450 } while (tok.space() || tok.right_brace() || tok.tab());
2451 if (!tok.newline() && !tok.eof())
2452 error("%1 is not allowed before an argument", start);
2453 else if (required)
2454 warning(WARN_MISSING, "missing name");
2455 }
2456 else if (required)
2457 error("name expected (got %1)", tok.description());
2458 else
2459 error("name expected (got %1): treated as missing", tok.description());
2460}
2461
2462static void non_empty_name_warning()
2463{
2464 if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab()
2465 && !tok.right_brace()
2466 // We don't want to give a warning for .el\{
2467 && !tok.left_brace())
2468 error("%1 is not allowed in a name", tok.description());
2469}
2470
2471symbol get_name(int required)
2472{
2473 if (compatible_flag) {
2474 char buf[3];
2475 tok.skip();
2476 if ((buf[0] = tok.ch()) != 0) {
2477 tok.next();
2478 if ((buf[1] = tok.ch()) != 0) {
2479 buf[2] = 0;
2480 tok.make_space();
2481 }
2482 else
2483 non_empty_name_warning();
2484 return symbol(buf);
2485 }
2486 else {
2487 empty_name_warning(required);
2488 return NULL_SYMBOL;
2489 }
2490 }
2491 else
2492 return get_long_name(required);
2493}
2494
2495symbol get_long_name(int required)
2496{
2497 return do_get_long_name(required, 0);
2498}
2499
2500static symbol do_get_long_name(int required, char end)
2501{
2502 while (tok.space())
2503 tok.next();
2504 char abuf[ABUF_SIZE];
2505 char *buf = abuf;
2506 int buf_size = ABUF_SIZE;
2507 int i = 0;
2508 for (;;) {
2509 // If end != 0 we normally have to append a null byte
2510 if (i + 2 > buf_size) {
2511 if (buf == abuf) {
2512 buf = new char[ABUF_SIZE*2];
2513 memcpy(buf, abuf, buf_size);
2514 buf_size = ABUF_SIZE*2;
2515 }
2516 else {
2517 char *old_buf = buf;
2518 buf = new char[buf_size*2];
2519 memcpy(buf, old_buf, buf_size);
2520 buf_size *= 2;
2521 a_delete old_buf;
2522 }
2523 }
2524 if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
2525 break;
2526 i++;
2527 tok.next();
2528 }
2529 if (i == 0) {
2530 empty_name_warning(required);
2531 return NULL_SYMBOL;
2532 }
2533 if (end && buf[i] == end)
2534 buf[i+1] = '\0';
2535 else
2536 non_empty_name_warning();
2537 if (buf == abuf)
2538 return symbol(buf);
2539 else {
2540 symbol s(buf);
2541 a_delete buf;
2542 return s;
2543 }
2544}
2545
2546void exit_troff()
2547{
2548 exit_started = 1;
2549 topdiv->set_last_page();
2550 if (!end_macro_name.is_null()) {
2551 spring_trap(end_macro_name);
2552 tok.next();
2553 process_input_stack();
2554 }
2555 curenv->final_break();
2556 tok.next();
2557 process_input_stack();
2558 end_diversions();
2559 if (topdiv->get_page_length() > 0) {
2560 done_end_macro = 1;
2561 topdiv->set_ejecting();
2562 static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
2563 input_stack::push(make_temp_iterator((char *)buf));
2564 topdiv->space(topdiv->get_page_length(), 1);
2565 tok.next();
2566 process_input_stack();
2567 seen_last_page_ejector = 1; // should be set already
2568 topdiv->set_ejecting();
2569 push_page_ejector();
2570 topdiv->space(topdiv->get_page_length(), 1);
2571 tok.next();
2572 process_input_stack();
2573 }
2574 // This will only happen if a trap-invoked macro starts a diversion,
2575 // or if vertical position traps have been disabled.
2576 cleanup_and_exit(0);
2577}
2578
2579// This implements .ex. The input stack must be cleared before calling
2580// exit_troff().
2581
2582void exit_request()
2583{
2584 input_stack::clear();
2585 if (exit_started)
2586 tok.next();
2587 else
2588 exit_troff();
2589}
2590
2591void return_macro_request()
2592{
2593 if (has_arg() && tok.ch())
2594 input_stack::pop_macro();
2595 input_stack::pop_macro();
2596 tok.next();
2597}
2598
2599void end_macro()
2600{
2601 end_macro_name = get_name();
2602 skip_line();
2603}
2604
2605void blank_line_macro()
2606{
2607 blank_line_macro_name = get_name();
2608 skip_line();
2609}
2610
2611static void trapping_blank_line()
2612{
2613 if (!blank_line_macro_name.is_null())
2614 spring_trap(blank_line_macro_name);
2615 else
2616 blank_line();
2617}
2618
2619void do_request()
2620{
2621 int old_compatible_flag = compatible_flag;
2622 compatible_flag = 0;
2623 symbol nm = get_name();
2624 if (nm.is_null())
2625 skip_line();
2626 else
4d3e9548 2627 interpolate_macro(nm, 1);
92d0a6a6 2628 compatible_flag = old_compatible_flag;
4d3e9548
JL
2629 request_or_macro *p = lookup_request(nm);
2630 macro *m = p->to_macro();
2631 if (m)
2632 tok.next();
92d0a6a6
JR
2633}
2634
2635inline int possibly_handle_first_page_transition()
2636{
2637 if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
2638 handle_first_page_transition();
2639 return 1;
2640 }
2641 else
2642 return 0;
2643}
2644
2645static int transparent_translate(int cc)
2646{
2647 if (!invalid_input_char(cc)) {
2648 charinfo *ci = charset_table[cc];
2649 switch (ci->get_special_translation(1)) {
2650 case charinfo::TRANSLATE_SPACE:
2651 return ' ';
2652 case charinfo::TRANSLATE_STRETCHABLE_SPACE:
2653 return ESCAPE_TILDE;
2654 case charinfo::TRANSLATE_DUMMY:
2655 return ESCAPE_AMPERSAND;
2656 case charinfo::TRANSLATE_HYPHEN_INDICATOR:
2657 return ESCAPE_PERCENT;
2658 }
2659 // This is really ugly.
2660 ci = ci->get_translation(1);
2661 if (ci) {
2662 int c = ci->get_ascii_code();
2663 if (c != '\0')
2664 return c;
2665 error("can't translate %1 to special character `%2'"
2666 " in transparent throughput",
2667 input_char_description(cc),
2668 ci->nm.contents());
2669 }
2670 }
2671 return cc;
2672}
2673
2674class int_stack {
2675 struct int_stack_element {
2676 int n;
2677 int_stack_element *next;
2678 } *top;
2679public:
2680 int_stack();
2681 ~int_stack();
2682 void push(int);
2683 int is_empty();
2684 int pop();
2685};
2686
2687int_stack::int_stack()
2688{
2689 top = 0;
2690}
2691
2692int_stack::~int_stack()
2693{
2694 while (top != 0) {
2695 int_stack_element *temp = top;
2696 top = top->next;
2697 delete temp;
2698 }
2699}
2700
2701int int_stack::is_empty()
2702{
2703 return top == 0;
2704}
2705
2706void int_stack::push(int n)
2707{
2708 int_stack_element *p = new int_stack_element;
2709 p->next = top;
2710 p->n = n;
2711 top = p;
2712}
2713
2714int int_stack::pop()
2715{
2716 assert(top != 0);
2717 int_stack_element *p = top;
2718 top = top->next;
2719 int n = p->n;
2720 delete p;
2721 return n;
2722}
2723
2724int node::reread(int *)
2725{
2726 return 0;
2727}
2728
465b256c
JR
2729int global_diverted_space = 0;
2730
92d0a6a6
JR
2731int diverted_space_node::reread(int *bolp)
2732{
465b256c 2733 global_diverted_space = 1;
92d0a6a6
JR
2734 if (curenv->get_fill())
2735 trapping_blank_line();
2736 else
2737 curdiv->space(n);
465b256c 2738 global_diverted_space = 0;
92d0a6a6
JR
2739 *bolp = 1;
2740 return 1;
2741}
2742
2743int diverted_copy_file_node::reread(int *bolp)
2744{
2745 curdiv->copy_file(filename.contents());
2746 *bolp = 1;
2747 return 1;
2748}
2749
2750int word_space_node::reread(int *)
2751{
2752 if (unformat) {
2753 for (width_list *w = orig_width; w; w = w->next)
2754 curenv->space(w->width, w->sentence_width);
2755 unformat = 0;
2756 return 1;
2757 }
2758 return 0;
2759}
2760
2761int unbreakable_space_node::reread(int *)
2762{
2763 return 0;
2764}
2765
2766int hmotion_node::reread(int *)
2767{
2768 if (unformat && was_tab) {
2769 curenv->handle_tab(0);
2770 unformat = 0;
2771 return 1;
2772 }
2773 return 0;
2774}
2775
2776void process_input_stack()
2777{
2778 int_stack trap_bol_stack;
2779 int bol = 1;
2780 for (;;) {
2781 int suppress_next = 0;
2782 switch (tok.type) {
2783 case token::TOKEN_CHAR:
2784 {
2785 unsigned char ch = tok.c;
2786 if (bol && !have_input
2787 && (ch == curenv->control_char
2788 || ch == curenv->no_break_control_char)) {
2789 break_flag = ch == curenv->control_char;
2790 // skip tabs as well as spaces here
2791 do {
2792 tok.next();
2793 } while (tok.white_space());
2794 symbol nm = get_name();
465b256c
JR
2795#if defined(DEBUGGING)
2796 if (debug_state) {
2797 if (! nm.is_null()) {
2798 if (strcmp(nm.contents(), "test") == 0) {
2799 fprintf(stderr, "found it!\n");
2800 fflush(stderr);
2801 }
2802 fprintf(stderr, "interpreting [%s]", nm.contents());
2803 if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
2804 fprintf(stderr, " currently in diversion: %s",
2805 curdiv->get_diversion_name());
2806 fprintf(stderr, "\n");
2807 fflush(stderr);
2808 }
2809 }
2810#endif
92d0a6a6
JR
2811 if (nm.is_null())
2812 skip_line();
465b256c 2813 else {
92d0a6a6 2814 interpolate_macro(nm);
465b256c
JR
2815#if defined(DEBUGGING)
2816 if (debug_state) {
2817 fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
2818 curenv->dump_troff_state();
2819 }
2820#endif
2821 }
92d0a6a6
JR
2822 suppress_next = 1;
2823 }
2824 else {
2825 if (possibly_handle_first_page_transition())
2826 ;
2827 else {
2828 for (;;) {
465b256c
JR
2829#if defined(DEBUGGING)
2830 if (debug_state) {
2831 fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
2832 }
2833#endif
92d0a6a6
JR
2834 curenv->add_char(charset_table[ch]);
2835 tok.next();
2836 if (tok.type != token::TOKEN_CHAR)
2837 break;
2838 ch = tok.c;
2839 }
2840 suppress_next = 1;
2841 bol = 0;
2842 }
2843 }
2844 break;
2845 }
2846 case token::TOKEN_TRANSPARENT:
2847 {
2848 if (bol) {
2849 if (possibly_handle_first_page_transition())
2850 ;
2851 else {
2852 int cc;
2853 do {
2854 node *n;
2855 cc = get_copy(&n);
2856 if (cc != EOF)
2857 if (cc != '\0')
2858 curdiv->transparent_output(transparent_translate(cc));
2859 else
2860 curdiv->transparent_output(n);
2861 } while (cc != '\n' && cc != EOF);
2862 if (cc == EOF)
2863 curdiv->transparent_output('\n');
2864 }
2865 }
2866 break;
2867 }
2868 case token::TOKEN_NEWLINE:
2869 {
2870 if (bol && !old_have_input
2871 && !curenv->get_prev_line_interrupted())
2872 trapping_blank_line();
2873 else {
2874 curenv->newline();
2875 bol = 1;
2876 }
2877 break;
2878 }
2879 case token::TOKEN_REQUEST:
2880 {
2881 int request_code = tok.c;
2882 tok.next();
2883 switch (request_code) {
2884 case TITLE_REQUEST:
2885 title();
2886 break;
2887 case COPY_FILE_REQUEST:
2888 copy_file();
2889 break;
2890 case TRANSPARENT_FILE_REQUEST:
2891 transparent_file();
2892 break;
2893#ifdef COLUMN
2894 case VJUSTIFY_REQUEST:
2895 vjustify();
2896 break;
2897#endif /* COLUMN */
2898 default:
2899 assert(0);
2900 break;
2901 }
2902 suppress_next = 1;
2903 break;
2904 }
2905 case token::TOKEN_SPACE:
2906 {
2907 if (possibly_handle_first_page_transition())
2908 ;
2909 else if (bol && !curenv->get_prev_line_interrupted()) {
2910 int nspaces = 0;
2911 // save space_width now so that it isn't changed by \f or \s
2912 // which we wouldn't notice here
2913 hunits space_width = curenv->get_space_width();
2914 do {
2915 nspaces += tok.nspaces();
2916 tok.next();
2917 } while (tok.space());
2918 if (tok.newline())
2919 trapping_blank_line();
2920 else {
2921 push_token(tok);
2922 curenv->do_break();
2923 curenv->add_node(new hmotion_node(space_width * nspaces,
2924 curenv->get_fill_color()));
2925 bol = 0;
2926 }
2927 }
2928 else {
2929 curenv->space();
2930 bol = 0;
2931 }
2932 break;
2933 }
2934 case token::TOKEN_EOF:
2935 return;
2936 case token::TOKEN_NODE:
2937 {
2938 if (possibly_handle_first_page_transition())
2939 ;
2940 else if (tok.nd->reread(&bol)) {
2941 delete tok.nd;
2942 tok.nd = 0;
2943 }
2944 else {
2945 curenv->add_node(tok.nd);
2946 tok.nd = 0;
2947 bol = 0;
2948 curenv->possibly_break_line(1);
2949 }
2950 break;
2951 }
2952 case token::TOKEN_PAGE_EJECTOR:
2953 {
2954 continue_page_eject();
2955 // I think we just want to preserve bol.
2956 // bol = 1;
2957 break;
2958 }
2959 case token::TOKEN_BEGIN_TRAP:
2960 {
2961 trap_bol_stack.push(bol);
2962 bol = 1;
2963 have_input = 0;
2964 break;
2965 }
2966 case token::TOKEN_END_TRAP:
2967 {
2968 if (trap_bol_stack.is_empty())
2969 error("spurious end trap token detected!");
2970 else
2971 bol = trap_bol_stack.pop();
2972 have_input = 0;
2973
2974 /* I'm not totally happy about this. But I can't think of any other
2975 way to do it. Doing an output_pending_lines() whenever a
2976 TOKEN_END_TRAP is detected doesn't work: for example,
2977
2978 .wh -1i x
2979 .de x
2980 'bp
2981 ..
2982 .wh -.5i y
2983 .de y
2984 .tl ''-%-''
2985 ..
2986 .br
2987 .ll .5i
2988 .sp |\n(.pu-1i-.5v
2989 a\%very\%very\%long\%word
2990
2991 will print all but the first lines from the word immediately
2992 after the footer, rather than on the next page. */
2993
2994 if (trap_bol_stack.is_empty())
2995 curenv->output_pending_lines();
2996 break;
2997 }
2998 default:
2999 {
3000 bol = 0;
3001 tok.process();
3002 break;
3003 }
3004 }
3005 if (!suppress_next)
3006 tok.next();
3007 trap_sprung_flag = 0;
3008 }
3009}
3010
3011#ifdef WIDOW_CONTROL
3012
3013void flush_pending_lines()
3014{
3015 while (!tok.newline() && !tok.eof())
3016 tok.next();
3017 curenv->output_pending_lines();
3018 tok.next();
3019}
3020
3021#endif /* WIDOW_CONTROL */
3022
3023request_or_macro::request_or_macro()
3024{
3025}
3026
3027macro *request_or_macro::to_macro()
3028{
3029 return 0;
3030}
3031
3032request::request(REQUEST_FUNCP pp) : p(pp)
3033{
3034}
3035
4d3e9548 3036void request::invoke(symbol, int)
92d0a6a6
JR
3037{
3038 (*p)();
3039}
3040
3041struct char_block {
3042 enum { SIZE = 128 };
3043 unsigned char s[SIZE];
3044 char_block *next;
3045 char_block();
3046};
3047
3048char_block::char_block()
3049: next(0)
3050{
3051}
3052
3053class char_list {
3054public:
3055 char_list();
3056 ~char_list();
3057 void append(unsigned char);
3058 void set(unsigned char, int);
3059 unsigned char get(int);
3060 int length();
3061private:
3062 unsigned char *ptr;
3063 int len;
3064 char_block *head;
3065 char_block *tail;
3066 friend class macro_header;
3067 friend class string_iterator;
3068};
3069
3070char_list::char_list()
3071: ptr(0), len(0), head(0), tail(0)
3072{
3073}
3074
3075char_list::~char_list()
3076{
3077 while (head != 0) {
3078 char_block *tem = head;
3079 head = head->next;
3080 delete tem;
3081 }
3082}
3083
3084int char_list::length()
3085{
3086 return len;
3087}
3088
3089void char_list::append(unsigned char c)
3090{
3091 if (tail == 0) {
3092 head = tail = new char_block;
3093 ptr = tail->s;
3094 }
3095 else {
3096 if (ptr >= tail->s + char_block::SIZE) {
3097 tail->next = new char_block;
3098 tail = tail->next;
3099 ptr = tail->s;
3100 }
3101 }
3102 *ptr++ = c;
3103 len++;
3104}
3105
3106void char_list::set(unsigned char c, int offset)
3107{
3108 assert(len > offset);
3109 // optimization for access at the end
3110 int boundary = len - len % char_block::SIZE;
3111 if (offset >= boundary) {
3112 *(tail->s + offset - boundary) = c;
3113 return;
3114 }
3115 char_block *tem = head;
3116 int l = 0;
3117 for (;;) {
3118 l += char_block::SIZE;
3119 if (l > offset) {
3120 *(tem->s + offset % char_block::SIZE) = c;
3121 return;
3122 }
3123 tem = tem->next;
3124 }
3125}
3126
3127unsigned char char_list::get(int offset)
3128{
3129 assert(len > offset);
3130 // optimization for access at the end
3131 int boundary = len - len % char_block::SIZE;
3132 if (offset >= boundary)
3133 return *(tail->s + offset - boundary);
3134 char_block *tem = head;
3135 int l = 0;
3136 for (;;) {
3137 l += char_block::SIZE;
3138 if (l > offset)
3139 return *(tem->s + offset % char_block::SIZE);
3140 tem = tem->next;
3141 }
3142}
3143
3144class node_list {
3145 node *head;
3146 node *tail;
3147public:
3148 node_list();
3149 ~node_list();
3150 void append(node *);
3151 int length();
3152 node *extract();
3153
3154 friend class macro_header;
3155 friend class string_iterator;
3156};
3157
3158void node_list::append(node *n)
3159{
3160 if (head == 0) {
3161 n->next = 0;
3162 head = tail = n;
3163 }
3164 else {
3165 n->next = 0;
3166 tail = tail->next = n;
3167 }
3168}
3169
3170int node_list::length()
3171{
3172 int total = 0;
3173 for (node *n = head; n != 0; n = n->next)
3174 ++total;
3175 return total;
3176}
3177
3178node_list::node_list()
3179{
3180 head = tail = 0;
3181}
3182
3183node *node_list::extract()
3184{
3185 node *temp = head;
3186 head = tail = 0;
3187 return temp;
3188}
3189
3190node_list::~node_list()
3191{
3192 delete_node_list(head);
3193}
3194
3195class macro_header {
3196public:
3197 int count;
3198 char_list cl;
3199 node_list nl;
3200 macro_header() { count = 1; }
3201 macro_header *copy(int);
3202};
3203
3204macro::~macro()
3205{
3206 if (p != 0 && --(p->count) <= 0)
3207 delete p;
3208}
3209
3210macro::macro()
4d3e9548 3211: is_a_diversion(0), is_a_string(1)
92d0a6a6
JR
3212{
3213 if (!input_stack::get_location(1, &filename, &lineno)) {
3214 filename = 0;
3215 lineno = 0;
3216 }
3217 len = 0;
3218 empty_macro = 1;
3219 p = 0;
3220}
3221
3222macro::macro(const macro &m)
465b256c 3223: filename(m.filename), lineno(m.lineno), len(m.len),
4d3e9548
JL
3224 empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion),
3225 is_a_string(m.is_a_string), p(m.p)
92d0a6a6
JR
3226{
3227 if (p != 0)
3228 p->count++;
3229}
3230
465b256c 3231macro::macro(int is_div)
4d3e9548 3232: is_a_diversion(is_div)
465b256c
JR
3233{
3234 if (!input_stack::get_location(1, &filename, &lineno)) {
3235 filename = 0;
3236 lineno = 0;
3237 }
3238 len = 0;
3239 empty_macro = 1;
4d3e9548 3240 is_a_string = 1;
465b256c
JR
3241 p = 0;
3242}
3243
3244int macro::is_diversion()
3245{
3246 return is_a_diversion;
3247}
3248
4d3e9548
JL
3249int macro::is_string()
3250{
3251 return is_a_string;
3252}
3253
3254void macro::clear_string_flag()
3255{
3256 is_a_string = 0;
3257}
3258
92d0a6a6
JR
3259macro &macro::operator=(const macro &m)
3260{
3261 // don't assign object
3262 if (m.p != 0)
3263 m.p->count++;
3264 if (p != 0 && --(p->count) <= 0)
3265 delete p;
3266 p = m.p;
3267 filename = m.filename;
3268 lineno = m.lineno;
3269 len = m.len;
3270 empty_macro = m.empty_macro;
465b256c 3271 is_a_diversion = m.is_a_diversion;
4d3e9548 3272 is_a_string = m.is_a_string;
92d0a6a6
JR
3273 return *this;
3274}
3275
3276void macro::append(unsigned char c)
3277{
3278 assert(c != 0);
3279 if (p == 0)
3280 p = new macro_header;
3281 if (p->cl.length() != len) {
3282 macro_header *tem = p->copy(len);
3283 if (--(p->count) <= 0)
3284 delete p;
3285 p = tem;
3286 }
3287 p->cl.append(c);
3288 ++len;
465b256c 3289 if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
92d0a6a6
JR
3290 empty_macro = 0;
3291}
3292
3293void macro::set(unsigned char c, int offset)
3294{
3295 assert(p != 0);
3296 assert(c != 0);
3297 p->cl.set(c, offset);
3298}
3299
3300unsigned char macro::get(int offset)
3301{
3302 assert(p != 0);
3303 return p->cl.get(offset);
3304}
3305
3306int macro::length()
3307{
3308 return len;
3309}
3310
3311void macro::append_str(const char *s)
3312{
3313 int i = 0;
3314
3315 if (s) {
3316 while (s[i] != (char)0) {
3317 append(s[i]);
3318 i++;
3319 }
3320 }
3321}
3322
3323void macro::append(node *n)
3324{
3325 assert(n != 0);
3326 if (p == 0)
3327 p = new macro_header;
3328 if (p->cl.length() != len) {
3329 macro_header *tem = p->copy(len);
3330 if (--(p->count) <= 0)
3331 delete p;
3332 p = tem;
3333 }
3334 p->cl.append(0);
3335 p->nl.append(n);
3336 ++len;
3337 empty_macro = 0;
3338}
3339
3340void macro::append_unsigned(unsigned int i)
3341{
3342 unsigned int j = i / 10;
3343 if (j != 0)
3344 append_unsigned(j);
3345 append(((unsigned char)(((int)'0') + i % 10)));
3346}
3347
3348void macro::append_int(int i)
3349{
3350 if (i < 0) {
3351 append('-');
3352 i = -i;
3353 }
3354 append_unsigned((unsigned int)i);
3355}
3356
3357void macro::print_size()
3358{
3359 errprint("%1", len);
3360}
3361
3362// make a copy of the first n bytes
3363
3364macro_header *macro_header::copy(int n)
3365{
3366 macro_header *p = new macro_header;
3367 char_block *bp = cl.head;
3368 unsigned char *ptr = bp->s;
3369 node *nd = nl.head;
3370 while (--n >= 0) {
3371 if (ptr >= bp->s + char_block::SIZE) {
3372 bp = bp->next;
3373 ptr = bp->s;
3374 }
3375 unsigned char c = *ptr++;
3376 p->cl.append(c);
3377 if (c == 0) {
3378 p->nl.append(nd->copy());
3379 nd = nd->next;
3380 }
3381 }
3382 return p;
3383}
3384
3385void print_macros()
3386{
3387 object_dictionary_iterator iter(request_dictionary);
3388 request_or_macro *rm;
3389 symbol s;
3390 while (iter.get(&s, (object **)&rm)) {
3391 assert(!s.is_null());
3392 macro *m = rm->to_macro();
3393 if (m) {
3394 errprint("%1\t", s.contents());
3395 m->print_size();
3396 errprint("\n");
3397 }
3398 }
3399 fflush(stderr);
3400 skip_line();
3401}
3402
3403class string_iterator : public input_iterator {
3404 macro mac;
3405 const char *how_invoked;
3406 int newline_flag;
3407 int lineno;
3408 char_block *bp;
3409 int count; // of characters remaining
3410 node *nd;
3411 int saved_compatible_flag;
4d3e9548 3412 int with_break; // inherited from the caller
92d0a6a6
JR
3413protected:
3414 symbol nm;
3415 string_iterator();
3416public:
4d3e9548 3417 string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
92d0a6a6
JR
3418 int fill(node **);
3419 int peek();
3420 int get_location(int, const char **, int *);
3421 void backtrace();
4d3e9548 3422 int get_break_flag() { return with_break; }
92d0a6a6
JR
3423 void save_compatible_flag(int f) { saved_compatible_flag = f; }
3424 int get_compatible_flag() { return saved_compatible_flag; }
465b256c 3425 int is_diversion();
92d0a6a6
JR
3426};
3427
3428string_iterator::string_iterator(const macro &m, const char *p, symbol s)
465b256c
JR
3429: input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
3430 lineno(1), nm(s)
92d0a6a6
JR
3431{
3432 count = mac.len;
3433 if (count != 0) {
3434 bp = mac.p->cl.head;
3435 nd = mac.p->nl.head;
3436 ptr = eptr = bp->s;
3437 }
3438 else {
3439 bp = 0;
3440 nd = 0;
3441 ptr = eptr = 0;
3442 }
4d3e9548 3443 with_break = input_stack::get_break_flag();
92d0a6a6
JR
3444}
3445
3446string_iterator::string_iterator()
3447{
3448 bp = 0;
3449 nd = 0;
3450 ptr = eptr = 0;
3451 newline_flag = 0;
3452 how_invoked = 0;
3453 lineno = 1;
3454 count = 0;
4d3e9548 3455 with_break = input_stack::get_break_flag();
92d0a6a6
JR
3456}
3457
465b256c
JR
3458int string_iterator::is_diversion()
3459{
3460 return mac.is_diversion();
3461}
3462
92d0a6a6
JR
3463int string_iterator::fill(node **np)
3464{
3465 if (newline_flag)
3466 lineno++;
3467 newline_flag = 0;
3468 if (count <= 0)
3469 return EOF;
3470 const unsigned char *p = eptr;
3471 if (p >= bp->s + char_block::SIZE) {
3472 bp = bp->next;
3473 p = bp->s;
3474 }
3475 if (*p == '\0') {
465b256c 3476 if (np) {
92d0a6a6 3477 *np = nd->copy();
465b256c
JR
3478 if (is_diversion())
3479 (*np)->div_nest_level = input_stack::get_div_level();
3480 else
3481 (*np)->div_nest_level = 0;
3482 }
92d0a6a6
JR
3483 nd = nd->next;
3484 eptr = ptr = p + 1;
3485 count--;
3486 return 0;
3487 }
3488 const unsigned char *e = bp->s + char_block::SIZE;
3489 if (e - p > count)
3490 e = p + count;
3491 ptr = p;
3492 while (p < e) {
3493 unsigned char c = *p;
3494 if (c == '\n' || c == ESCAPE_NEWLINE) {
3495 newline_flag = 1;
3496 p++;
3497 break;
3498 }
3499 if (c == '\0')
3500 break;
3501 p++;
3502 }
3503 eptr = p;
3504 count -= p - ptr;
3505 return *ptr++;
3506}
3507
3508int string_iterator::peek()
3509{
3510 if (count <= 0)
3511 return EOF;
3512 const unsigned char *p = eptr;
3513 if (p >= bp->s + char_block::SIZE) {
3514 p = bp->next->s;
3515 }
3516 return *p;
3517}
3518
3519int string_iterator::get_location(int allow_macro,
3520 const char **filep, int *linep)
3521{
3522 if (!allow_macro)
3523 return 0;
3524 if (mac.filename == 0)
3525 return 0;
3526 *filep = mac.filename;
3527 *linep = mac.lineno + lineno - 1;
3528 return 1;
3529}
3530
3531void string_iterator::backtrace()
3532{
3533 if (mac.filename) {
3534 errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1);
3535 if (how_invoked) {
3536 if (!nm.is_null())
3537 errprint(": %1 `%2'\n", how_invoked, nm.contents());
3538 else
3539 errprint(": %1\n", how_invoked);
3540 }
3541 else
3542 errprint("\n");
3543 }
3544}
3545
3546class temp_iterator : public input_iterator {
3547 unsigned char *base;
3548 temp_iterator(const char *, int len);
3549public:
3550 ~temp_iterator();
3551 friend input_iterator *make_temp_iterator(const char *);
3552};
3553
3554#ifdef __GNUG__
3555inline
3556#endif
3557temp_iterator::temp_iterator(const char *s, int len)
3558{
3559 base = new unsigned char[len];
3560 memcpy(base, s, len);
3561 ptr = base;
3562 eptr = base + len;
3563}
3564
3565temp_iterator::~temp_iterator()
3566{
3567 a_delete base;
3568}
3569
3570class small_temp_iterator : public input_iterator {
3571private:
3572 small_temp_iterator(const char *, int);
3573 ~small_temp_iterator();
3574 enum { BLOCK = 16 };
3575 static small_temp_iterator *free_list;
3576 void *operator new(size_t);
3577 void operator delete(void *);
3578 enum { SIZE = 12 };
3579 unsigned char buf[SIZE];
3580 friend input_iterator *make_temp_iterator(const char *);
3581};
3582
3583small_temp_iterator *small_temp_iterator::free_list = 0;
3584
3585void *small_temp_iterator::operator new(size_t n)
3586{
3587 assert(n == sizeof(small_temp_iterator));
3588 if (!free_list) {
3589 free_list =
3590 (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK];
3591 for (int i = 0; i < BLOCK - 1; i++)
3592 free_list[i].next = free_list + i + 1;
3593 free_list[BLOCK-1].next = 0;
3594 }
3595 small_temp_iterator *p = free_list;
3596 free_list = (small_temp_iterator *)(free_list->next);
3597 p->next = 0;
3598 return p;
3599}
3600
3601#ifdef __GNUG__
3602inline
3603#endif
3604void small_temp_iterator::operator delete(void *p)
3605{
3606 if (p) {
3607 ((small_temp_iterator *)p)->next = free_list;
3608 free_list = (small_temp_iterator *)p;
3609 }
3610}
3611
3612small_temp_iterator::~small_temp_iterator()
3613{
3614}
3615
3616#ifdef __GNUG__
3617inline
3618#endif
3619small_temp_iterator::small_temp_iterator(const char *s, int len)
3620{
3621 for (int i = 0; i < len; i++)
3622 buf[i] = s[i];
3623 ptr = buf;
3624 eptr = buf + len;
3625}
3626
3627input_iterator *make_temp_iterator(const char *s)
3628{
3629 if (s == 0)
3630 return new small_temp_iterator(s, 0);
3631 else {
3632 int n = strlen(s);
3633 if (n <= small_temp_iterator::SIZE)
3634 return new small_temp_iterator(s, n);
3635 else
3636 return new temp_iterator(s, n);
3637 }
3638}
3639
3640// this is used when macros with arguments are interpolated
3641
3642struct arg_list {
3643 macro mac;
4d3e9548 3644 int space_follows;
92d0a6a6 3645 arg_list *next;
4d3e9548
JL
3646 arg_list(const macro &, int);
3647 arg_list(const arg_list *);
92d0a6a6
JR
3648 ~arg_list();
3649};
3650
4d3e9548 3651arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
92d0a6a6
JR
3652{
3653}
3654
4d3e9548
JL
3655arg_list::arg_list(const arg_list *al)
3656: next(0)
3657{
3658 mac = al->mac;
3659 space_follows = al->space_follows;
3660 arg_list **a = &next;
3661 arg_list *p = al->next;
3662 while (p) {
3663 *a = new arg_list(p->mac, p->space_follows);
3664 p = p->next;
3665 a = &(*a)->next;
3666 }
3667}
3668
92d0a6a6
JR
3669arg_list::~arg_list()
3670{
3671}
3672
3673class macro_iterator : public string_iterator {
3674 arg_list *args;
3675 int argc;
4d3e9548 3676 int with_break; // whether called as .foo or 'foo
92d0a6a6 3677public:
4d3e9548 3678 macro_iterator(symbol, macro &, const char * = "macro", int = 0);
92d0a6a6
JR
3679 macro_iterator();
3680 ~macro_iterator();
3681 int has_args() { return 1; }
4d3e9548
JL
3682 input_iterator *get_arg(int);
3683 arg_list *get_arg_list();
3684 symbol get_macro_name();
3685 int space_follows_arg(int);
3686 int get_break_flag() { return with_break; }
92d0a6a6 3687 int nargs() { return argc; }
4d3e9548
JL
3688 void add_arg(const macro &, int);
3689 void shift(int);
92d0a6a6 3690 int is_macro() { return 1; }
465b256c 3691 int is_diversion();
92d0a6a6
JR
3692};
3693
3694input_iterator *macro_iterator::get_arg(int i)
3695{
3696 if (i == 0)
3697 return make_temp_iterator(nm.contents());
3698 if (i > 0 && i <= argc) {
3699 arg_list *p = args;
3700 for (int j = 1; j < i; j++) {
3701 assert(p != 0);
3702 p = p->next;
3703 }
3704 return new string_iterator(p->mac);
3705 }
3706 else
3707 return 0;
3708}
3709
4d3e9548
JL
3710arg_list *macro_iterator::get_arg_list()
3711{
3712 return args;
3713}
3714
3715symbol macro_iterator::get_macro_name()
3716{
3717 return nm;
3718}
3719
3720int macro_iterator::space_follows_arg(int i)
3721{
3722 if (i > 0 && i <= argc) {
3723 arg_list *p = args;
3724 for (int j = 1; j < i; j++) {
3725 assert(p != 0);
3726 p = p->next;
3727 }
3728 return p->space_follows;
3729 }
3730 else
3731 return 0;
3732}
3733
3734void macro_iterator::add_arg(const macro &m, int s)
92d0a6a6
JR
3735{
3736 arg_list **p;
3737 for (p = &args; *p; p = &((*p)->next))
3738 ;
4d3e9548 3739 *p = new arg_list(m, s);
92d0a6a6
JR
3740 ++argc;
3741}
3742
3743void macro_iterator::shift(int n)
3744{
3745 while (n > 0 && argc > 0) {
3746 arg_list *tem = args;
3747 args = args->next;
3748 delete tem;
3749 --argc;
3750 --n;
3751 }
3752}
3753
3754// This gets used by eg .if '\?xxx\?''.
3755
3756int operator==(const macro &m1, const macro &m2)
3757{
3758 if (m1.len != m2.len)
3759 return 0;
3760 string_iterator iter1(m1);
3761 string_iterator iter2(m2);
3762 int n = m1.len;
3763 while (--n >= 0) {
3764 node *nd1 = 0;
3765 int c1 = iter1.get(&nd1);
3766 assert(c1 != EOF);
3767 node *nd2 = 0;
3768 int c2 = iter2.get(&nd2);
3769 assert(c2 != EOF);
3770 if (c1 != c2) {
3771 if (c1 == 0)
3772 delete nd1;
3773 else if (c2 == 0)
3774 delete nd2;
3775 return 0;
3776 }
3777 if (c1 == 0) {
3778 assert(nd1 != 0);
3779 assert(nd2 != 0);
3780 int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
3781 delete nd1;
3782 delete nd2;
3783 if (!are_same)
3784 return 0;
3785 }
3786 }
3787 return 1;
3788}
3789
4d3e9548 3790static void interpolate_macro(symbol nm, int no_next)
92d0a6a6
JR
3791{
3792 request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
3793 if (p == 0) {
3794 int warned = 0;
3795 const char *s = nm.contents();
3796 if (strlen(s) > 2) {
3797 request_or_macro *r;
3798 char buf[3];
3799 buf[0] = s[0];
3800 buf[1] = s[1];
3801 buf[2] = '\0';
3802 r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
3803 if (r) {
3804 macro *m = r->to_macro();
3805 if (!m || !m->empty())
3806 warned = warning(WARN_SPACE,
3807 "macro `%1' not defined "
4d3e9548 3808 "(possibly missing space after `%2')",
92d0a6a6
JR
3809 nm.contents(), buf);
3810 }
3811 }
3812 if (!warned) {
3813 warning(WARN_MAC, "macro `%1' not defined", nm.contents());
3814 p = new macro;
3815 request_dictionary.define(nm, p);
3816 }
3817 }
3818 if (p)
4d3e9548 3819 p->invoke(nm, no_next);
92d0a6a6
JR
3820 else {
3821 skip_line();
3822 return;
3823 }
3824}
3825
3826static void decode_args(macro_iterator *mi)
3827{
3828 if (!tok.newline() && !tok.eof()) {
3829 node *n;
3830 int c = get_copy(&n);
3831 for (;;) {
3832 while (c == ' ')
3833 c = get_copy(&n);
3834 if (c == '\n' || c == EOF)
3835 break;
3836 macro arg;
3837 int quote_input_level = 0;
3838 int done_tab_warning = 0;
4d3e9548
JL
3839 arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
3840 // we store discarded double quotes for \$^
465b256c 3841 if (c == '"') {
4d3e9548 3842 arg.append(DOUBLE_QUOTE);
92d0a6a6
JR
3843 quote_input_level = input_stack::get_level();
3844 c = get_copy(&n);
3845 }
3846 while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
465b256c 3847 if (quote_input_level > 0 && c == '"'
92d0a6a6
JR
3848 && (compatible_flag
3849 || input_stack::get_level() == quote_input_level)) {
4d3e9548 3850 arg.append(DOUBLE_QUOTE);
92d0a6a6
JR
3851 c = get_copy(&n);
3852 if (c == '"') {
3853 arg.append(c);
3854 c = get_copy(&n);
3855 }
3856 else
3857 break;
3858 }
3859 else {
3860 if (c == 0)
3861 arg.append(n);
3862 else {
3863 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3864 warning(WARN_TAB, "tab character in unquoted macro argument");
3865 done_tab_warning = 1;
3866 }
3867 arg.append(c);
3868 }
3869 c = get_copy(&n);
3870 }
3871 }
465b256c 3872 arg.append(POP_GROFFCOMP_MODE);
4d3e9548 3873 mi->add_arg(arg, (c == ' '));
92d0a6a6
JR
3874 }
3875 }
3876}
3877
3878static void decode_string_args(macro_iterator *mi)
3879{
3880 node *n;
3881 int c = get_copy(&n);
3882 for (;;) {
3883 while (c == ' ')
3884 c = get_copy(&n);
3885 if (c == '\n' || c == EOF) {
3886 error("missing `]'");
3887 break;
3888 }
3889 if (c == ']')
3890 break;
3891 macro arg;
3892 int quote_input_level = 0;
3893 int done_tab_warning = 0;
465b256c 3894 if (c == '"') {
92d0a6a6
JR
3895 quote_input_level = input_stack::get_level();
3896 c = get_copy(&n);
3897 }
3898 while (c != EOF && c != '\n'
3899 && !(c == ']' && quote_input_level == 0)
3900 && !(c == ' ' && quote_input_level == 0)) {
465b256c 3901 if (quote_input_level > 0 && c == '"'
92d0a6a6
JR
3902 && input_stack::get_level() == quote_input_level) {
3903 c = get_copy(&n);
3904 if (c == '"') {
3905 arg.append(c);
3906 c = get_copy(&n);
3907 }
3908 else
3909 break;
3910 }
3911 else {
3912 if (c == 0)
3913 arg.append(n);
3914 else {
3915 if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
3916 warning(WARN_TAB, "tab character in unquoted string argument");
3917 done_tab_warning = 1;
3918 }
3919 arg.append(c);
3920 }
3921 c = get_copy(&n);
3922 }
3923 }
4d3e9548 3924 mi->add_arg(arg, (c == ' '));
92d0a6a6
JR
3925 }
3926}
3927
4d3e9548 3928void macro::invoke(symbol nm, int no_next)
92d0a6a6
JR
3929{
3930 macro_iterator *mi = new macro_iterator(nm, *this);
3931 decode_args(mi);
3932 input_stack::push(mi);
4d3e9548
JL
3933 // we must delay tok.next() in case the function has been called by
3934 // do_request to assure proper handling of compatible_flag
3935 if (!no_next)
3936 tok.next();
92d0a6a6
JR
3937}
3938
3939macro *macro::to_macro()
3940{
3941 return this;
3942}
3943
3944int macro::empty()
3945{
3946 return empty_macro == 1;
3947}
3948
4d3e9548
JL
3949macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called,
3950 int init_args)
3951: string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
92d0a6a6 3952{
4d3e9548
JL
3953 if (init_args) {
3954 arg_list *al = input_stack::get_arg_list();
3955 if (al) {
3956 args = new arg_list(al);
3957 argc = input_stack::nargs();
3958 }
3959 }
92d0a6a6
JR
3960}
3961
4d3e9548 3962macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
92d0a6a6
JR
3963{
3964}
3965
3966macro_iterator::~macro_iterator()
3967{
3968 while (args != 0) {
3969 arg_list *tem = args;
3970 args = args->next;
3971 delete tem;
3972 }
3973}
3974
3975dictionary composite_dictionary(17);
3976
3977void composite_request()
3978{
3979 symbol from = get_name(1);
3980 if (!from.is_null()) {
3981 const char *from_gn = glyph_name_to_unicode(from.contents());
3982 if (!from_gn) {
3983 from_gn = check_unicode_name(from.contents());
3984 if (!from_gn) {
3985 error("invalid composite glyph name `%1'", from.contents());
3986 skip_line();
3987 return;
3988 }
3989 }
3990 const char *from_decomposed = decompose_unicode(from_gn);
3991 if (from_decomposed)
3992 from_gn = &from_decomposed[1];
3993 symbol to = get_name(1);
3994 if (to.is_null())
3995 composite_dictionary.remove(symbol(from_gn));
3996 else {
3997 const char *to_gn = glyph_name_to_unicode(to.contents());
3998 if (!to_gn) {
3999 to_gn = check_unicode_name(to.contents());
4000 if (!to_gn) {
4001 error("invalid composite glyph name `%1'", to.contents());
4002 skip_line();
4003 return;
4004 }
4005 }
4006 const char *to_decomposed = decompose_unicode(to_gn);
4007 if (to_decomposed)
4008 to_gn = &to_decomposed[1];
4009 if (strcmp(from_gn, to_gn) == 0)
4010 composite_dictionary.remove(symbol(from_gn));
4011 else
4012 (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
4013 }
4014 }
4015 skip_line();
4016}
4017
4018static symbol composite_glyph_name(symbol nm)
4019{
4020 macro_iterator *mi = new macro_iterator();
4021 decode_string_args(mi);
4022 input_stack::push(mi);
4023 const char *gn = glyph_name_to_unicode(nm.contents());
4024 if (!gn) {
4025 gn = check_unicode_name(nm.contents());
4026 if (!gn) {
4027 error("invalid base glyph `%1' in composite glyph name", nm.contents());
4028 return EMPTY_SYMBOL;
4029 }
4030 }
4031 const char *gn_decomposed = decompose_unicode(gn);
4032 string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
4033 string gl;
4034 int n = input_stack::nargs();
4035 for (int i = 1; i <= n; i++) {
4036 glyph_name += '_';
4037 input_iterator *p = input_stack::get_arg(i);
4038 gl.clear();
4039 int c;
4040 while ((c = p->get(0)) != EOF)
4d3e9548
JL
4041 if (c != DOUBLE_QUOTE)
4042 gl += c;
92d0a6a6
JR
4043 gl += '\0';
4044 const char *u = glyph_name_to_unicode(gl.contents());
4045 if (!u) {
4046 u = check_unicode_name(gl.contents());
4047 if (!u) {
4048 error("invalid component `%1' in composite glyph name",
4049 gl.contents());
4050 return EMPTY_SYMBOL;
4051 }
4052 }
4053 const char *decomposed = decompose_unicode(u);
4054 if (decomposed)
4055 u = &decomposed[1];
4056 void *mapped_composite = composite_dictionary.lookup(symbol(u));
4057 if (mapped_composite)
4058 u = (const char *)mapped_composite;
4059 glyph_name += u;
4060 }
4061 glyph_name += '\0';
4062 const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
4063 if (groff_gn)
4064 return symbol(groff_gn);
4065 gl.clear();
4066 gl += 'u';
4067 gl += glyph_name;
4068 return symbol(gl.contents());
4069}
4070
4071int trap_sprung_flag = 0;
4072int postpone_traps_flag = 0;
4073symbol postponed_trap;
4074
4075void spring_trap(symbol nm)
4076{
4077 assert(!nm.is_null());
4078 trap_sprung_flag = 1;
4079 if (postpone_traps_flag) {
4080 postponed_trap = nm;
4081 return;
4082 }
4083 static char buf[2] = { BEGIN_TRAP, 0 };
4084 static char buf2[2] = { END_TRAP, '\0' };
4085 input_stack::push(make_temp_iterator(buf2));
4086 request_or_macro *p = lookup_request(nm);
4087 macro *m = p->to_macro();
4088 if (m)
4089 input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro"));
4090 else
4091 error("you can't invoke a request with a trap");
4092 input_stack::push(make_temp_iterator(buf));
4093}
4094
4095void postpone_traps()
4096{
4097 postpone_traps_flag = 1;
4098}
4099
4100int unpostpone_traps()
4101{
4102 postpone_traps_flag = 0;
4103 if (!postponed_trap.is_null()) {
4104 spring_trap(postponed_trap);
4105 postponed_trap = NULL_SYMBOL;
4106 return 1;
4107 }
4108 else
4109 return 0;
4110}
4111
4112void read_request()
4113{
4114 macro_iterator *mi = new macro_iterator;
4115 int reading_from_terminal = isatty(fileno(stdin));
4116 int had_prompt = 0;
4117 if (!tok.newline() && !tok.eof()) {
4118 int c = get_copy(0);
4119 while (c == ' ')
4120 c = get_copy(0);
4121 while (c != EOF && c != '\n' && c != ' ') {
4122 if (!invalid_input_char(c)) {
4123 if (reading_from_terminal)
4124 fputc(c, stderr);
4125 had_prompt = 1;
4126 }
4127 c = get_copy(0);
4128 }
4129 if (c == ' ') {
4130 tok.make_space();
4131 decode_args(mi);
4132 }
4133 }
4134 if (reading_from_terminal) {
4135 fputc(had_prompt ? ':' : '\a', stderr);
4136 fflush(stderr);
4137 }
4138 input_stack::push(mi);
4139 macro mac;
4140 int nl = 0;
4141 int c;
4142 while ((c = getchar()) != EOF) {
4143 if (invalid_input_char(c))
4144 warning(WARN_INPUT, "invalid input character code %1", int(c));
4145 else {
4146 if (c == '\n') {
4147 if (nl)
4148 break;
4149 else
4150 nl = 1;
4151 }
4152 else
4153 nl = 0;
4154 mac.append(c);
4155 }
4156 }
4157 if (reading_from_terminal)
4158 clearerr(stdin);
4159 input_stack::push(new string_iterator(mac));
4160 tok.next();
4161}
4162
4163enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
4164enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
465b256c 4165enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
92d0a6a6
JR
4166
4167void do_define_string(define_mode mode, comp_mode comp)
4168{
4169 symbol nm;
4170 node *n = 0; // pacify compiler
4171 int c;
4172 nm = get_name(1);
4173 if (nm.is_null()) {
4174 skip_line();
4175 return;
4176 }
4177 if (tok.newline())
4178 c = '\n';
4179 else if (tok.tab())
4180 c = '\t';
4181 else if (!tok.space()) {
4182 error("bad string definition");
4183 skip_line();
4184 return;
4185 }
4186 else
4187 c = get_copy(&n);
4188 while (c == ' ')
4189 c = get_copy(&n);
4190 if (c == '"')
4191 c = get_copy(&n);
4192 macro mac;
4193 request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
4194 macro *mm = rm ? rm->to_macro() : 0;
4195 if (mode == DEFINE_APPEND && mm)
4196 mac = *mm;
4197 if (comp == COMP_DISABLE)
465b256c
JR
4198 mac.append(PUSH_GROFF_MODE);
4199 else if (comp == COMP_ENABLE)
4200 mac.append(PUSH_COMP_MODE);
92d0a6a6
JR
4201 while (c != '\n' && c != EOF) {
4202 if (c == 0)
4203 mac.append(n);
4204 else
4205 mac.append((unsigned char)c);
4206 c = get_copy(&n);
4207 }
4d3e9548
JL
4208 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4209 mac.append(POP_GROFFCOMP_MODE);
92d0a6a6
JR
4210 if (!mm) {
4211 mm = new macro;
4212 request_dictionary.define(nm, mm);
4213 }
92d0a6a6
JR
4214 *mm = mac;
4215 tok.next();
4216}
4217
4218void define_string()
4219{
465b256c
JR
4220 do_define_string(DEFINE_NORMAL,
4221 compatible_flag ? COMP_ENABLE: COMP_IGNORE);
92d0a6a6
JR
4222}
4223
4224void define_nocomp_string()
4225{
4226 do_define_string(DEFINE_NORMAL, COMP_DISABLE);
4227}
4228
4229void append_string()
4230{
465b256c
JR
4231 do_define_string(DEFINE_APPEND,
4232 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
92d0a6a6
JR
4233}
4234
4235void append_nocomp_string()
4236{
4237 do_define_string(DEFINE_APPEND, COMP_DISABLE);
4238}
4239
4240void do_define_character(char_mode mode, const char *font_name)
4241{
4242 node *n = 0; // pacify compiler
4243 int c;
4244 tok.skip();
4245 charinfo *ci = tok.get_char(1);
4246 if (ci == 0) {
4247 skip_line();
4248 return;
4249 }
4250 if (font_name) {
4251 string s(font_name);
4252 s += ' ';
4253 s += ci->nm.contents();
4254 s += '\0';
4255 ci = get_charinfo(symbol(s.contents()));
4256 }
4257 tok.next();
4258 if (tok.newline())
4259 c = '\n';
4260 else if (tok.tab())
4261 c = '\t';
4262 else if (!tok.space()) {
4263 error("bad character definition");
4264 skip_line();
4265 return;
4266 }
4267 else
4268 c = get_copy(&n);
4269 while (c == ' ' || c == '\t')
4270 c = get_copy(&n);
4271 if (c == '"')
4272 c = get_copy(&n);
4273 macro *m = new macro;
4274 while (c != '\n' && c != EOF) {
4275 if (c == 0)
4276 m->append(n);
4277 else
4278 m->append((unsigned char)c);
4279 c = get_copy(&n);
4280 }
4281 m = ci->setx_macro(m, mode);
4282 if (m)
4283 delete m;
4284 tok.next();
4285}
4286
4287void define_character()
4288{
4289 do_define_character(CHAR_NORMAL);
4290}
4291
4292void define_fallback_character()
4293{
4294 do_define_character(CHAR_FALLBACK);
4295}
4296
4297void define_special_character()
4298{
4299 do_define_character(CHAR_SPECIAL);
4300}
4301
4302static void remove_character()
4303{
4304 tok.skip();
4305 while (!tok.newline() && !tok.eof()) {
4306 if (!tok.space() && !tok.tab()) {
4307 charinfo *ci = tok.get_char(1);
4308 if (!ci)
4309 break;
4310 macro *m = ci->set_macro(0);
4311 if (m)
4312 delete m;
4313 }
4314 tok.next();
4315 }
4316 skip_line();
4317}
4318
4319static void interpolate_string(symbol nm)
4320{
4321 request_or_macro *p = lookup_request(nm);
4322 macro *m = p->to_macro();
4323 if (!m)
4324 error("you can only invoke a string or macro using \\*");
4325 else {
4d3e9548
JL
4326 if (m->is_string()) {
4327 string_iterator *si = new string_iterator(*m, "string", nm);
4328 input_stack::push(si);
4329 }
4330 else {
4331 // if a macro is called as a string, \$0 doesn't get changed
4332 macro_iterator *mi = new macro_iterator(input_stack::get_macro_name(),
4333 *m, "string", 1);
4334 input_stack::push(mi);
4335 }
92d0a6a6
JR
4336 }
4337}
4338
4339static void interpolate_string_with_args(symbol s)
4340{
4341 request_or_macro *p = lookup_request(s);
4342 macro *m = p->to_macro();
4343 if (!m)
4344 error("you can only invoke a string or macro using \\*");
4345 else {
4346 macro_iterator *mi = new macro_iterator(s, *m);
4347 decode_string_args(mi);
4348 input_stack::push(mi);
4349 }
4350}
4351
92d0a6a6
JR
4352static void interpolate_arg(symbol nm)
4353{
4354 const char *s = nm.contents();
4355 if (!s || *s == '\0')
4356 copy_mode_error("missing argument name");
4357 else if (s[1] == 0 && csdigit(s[0]))
4358 input_stack::push(input_stack::get_arg(s[0] - '0'));
4359 else if (s[0] == '*' && s[1] == '\0') {
465b256c
JR
4360 int limit = input_stack::nargs();
4361 string args;
4362 for (int i = 1; i <= limit; i++) {
4363 input_iterator *p = input_stack::get_arg(i);
4364 int c;
4365 while ((c = p->get(0)) != EOF)
4d3e9548
JL
4366 if (c != DOUBLE_QUOTE)
4367 args += c;
465b256c
JR
4368 if (i != limit)
4369 args += ' ';
4370 }
4371 if (limit > 0) {
4372 args += '\0';
4373 input_stack::push(make_temp_iterator(args.contents()));
92d0a6a6
JR
4374 }
4375 }
4376 else if (s[0] == '@' && s[1] == '\0') {
465b256c
JR
4377 int limit = input_stack::nargs();
4378 string args;
4379 for (int i = 1; i <= limit; i++) {
4380 args += '"';
4d3e9548 4381 args += char(BEGIN_QUOTE);
465b256c
JR
4382 input_iterator *p = input_stack::get_arg(i);
4383 int c;
4384 while ((c = p->get(0)) != EOF)
4d3e9548
JL
4385 if (c != DOUBLE_QUOTE)
4386 args += c;
4387 args += char(END_QUOTE);
465b256c
JR
4388 args += '"';
4389 if (i != limit)
4390 args += ' ';
4391 }
4392 if (limit > 0) {
4393 args += '\0';
4394 input_stack::push(make_temp_iterator(args.contents()));
92d0a6a6
JR
4395 }
4396 }
4d3e9548
JL
4397 else if (s[0] == '^' && s[1] == '\0') {
4398 int limit = input_stack::nargs();
4399 string args;
4400 int c = input_stack::peek();
4401 for (int i = 1; i <= limit; i++) {
4402 input_iterator *p = input_stack::get_arg(i);
4403 while ((c = p->get(0)) != EOF) {
4404 if (c == DOUBLE_QUOTE)
4405 c = '"';
4406 args += c;
4407 }
4408 if (input_stack::space_follows_arg(i))
4409 args += ' ';
4410 }
4411 if (limit > 0) {
4412 args += '\0';
4413 input_stack::push(make_temp_iterator(args.contents()));
4414 }
4415 }
92d0a6a6
JR
4416 else {
4417 const char *p;
4418 for (p = s; *p && csdigit(*p); p++)
4419 ;
4420 if (*p)
4421 copy_mode_error("bad argument name `%1'", s);
4422 else
4423 input_stack::push(input_stack::get_arg(atoi(s)));
4424 }
4425}
4426
4427void handle_first_page_transition()
4428{
4429 push_token(tok);
4430 topdiv->begin_page();
4431}
4432
4433// We push back a token by wrapping it up in a token_node, and
4434// wrapping that up in a string_iterator.
4435
4436static void push_token(const token &t)
4437{
4438 macro m;
4439 m.append(new token_node(t));
4440 input_stack::push(new string_iterator(m));
4441}
4442
4443void push_page_ejector()
4444{
4445 static char buf[2] = { PAGE_EJECTOR, '\0' };
4446 input_stack::push(make_temp_iterator(buf));
4447}
4448
4449void handle_initial_request(unsigned char code)
4450{
4451 char buf[2];
4452 buf[0] = code;
4453 buf[1] = '\0';
4454 macro mac;
4455 mac.append(new token_node(tok));
4456 input_stack::push(new string_iterator(mac));
4457 input_stack::push(make_temp_iterator(buf));
4458 topdiv->begin_page();
4459 tok.next();
4460}
4461
4462void handle_initial_title()
4463{
4464 handle_initial_request(TITLE_REQUEST);
4465}
4466
4467// this should be local to define_macro, but cfront 1.2 doesn't support that
4468static symbol dot_symbol(".");
4469
4470void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
4471{
4472 symbol nm, term;
4473 if (calling == CALLING_INDIRECT) {
4474 symbol temp1 = get_name(1);
4475 if (temp1.is_null()) {
4476 skip_line();
4477 return;
4478 }
4479 symbol temp2 = get_name();
4480 input_stack::push(make_temp_iterator("\n"));
4481 if (!temp2.is_null()) {
4482 interpolate_string(temp2);
4483 input_stack::push(make_temp_iterator(" "));
4484 }
4485 interpolate_string(temp1);
4486 input_stack::push(make_temp_iterator(" "));
4487 tok.next();
4488 }
4489 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4490 nm = get_name(1);
4491 if (nm.is_null()) {
4492 skip_line();
4493 return;
4494 }
4495 }
4496 term = get_name(); // the request that terminates the definition
4497 if (term.is_null())
4498 term = dot_symbol;
4499 while (!tok.newline() && !tok.eof())
4500 tok.next();
4501 const char *start_filename;
4502 int start_lineno;
4503 int have_start_location = input_stack::get_location(0, &start_filename,
4504 &start_lineno);
4505 node *n;
4506 // doing this here makes the line numbers come out right
4507 int c = get_copy(&n, 1);
4508 macro mac;
4509 macro *mm = 0;
4510 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4511 request_or_macro *rm =
4512 (request_or_macro *)request_dictionary.lookup(nm);
4513 if (rm)
4514 mm = rm->to_macro();
4515 if (mm && mode == DEFINE_APPEND)
4516 mac = *mm;
4517 }
4518 int bol = 1;
4519 if (comp == COMP_DISABLE)
465b256c
JR
4520 mac.append(PUSH_GROFF_MODE);
4521 else if (comp == COMP_ENABLE)
4522 mac.append(PUSH_COMP_MODE);
92d0a6a6 4523 for (;;) {
4d3e9548
JL
4524 if (c == '\n')
4525 mac.clear_string_flag();
92d0a6a6
JR
4526 while (c == ESCAPE_NEWLINE) {
4527 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
4528 mac.append(c);
4529 c = get_copy(&n, 1);
4530 }
4531 if (bol && c == '.') {
4532 const char *s = term.contents();
4533 int d = 0;
4534 // see if it matches term
4535 int i = 0;
4536 if (s[0] != 0) {
4537 while ((d = get_copy(&n)) == ' ' || d == '\t')
4538 ;
4539 if ((unsigned char)s[0] == d) {
4540 for (i = 1; s[i] != 0; i++) {
4541 d = get_copy(&n);
4542 if ((unsigned char)s[i] != d)
4543 break;
4544 }
4545 }
4546 }
4547 if (s[i] == 0
4548 && ((i == 2 && compatible_flag)
4549 || (d = get_copy(&n)) == ' '
4550 || d == '\n')) { // we found it
4551 if (d == '\n')
4552 tok.make_newline();
4553 else
4554 tok.make_space();
4555 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4556 if (!mm) {
4557 mm = new macro;
4558 request_dictionary.define(nm, mm);
4559 }
465b256c
JR
4560 if (comp == COMP_DISABLE || comp == COMP_ENABLE)
4561 mac.append(POP_GROFFCOMP_MODE);
92d0a6a6
JR
4562 *mm = mac;
4563 }
4564 if (term != dot_symbol) {
4565 ignoring = 0;
4566 interpolate_macro(term);
4567 }
4568 else
4569 skip_line();
4570 return;
4571 }
4572 if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
4573 mac.append(c);
4574 for (int j = 0; j < i; j++)
4575 mac.append(s[j]);
4576 }
4577 c = d;
4578 }
4579 if (c == EOF) {
4580 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4581 if (have_start_location)
4582 error_with_file_and_line(start_filename, start_lineno,
4583 "end of file while defining macro `%1'",
4584 nm.contents());
4585 else
4586 error("end of file while defining macro `%1'", nm.contents());
4587 }
4588 else {
4589 if (have_start_location)
4590 error_with_file_and_line(start_filename, start_lineno,
4591 "end of file while ignoring input lines");
4592 else
4593 error("end of file while ignoring input lines");
4594 }
4595 tok.next();
4596 return;
4597 }
4598 if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
4599 if (c == 0)
4600 mac.append(n);
4601 else
4602 mac.append(c);
4603 }
4604 bol = (c == '\n');
4605 c = get_copy(&n, 1);
4606 }
4607}
4608
4609void define_macro()
4610{
465b256c
JR
4611 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
4612 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
92d0a6a6
JR
4613}
4614
4615void define_nocomp_macro()
4616{
4617 do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
4618}
4619
4620void define_indirect_macro()
4621{
465b256c
JR
4622 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
4623 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
92d0a6a6
JR
4624}
4625
4626void define_indirect_nocomp_macro()
4627{
4628 do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
4629}
4630
4631void append_macro()
4632{
465b256c
JR
4633 do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
4634 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
92d0a6a6
JR
4635}
4636
4637void append_nocomp_macro()
4638{
4639 do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
4640}
4641
4642void append_indirect_macro()
4643{
465b256c
JR
4644 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
4645 compatible_flag ? COMP_ENABLE : COMP_IGNORE);
92d0a6a6
JR
4646}
4647
4648void append_indirect_nocomp_macro()
4649{
4650 do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
4651}
4652
4653void ignore()
4654{
4655 ignoring = 1;
4656 do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
4657 ignoring = 0;
4658}
4659
4660void remove_macro()
4661{
4662 for (;;) {
4663 symbol s = get_name();
4664 if (s.is_null())
4665 break;
4666 request_dictionary.remove(s);
4667 }
4668 skip_line();
4669}
4670
4671void rename_macro()
4672{
4673 symbol s1 = get_name(1);
4674 if (!s1.is_null()) {
4675 symbol s2 = get_name(1);
4676 if (!s2.is_null())
4677 request_dictionary.rename(s1, s2);
4678 }
4679 skip_line();
4680}
4681
4682void alias_macro()
4683{
4684 symbol s1 = get_name(1);
4685 if (!s1.is_null()) {
4686 symbol s2 = get_name(1);
4687 if (!s2.is_null()) {
4688 if (!request_dictionary.alias(s1, s2))
4689 warning(WARN_MAC, "macro `%1' not defined", s2.contents());
4690 }
4691 }
4692 skip_line();
4693}
4694
4695void chop_macro()
4696{
4697 symbol s = get_name(1);
4698 if (!s.is_null()) {
4699 request_or_macro *p = lookup_request(s);
4700 macro *m = p->to_macro();
4701 if (!m)
4702 error("cannot chop request");
4703 else if (m->empty())
4704 error("cannot chop empty macro");
4705 else {
4706 int have_restore = 0;
4707 // we have to check for additional save/restore pairs which could be
4708 // there due to empty am1 requests.
4709 for (;;) {
465b256c 4710 if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
4d3e9548 4711 break;
92d0a6a6
JR
4712 have_restore = 1;
4713 m->len -= 1;
465b256c
JR
4714 if (m->get(m->len - 1) != PUSH_GROFF_MODE
4715 && m->get(m->len - 1) != PUSH_COMP_MODE)
4d3e9548 4716 break;
92d0a6a6
JR
4717 have_restore = 0;
4718 m->len -= 1;
4719 if (m->len == 0)
4720 break;
4721 }
4722 if (m->len == 0)
4723 error("cannot chop empty macro");
4724 else {
4725 if (have_restore)
465b256c 4726 m->set(POP_GROFFCOMP_MODE, m->len - 1);
92d0a6a6
JR
4727 else
4728 m->len -= 1;
4729 }
4730 }
4731 }
4732 skip_line();
4733}
4734
4735void substring_request()
4736{
4737 int start; // 0, 1, ..., n-1 or -1, -2, ...
4738 symbol s = get_name(1);
4739 if (!s.is_null() && get_integer(&start)) {
4740 request_or_macro *p = lookup_request(s);
4741 macro *m = p->to_macro();
4742 if (!m)
4743 error("cannot apply `substring' on a request");
4744 else {
4745 int end = -1;
4746 if (!has_arg() || get_integer(&end)) {
4747 int real_length = 0; // 1, 2, ..., n
4748 string_iterator iter1(*m);
4749 for (int l = 0; l < m->len; l++) {
4750 int c = iter1.get(0);
465b256c
JR
4751 if (c == PUSH_GROFF_MODE
4752 || c == PUSH_COMP_MODE
4753 || c == POP_GROFFCOMP_MODE)
92d0a6a6
JR
4754 continue;
4755 if (c == EOF)
4756 break;
4757 real_length++;
4758 }
4759 if (start < 0)
4760 start += real_length;
4761 if (end < 0)
4762 end += real_length;
4763 if (start > end) {
4764 int tem = start;
4765 start = end;
4766 end = tem;
4767 }
4768 if (start >= real_length || end < 0) {
4769 warning(WARN_RANGE,
4770 "start and end index of substring out of range");
4771 m->len = 0;
4772 if (m->p) {
4773 if (--(m->p->count) <= 0)
4774 delete m->p;
4775 m->p = 0;
4776 }
4777 skip_line();
4778 return;
4779 }
4780 if (start < 0) {
4781 warning(WARN_RANGE,
4782 "start index of substring out of range, set to 0");
4783 start = 0;
4784 }
4785 if (end >= real_length) {
4786 warning(WARN_RANGE,
4787 "end index of substring out of range, set to string length");
4788 end = real_length - 1;
4789 }
4790 // now extract the substring
4791 string_iterator iter(*m);
4792 int i;
4793 for (i = 0; i < start; i++) {
4794 int c = iter.get(0);
465b256c
JR
4795 while (c == PUSH_GROFF_MODE
4796 || c == PUSH_COMP_MODE
4797 || c == POP_GROFFCOMP_MODE)
92d0a6a6
JR
4798 c = iter.get(0);
4799 if (c == EOF)
4800 break;
4801 }
4802 macro mac;
4803 for (; i <= end; i++) {
4804 node *nd = 0; // pacify compiler
4805 int c = iter.get(&nd);
465b256c
JR
4806 while (c == PUSH_GROFF_MODE
4807 || c == PUSH_COMP_MODE
4808 || c == POP_GROFFCOMP_MODE)
92d0a6a6
JR
4809 c = iter.get(0);
4810 if (c == EOF)
4811 break;
4812 if (c == 0)
4813 mac.append(nd);
4814 else
4815 mac.append((unsigned char)c);
4816 }
4817 *m = mac;
4818 }
4819 }
4820 }
4821 skip_line();
4822}
4823
4824void length_request()
4825{
4826 symbol ret;
4827 ret = get_name(1);
4828 if (ret.is_null()) {
4829 skip_line();
4830 return;
4831 }
4832 int c;
4833 node *n;
4834 if (tok.newline())
4835 c = '\n';
4836 else if (tok.tab())
4837 c = '\t';
4838 else if (!tok.space()) {
4839 error("bad string definition");
4840 skip_line();
4841 return;
4842 }
4843 else
4844 c = get_copy(&n);
4845 while (c == ' ')
4846 c = get_copy(&n);
4847 if (c == '"')
4848 c = get_copy(&n);
4849 int len = 0;
4850 while (c != '\n' && c != EOF) {
4851 ++len;
4852 c = get_copy(&n);
4853 }
4854 reg *r = (reg*)number_reg_dictionary.lookup(ret);
4855 if (r)
4856 r->set_value(len);
4857 else
4858 set_number_reg(ret, len);
4859 tok.next();
4860}
4861
4862void asciify_macro()
4863{
4864 symbol s = get_name(1);
4865 if (!s.is_null()) {
4866 request_or_macro *p = lookup_request(s);
4867 macro *m = p->to_macro();
4868 if (!m)
4869 error("cannot asciify request");
4870 else {
4871 macro am;
4872 string_iterator iter(*m);
4873 for (;;) {
4874 node *nd = 0; // pacify compiler
4875 int c = iter.get(&nd);
4876 if (c == EOF)
4877 break;
4878 if (c != 0)
4879 am.append(c);
4880 else
4881 nd->asciify(&am);
4882 }
4883 *m = am;
4884 }
4885 }
4886 skip_line();
4887}
4888
4889void unformat_macro()
4890{
4891 symbol s = get_name(1);
4892 if (!s.is_null()) {
4893 request_or_macro *p = lookup_request(s);
4894 macro *m = p->to_macro();
4895 if (!m)
4896 error("cannot unformat request");
4897 else {
4898 macro am;
4899 string_iterator iter(*m);
4900 for (;;) {
4901 node *nd = 0; // pacify compiler
4902 int c = iter.get(&nd);
4903 if (c == EOF)
4904 break;
4905 if (c != 0)
4906 am.append(c);
4907 else {
4908 if (nd->set_unformat_flag())
4909 am.append(nd);
4910 }
4911 }
4912 *m = am;
4913 }
4914 }
4915 skip_line();
4916}
4917
4918static void interpolate_environment_variable(symbol nm)
4919{
4920 const char *s = getenv(nm.contents());
4921 if (s && *s)
4922 input_stack::push(make_temp_iterator(s));
4923}
4924
4925void interpolate_number_reg(symbol nm, int inc)
4926{
4927 reg *r = lookup_number_reg(nm);
4928 if (inc < 0)
4929 r->decrement();
4930 else if (inc > 0)
4931 r->increment();
4932 input_stack::push(make_temp_iterator(r->get_string()));
4933}
4934
4935static void interpolate_number_format(symbol nm)
4936{
4937 reg *r = (reg *)number_reg_dictionary.lookup(nm);
4938 if (r)
4939 input_stack::push(make_temp_iterator(r->get_format()));
4940}
4941
4942static int get_delim_number(units *n, unsigned char si, int prev_value)
4943{
4944 token start;
4945 start.next();
4946 if (start.delimiter(1)) {
4947 tok.next();
4948 if (get_number(n, si, prev_value)) {
4949 if (start != tok)
4950 warning(WARN_DELIM, "closing delimiter does not match");
4951 return 1;
4952 }
4953 }
4954 return 0;
4955}
4956
4957static int get_delim_number(units *n, unsigned char si)
4958{
4959 token start;
4960 start.next();
4961 if (start.delimiter(1)) {
4962 tok.next();
4963 if (get_number(n, si)) {
4964 if (start != tok)
4965 warning(WARN_DELIM, "closing delimiter does not match");
4966 return 1;
4967 }
4968 }
4969 return 0;
4970}
4971
4972static int get_line_arg(units *n, unsigned char si, charinfo **cp)
4973{
4974 token start;
4975 start.next();
4976 int start_level = input_stack::get_level();
4977 if (!start.delimiter(1))
4978 return 0;
4979 tok.next();
4980 if (get_number(n, si)) {
4981 if (tok.dummy() || tok.transparent_dummy())
4982 tok.next();
4983 if (!(start == tok && input_stack::get_level() == start_level)) {
4984 *cp = tok.get_char(1);
4985 tok.next();
4986 }
4987 if (!(start == tok && input_stack::get_level() == start_level))
4988 warning(WARN_DELIM, "closing delimiter does not match");
4989 return 1;
4990 }
4991 return 0;
4992}
4993
4994static int read_size(int *x)
4995{
4996 tok.next();
4997 int c = tok.ch();
4998 int inc = 0;
4999 if (c == '-') {
5000 inc = -1;
5001 tok.next();
5002 c = tok.ch();
5003 }
5004 else if (c == '+') {
5005 inc = 1;
5006 tok.next();
5007 c = tok.ch();
5008 }
5009 int val = 0; // pacify compiler
5010 int bad = 0;
5011 if (c == '(') {
5012 tok.next();
5013 c = tok.ch();
5014 if (!inc) {
5015 // allow an increment either before or after the left parenthesis
5016 if (c == '-') {
5017 inc = -1;
5018 tok.next();
5019 c = tok.ch();
5020 }
5021 else if (c == '+') {
5022 inc = 1;
5023 tok.next();
5024 c = tok.ch();
5025 }
5026 }
5027 if (!csdigit(c))
5028 bad = 1;
5029 else {
5030 val = c - '0';
5031 tok.next();
5032 c = tok.ch();
5033 if (!csdigit(c))
5034 bad = 1;
5035 else {
5036 val = val*10 + (c - '0');
5037 val *= sizescale;
5038 }
5039 }
5040 }
5041 else if (csdigit(c)) {
5042 val = c - '0';
5043 if (!inc && c != '0' && c < '4') {
5044 tok.next();
5045 c = tok.ch();
5046 if (!csdigit(c))
5047 bad = 1;
5048 else
5049 val = val*10 + (c - '0');
5050 }
5051 val *= sizescale;
5052 }
5053 else if (!tok.delimiter(1))
5054 return 0;
5055 else {
5056 token start(tok);
5057 tok.next();
4d3e9548
JL
5058 c = tok.ch();
5059 if (!inc && (c == '-' || c == '+')) {
5060 inc = c == '+' ? 1 : -1;
5061 tok.next();
5062 }
5063 if (!get_number(&val, 'z'))
92d0a6a6
JR
5064 return 0;
5065 if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
5066 if (start.ch() == '[')
5067 error("missing `]'");
5068 else
5069 error("missing closing delimiter");
5070 return 0;
5071 }
5072 }
5073 if (!bad) {
5074 switch (inc) {
5075 case 0:
5076 if (val == 0) {
5077 // special case -- \s[0] and \s0 means to revert to previous size
5078 *x = 0;
5079 return 1;
5080 }
5081 *x = val;
5082 break;
5083 case 1:
5084 *x = curenv->get_requested_point_size() + val;
5085 break;
5086 case -1:
5087 *x = curenv->get_requested_point_size() - val;
5088 break;
5089 default:
5090 assert(0);
5091 }
5092 if (*x <= 0) {
5093 warning(WARN_RANGE,
4d3e9548 5094 "\\s escape results in non-positive point size; set to 1");
92d0a6a6
JR
5095 *x = 1;
5096 }
5097 return 1;
5098 }
5099 else {
5100 error("bad digit in point size");
5101 return 0;
5102 }
5103}
5104
5105static symbol get_delim_name()
5106{
5107 token start;
5108 start.next();
5109 if (start.eof()) {
5110 error("end of input at start of delimited name");
5111 return NULL_SYMBOL;
5112 }
5113 if (start.newline()) {
5114 error("can't delimit name with a newline");
5115 return NULL_SYMBOL;
5116 }
5117 int start_level = input_stack::get_level();
5118 char abuf[ABUF_SIZE];
5119 char *buf = abuf;
5120 int buf_size = ABUF_SIZE;
5121 int i = 0;
5122 for (;;) {
5123 if (i + 1 > buf_size) {
5124 if (buf == abuf) {
5125 buf = new char[ABUF_SIZE*2];
5126 memcpy(buf, abuf, buf_size);
5127 buf_size = ABUF_SIZE*2;
5128 }
5129 else {
5130 char *old_buf = buf;
5131 buf = new char[buf_size*2];
5132 memcpy(buf, old_buf, buf_size);
5133 buf_size *= 2;
5134 a_delete old_buf;
5135 }
5136 }
5137 tok.next();
5138 if (tok == start
5139 && (compatible_flag || input_stack::get_level() == start_level))
5140 break;
5141 if ((buf[i] = tok.ch()) == 0) {
5142 error("missing delimiter (got %1)", tok.description());
5143 if (buf != abuf)
5144 a_delete buf;
5145 return NULL_SYMBOL;
5146 }
5147 i++;
5148 }
5149 buf[i] = '\0';
5150 if (buf == abuf) {
5151 if (i == 0) {
5152 error("empty delimited name");
5153 return NULL_SYMBOL;
5154 }
5155 else
5156 return symbol(buf);
5157 }
5158 else {
5159 symbol s(buf);
5160 a_delete buf;
5161 return s;
5162 }
5163}
5164
5165// Implement \R
5166
5167static void do_register()
5168{
5169 token start;
5170 start.next();
5171 if (!start.delimiter(1))
5172 return;
5173 tok.next();
5174 symbol nm = get_long_name(1);
5175 if (nm.is_null())
5176 return;
5177 while (tok.space())
5178 tok.next();
5179 reg *r = (reg *)number_reg_dictionary.lookup(nm);
5180 int prev_value;
5181 if (!r || !r->get_value(&prev_value))
5182 prev_value = 0;
5183 int val;
5184 if (!get_number(&val, 'u', prev_value))
5185 return;
5186 if (start != tok)
5187 warning(WARN_DELIM, "closing delimiter does not match");
5188 if (r)
5189 r->set_value(val);
5190 else
5191 set_number_reg(nm, val);
5192}
5193
5194// this implements the \w escape sequence
5195
5196static void do_width()
5197{
5198 token start;
5199 start.next();
5200 int start_level = input_stack::get_level();
5201 environment env(curenv);
5202 environment *oldenv = curenv;
5203 curenv = &env;
5204 for (;;) {
5205 tok.next();
5206 if (tok.eof()) {
5207 warning(WARN_DELIM, "missing closing delimiter");
5208 break;
5209 }
5210 if (tok.newline()) {
5211 warning(WARN_DELIM, "missing closing delimiter");
5212 input_stack::push(make_temp_iterator("\n"));
5213 break;
5214 }
5215 if (tok == start
5216 && (compatible_flag || input_stack::get_level() == start_level))
5217 break;
5218 tok.process();
5219 }
5220 env.wrap_up_tab();
5221 units x = env.get_input_line_position().to_units();
5222 input_stack::push(make_temp_iterator(i_to_a(x)));
5223 env.width_registers();
5224 curenv = oldenv;
5225 have_input = 0;
5226}
5227
5228charinfo *page_character;
5229
5230void set_page_character()
5231{
5232 page_character = get_optional_char();
5233 skip_line();
5234}
5235
5236static const symbol percent_symbol("%");
5237
5238void read_title_parts(node **part, hunits *part_width)
5239{
5240 tok.skip();
5241 if (tok.newline() || tok.eof())
5242 return;
5243 token start(tok);
5244 int start_level = input_stack::get_level();
5245 tok.next();
5246 for (int i = 0; i < 3; i++) {
5247 while (!tok.newline() && !tok.eof()) {
5248 if (tok == start
5249 && (compatible_flag || input_stack::get_level() == start_level)) {
5250 tok.next();
5251 break;
5252 }
5253 if (page_character != 0 && tok.get_char() == page_character)
5254 interpolate_number_reg(percent_symbol, 0);
5255 else
5256 tok.process();
5257 tok.next();
5258 }
5259 curenv->wrap_up_tab();
5260 part_width[i] = curenv->get_input_line_position();
5261 part[i] = curenv->extract_output_line();
5262 }
5263 while (!tok.newline() && !tok.eof())
5264 tok.next();
5265}
5266
5267class non_interpreted_node : public node {
5268 macro mac;
5269public:
5270 non_interpreted_node(const macro &);
5271 int interpret(macro *);
5272 node *copy();
5273 int ends_sentence();
5274 int same(node *);
5275 const char *type();
5276 int force_tprint();
465b256c 5277 int is_tag();
92d0a6a6
JR
5278};
5279
5280non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
5281{
5282}
5283
5284int non_interpreted_node::ends_sentence()
5285{
5286 return 2;
5287}
5288
5289int non_interpreted_node::same(node *nd)
5290{
5291 return mac == ((non_interpreted_node *)nd)->mac;
5292}
5293
5294const char *non_interpreted_node::type()
5295{
5296 return "non_interpreted_node";
5297}
5298
5299int non_interpreted_node::force_tprint()
5300{
5301 return 0;
5302}
5303
465b256c
JR
5304int non_interpreted_node::is_tag()
5305{
5306 return 0;
5307}
5308
92d0a6a6
JR
5309node *non_interpreted_node::copy()
5310{
5311 return new non_interpreted_node(mac);
5312}
5313
5314int non_interpreted_node::interpret(macro *m)
5315{
5316 string_iterator si(mac);
5317 node *n = 0; // pacify compiler
5318 for (;;) {
5319 int c = si.get(&n);
5320 if (c == EOF)
5321 break;
5322 if (c == 0)
5323 m->append(n);
5324 else
5325 m->append(c);
5326 }
5327 return 1;
5328}
5329
5330static node *do_non_interpreted()
5331{
5332 node *n;
5333 int c;
5334 macro mac;
5335 while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
5336 if (c == 0)
5337 mac.append(n);
5338 else
5339 mac.append(c);
5340 if (c == EOF || c == '\n') {
5341 error("missing \\?");
5342 return 0;
5343 }
5344 return new non_interpreted_node(mac);
5345}
5346
5347static void encode_char(macro *mac, char c)
5348{
5349 if (c == '\0') {
5350 if ((font::use_charnames_in_special) && tok.special()) {
5351 charinfo *ci = tok.get_char(1);
5352 const char *s = ci->get_symbol()->contents();
5353 if (s[0] != (char)0) {
5354 mac->append('\\');
4d3e9548 5355 mac->append('[');
92d0a6a6
JR
5356 int i = 0;
5357 while (s[i] != (char)0) {
5358 mac->append(s[i]);
5359 i++;
5360 }
4d3e9548 5361 mac->append(']');
92d0a6a6
JR
5362 }
5363 }
5364 else if (tok.stretchable_space()
5365 || tok.unstretchable_space())
5366 mac->append(' ');
5367 else if (!(tok.hyphen_indicator()
5368 || tok.dummy()
5369 || tok.transparent_dummy()
5370 || tok.zero_width_break()))
5371 error("%1 is invalid within \\X", tok.description());
5372 }
5373 else {
5374 if ((font::use_charnames_in_special) && (c == '\\')) {
5375 /*
5376 * add escape escape sequence
5377 */
5378 mac->append(c);
5379 }
5380 mac->append(c);
5381 }
5382}
5383
5384node *do_special()
5385{
5386 token start;
5387 start.next();
5388 int start_level = input_stack::get_level();
5389 macro mac;
5390 for (tok.next();
5391 tok != start || input_stack::get_level() != start_level;
5392 tok.next()) {
5393 if (tok.eof()) {
5394 warning(WARN_DELIM, "missing closing delimiter");
5395 return 0;
5396 }
5397 if (tok.newline()) {
5398 input_stack::push(make_temp_iterator("\n"));
5399 warning(WARN_DELIM, "missing closing delimiter");
5400 break;
5401 }
5402 unsigned char c;
5403 if (tok.space())
5404 c = ' ';
5405 else if (tok.tab())
5406 c = '\t';
5407 else if (tok.leader())
5408 c = '\001';
5409 else if (tok.backspace())
5410 c = '\b';
5411 else
5412 c = tok.ch();
5413 encode_char(&mac, c);
5414 }
5415 return new special_node(mac);
5416}
5417
4d3e9548
JL
5418void device_request()
5419{
5420 if (!tok.newline() && !tok.eof()) {
5421 int c;
5422 macro mac;
5423 for (;;) {
5424 c = get_copy(0);
5425 if (c == '"') {
5426 c = get_copy(0);
5427 break;
5428 }
5429 if (c != ' ' && c != '\t')
5430 break;
5431 }
5432 for (; c != '\n' && c != EOF; c = get_copy(0))
5433 mac.append(c);
5434 curenv->add_node(new special_node(mac));
5435 }
5436 tok.next();
5437}
5438
5439void device_macro_request()
5440{
5441 symbol s = get_name(1);
5442 if (!(s.is_null() || s.is_empty())) {
5443 request_or_macro *p = lookup_request(s);
5444 macro *m = p->to_macro();
5445 if (m)
5446 curenv->add_node(new special_node(*m));
5447 else
5448 error("can't transparently throughput a request");
5449 }
5450 skip_line();
5451}
5452
92d0a6a6
JR
5453void output_request()
5454{
5455 if (!tok.newline() && !tok.eof()) {
5456 int c;
5457 for (;;) {
5458 c = get_copy(0);
5459 if (c == '"') {
5460 c = get_copy(0);
5461 break;
5462 }
5463 if (c != ' ' && c != '\t')
5464 break;
5465 }
5466 for (; c != '\n' && c != EOF; c = get_copy(0))
5467 topdiv->transparent_output(c);
5468 topdiv->transparent_output('\n');
5469 }
5470 tok.next();
5471}
5472
5473extern int image_no; // from node.cpp
5474
5475static node *do_suppress(symbol nm)
5476{
5477 if (nm.is_null() || nm.is_empty()) {
5478 error("expecting an argument to escape \\O");
5479 return 0;
5480 }
5481 const char *s = nm.contents();
5482 switch (*s) {
5483 case '0':
5484 if (begin_level == 0)
5485 // suppress generation of glyphs
5486 return new suppress_node(0, 0);
5487 break;
5488 case '1':
5489 if (begin_level == 0)
5490 // enable generation of glyphs
5491 return new suppress_node(1, 0);
5492 break;
5493 case '2':
5494 if (begin_level == 0)
5495 return new suppress_node(1, 1);
5496 break;
5497 case '3':
4d3e9548 5498 have_input = 1;
92d0a6a6
JR
5499 begin_level++;
5500 break;
5501 case '4':
4d3e9548 5502 have_input = 1;
92d0a6a6
JR
5503 begin_level--;
5504 break;
5505 case '5':
5506 {
5507 s++; // move over '5'
5508 char position = *s;
5509 if (*s == (char)0) {
5510 error("missing position and filename in \\O");
5511 return 0;
5512 }
5513 if (!(position == 'l'
5514 || position == 'r'
5515 || position == 'c'
5516 || position == 'i')) {
5517 error("l, r, c, or i position expected (got %1 in \\O)", position);
5518 return 0;
5519 }
5520 s++; // onto image name
5521 if (s == (char *)0) {
5522 error("missing image name for \\O");
5523 return 0;
5524 }
5525 image_no++;
5526 if (begin_level == 0)
5527 return new suppress_node(symbol(s), position, image_no);
4d3e9548
JL
5528 else
5529 have_input = 1;
92d0a6a6
JR
5530 }
5531 break;
5532 default:
5533 error("`%1' is an invalid argument to \\O", *s);
5534 }
5535 return 0;
5536}
5537
5538void special_node::tprint(troff_output_file *out)
5539{
5540 tprint_start(out);
5541 string_iterator iter(mac);
5542 for (;;) {
5543 int c = iter.get(0);
5544 if (c == EOF)
5545 break;
5546 for (const char *s = ::asciify(c); *s; s++)
5547 tprint_char(out, *s);
5548 }
5549 tprint_end(out);
5550}
5551
5552int get_file_line(const char **filename, int *lineno)
5553{
5554 return input_stack::get_location(0, filename, lineno);
5555}
5556
5557void line_file()
5558{
5559 int n;
5560 if (get_integer(&n)) {
5561 const char *filename = 0;
5562 if (has_arg()) {
5563 symbol s = get_long_name();
5564 filename = s.contents();
5565 }
5566 (void)input_stack::set_location(filename, n-1);
5567 }
5568 skip_line();
5569}
5570
5571static int nroff_mode = 0;
5572
5573static void nroff_request()
5574{
5575 nroff_mode = 1;
5576 skip_line();
5577}
5578
5579static void troff_request()
5580{
5581 nroff_mode = 0;
5582 skip_line();
5583}
5584
5585static void skip_alternative()
5586{
5587 int level = 0;
5588 // ensure that ``.if 0\{'' works as expected
5589 if (tok.left_brace())
5590 level++;
5591 int c;
5592 for (;;) {
5593 c = input_stack::get(0);
5594 if (c == EOF)
5595 break;
5596 if (c == ESCAPE_LEFT_BRACE)
5597 ++level;
5598 else if (c == ESCAPE_RIGHT_BRACE)
5599 --level;
5600 else if (c == escape_char && escape_char > 0)
5601 switch(input_stack::get(0)) {
5602 case '{':
5603 ++level;
5604 break;
5605 case '}':
5606 --level;
5607 break;
5608 case '"':
5609 while ((c = input_stack::get(0)) != '\n' && c != EOF)
5610 ;
5611 }
5612 /*
5613 Note that the level can properly be < 0, eg
5614
5615 .if 1 \{\
5616 .if 0 \{\
5617 .\}\}
5618
5619 So don't give an error message in this case.
5620 */
5621 if (level <= 0 && c == '\n')
5622 break;
5623 }
5624 tok.next();
5625}
5626
5627static void begin_alternative()
5628{
5629 while (tok.space() || tok.left_brace())
5630 tok.next();
5631}
5632
5633void nop_request()
5634{
5635 while (tok.space())
5636 tok.next();
5637}
5638
5639static int_stack if_else_stack;
5640
5641int do_if_request()
5642{
5643 int invert = 0;
5644 while (tok.space())
5645 tok.next();
5646 while (tok.ch() == '!') {
5647 tok.next();
5648 invert = !invert;
5649 }
5650 int result;
5651 unsigned char c = tok.ch();
5652 if (c == 't') {
5653 tok.next();
5654 result = !nroff_mode;
5655 }
5656 else if (c == 'n') {
5657 tok.next();
5658 result = nroff_mode;
5659 }
5660 else if (c == 'v') {
5661 tok.next();
5662 result = 0;
5663 }
5664 else if (c == 'o') {
5665 result = (topdiv->get_page_number() & 1);
5666 tok.next();
5667 }
5668 else if (c == 'e') {
5669 result = !(topdiv->get_page_number() & 1);
5670 tok.next();
5671 }
5672 else if (c == 'd' || c == 'r') {
5673 tok.next();
5674 symbol nm = get_name(1);
5675 if (nm.is_null()) {
5676 skip_alternative();
5677 return 0;
5678 }
5679 result = (c == 'd'
5680 ? request_dictionary.lookup(nm) != 0
5681 : number_reg_dictionary.lookup(nm) != 0);
5682 }
5683 else if (c == 'm') {
5684 tok.next();
5685 symbol nm = get_long_name(1);
5686 if (nm.is_null()) {
5687 skip_alternative();
5688 return 0;
5689 }
5690 result = (nm == default_symbol
5691 || color_dictionary.lookup(nm) != 0);
5692 }
5693 else if (c == 'c') {
5694 tok.next();
5695 tok.skip();
5696 charinfo *ci = tok.get_char(1);
5697 if (ci == 0) {
5698 skip_alternative();
5699 return 0;
5700 }
5701 result = character_exists(ci, curenv);
5702 tok.next();
5703 }
465b256c
JR
5704 else if (c == 'F') {
5705 tok.next();
5706 symbol nm = get_long_name(1);
5707 if (nm.is_null()) {
5708 skip_alternative();
5709 return 0;
5710 }
5711 result = check_font(curenv->get_family()->nm, nm);
5712 }
5713 else if (c == 'S') {
5714 tok.next();
5715 symbol nm = get_long_name(1);
5716 if (nm.is_null()) {
5717 skip_alternative();
5718 return 0;
5719 }
5720 result = check_style(nm);
5721 }
92d0a6a6
JR
5722 else if (tok.space())
5723 result = 0;
5724 else if (tok.delimiter()) {
5725 token delim = tok;
5726 int delim_level = input_stack::get_level();
5727 environment env1(curenv);
5728 environment env2(curenv);
5729 environment *oldenv = curenv;
5730 curenv = &env1;
465b256c 5731 suppress_push = 1;
92d0a6a6
JR
5732 for (int i = 0; i < 2; i++) {
5733 for (;;) {
5734 tok.next();
5735 if (tok.newline() || tok.eof()) {
5736 warning(WARN_DELIM, "missing closing delimiter");
5737 tok.next();
5738 curenv = oldenv;
5739 return 0;
5740 }
5741 if (tok == delim
5742 && (compatible_flag || input_stack::get_level() == delim_level))
5743 break;
5744 tok.process();
5745 }
5746 curenv = &env2;
5747 }
5748 node *n1 = env1.extract_output_line();
5749 node *n2 = env2.extract_output_line();
5750 result = same_node_list(n1, n2);
5751 delete_node_list(n1);
5752 delete_node_list(n2);
5753 curenv = oldenv;
5754 have_input = 0;
465b256c 5755 suppress_push = 0;
92d0a6a6
JR
5756 tok.next();
5757 }
5758 else {
5759 units n;
5760 if (!get_number(&n, 'u')) {
5761 skip_alternative();
5762 return 0;
5763 }
5764 else
5765 result = n > 0;
5766 }
5767 if (invert)
5768 result = !result;
5769 if (result)
5770 begin_alternative();
5771 else
5772 skip_alternative();
5773 return result;
5774}
5775
5776void if_else_request()
5777{
5778 if_else_stack.push(do_if_request());
5779}
5780
5781void if_request()
5782{
5783 do_if_request();
5784}
5785
5786void else_request()
5787{
5788 if (if_else_stack.is_empty()) {
5789 warning(WARN_EL, "unbalanced .el request");
5790 skip_alternative();
5791 }
5792 else {
5793 if (if_else_stack.pop())
5794 skip_alternative();
5795 else
5796 begin_alternative();
5797 }
5798}
5799
5800static int while_depth = 0;
5801static int while_break_flag = 0;
5802
5803void while_request()
5804{
5805 macro mac;
5806 int escaped = 0;
5807 int level = 0;
5808 mac.append(new token_node(tok));
5809 for (;;) {
5810 node *n = 0; // pacify compiler
5811 int c = input_stack::get(&n);
5812 if (c == EOF)
5813 break;
5814 if (c == 0) {
5815 escaped = 0;
5816 mac.append(n);
5817 }
5818 else if (escaped) {
5819 if (c == '{')
5820 level += 1;
5821 else if (c == '}')
5822 level -= 1;
5823 escaped = 0;
5824 mac.append(c);
5825 }
5826 else {
5827 if (c == ESCAPE_LEFT_BRACE)
5828 level += 1;
5829 else if (c == ESCAPE_RIGHT_BRACE)
5830 level -= 1;
5831 else if (c == escape_char)
5832 escaped = 1;
5833 mac.append(c);
5834 if (c == '\n' && level <= 0)
5835 break;
5836 }
5837 }
5838 if (level != 0)
5839 error("unbalanced \\{ \\}");
5840 else {
5841 while_depth++;
5842 input_stack::add_boundary();
5843 for (;;) {
5844 input_stack::push(new string_iterator(mac, "while loop"));
5845 tok.next();
5846 if (!do_if_request()) {
5847 while (input_stack::get(0) != EOF)
5848 ;
5849 break;
5850 }
5851 process_input_stack();
5852 if (while_break_flag || input_stack::is_return_boundary()) {
5853 while_break_flag = 0;
5854 break;
5855 }
5856 }
5857 input_stack::remove_boundary();
5858 while_depth--;
5859 }
5860 tok.next();
5861}
5862
5863void while_break_request()
5864{
5865 if (!while_depth) {
5866 error("no while loop");
5867 skip_line();
5868 }
5869 else {
5870 while_break_flag = 1;
5871 while (input_stack::get(0) != EOF)
5872 ;
5873 tok.next();
5874 }
5875}
5876
5877void while_continue_request()
5878{
5879 if (!while_depth) {
5880 error("no while loop");
5881 skip_line();
5882 }
5883 else {
5884 while (input_stack::get(0) != EOF)
5885 ;
5886 tok.next();
5887 }
5888}
5889
5890// .so
5891
5892void source()
5893{
5894 symbol nm = get_long_name(1);
5895 if (nm.is_null())
5896 skip_line();
5897 else {
5898 while (!tok.newline() && !tok.eof())
5899 tok.next();
5900 errno = 0;
5901 FILE *fp = include_search_path.open_file_cautious(nm.contents());
5902 if (fp)
5903 input_stack::push(new file_iterator(fp, nm.contents()));
5904 else
5905 error("can't open `%1': %2", nm.contents(), strerror(errno));
5906 tok.next();
5907 }
5908}
5909
5910// like .so but use popen()
5911
5912void pipe_source()
5913{
4d3e9548 5914 if (!unsafe_flag) {
92d0a6a6
JR
5915 error(".pso request not allowed in safer mode");
5916 skip_line();
5917 }
5918 else {
5919#ifdef POPEN_MISSING
5920 error("pipes not available on this system");
5921 skip_line();
5922#else /* not POPEN_MISSING */
5923 if (tok.newline() || tok.eof())
5924 error("missing command");
5925 else {
5926 int c;
5927 while ((c = get_copy(0)) == ' ' || c == '\t')
5928 ;
5929 int buf_size = 24;
5930 char *buf = new char[buf_size];
5931 int buf_used = 0;
5932 for (; c != '\n' && c != EOF; c = get_copy(0)) {
5933 const char *s = asciify(c);
5934 int slen = strlen(s);
5935 if (buf_used + slen + 1> buf_size) {
5936 char *old_buf = buf;
5937 int old_buf_size = buf_size;
5938 buf_size *= 2;
5939 buf = new char[buf_size];
5940 memcpy(buf, old_buf, old_buf_size);
5941 a_delete old_buf;
5942 }
5943 strcpy(buf + buf_used, s);
5944 buf_used += slen;
5945 }
5946 buf[buf_used] = '\0';
5947 errno = 0;
5948 FILE *fp = popen(buf, POPEN_RT);
5949 if (fp)
5950 input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
5951 else
5952 error("can't open pipe to process `%1': %2", buf, strerror(errno));
5953 a_delete buf;
5954 }
5955 tok.next();
5956#endif /* not POPEN_MISSING */
5957 }
5958}
5959
5960// .psbb
5961
5962static int llx_reg_contents = 0;
5963static int lly_reg_contents = 0;
5964static int urx_reg_contents = 0;
5965static int ury_reg_contents = 0;
5966
5967struct bounding_box {
5968 int llx, lly, urx, ury;
5969};
5970
5971/* Parse the argument to a %%BoundingBox comment. Return 1 if it
5972contains 4 numbers, 2 if it contains (atend), 0 otherwise. */
5973
5974int parse_bounding_box(char *p, bounding_box *bb)
5975{
5976 if (sscanf(p, "%d %d %d %d",
5977 &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4)
5978 return 1;
5979 else {
5980 /* The Document Structuring Conventions say that the numbers
5981 should be integers. Unfortunately some broken applications
5982 get this wrong. */
5983 double x1, x2, x3, x4;
5984 if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
5985 bb->llx = (int)x1;
5986 bb->lly = (int)x2;
5987 bb->urx = (int)x3;
5988 bb->ury = (int)x4;
5989 return 1;
5990 }
5991 else {
5992 for (; *p == ' ' || *p == '\t'; p++)
5993 ;
5994 if (strncmp(p, "(atend)", 7) == 0) {
5995 return 2;
5996 }
5997 }
5998 }
5999 bb->llx = bb->lly = bb->urx = bb->ury = 0;
6000 return 0;
6001}
6002
6003// This version is taken from psrm.cpp
6004
6005#define PS_LINE_MAX 255
6006cset white_space("\n\r \t");
6007
6008int ps_get_line(char *buf, FILE *fp, const char* filename)
6009{
6010 int c = getc(fp);
6011 if (c == EOF) {
6012 buf[0] = '\0';
6013 return 0;
6014 }
6015 int i = 0;
6016 int err = 0;
6017 while (c != '\r' && c != '\n' && c != EOF) {
6018 if ((c < 0x1b && !white_space(c)) || c == 0x7f)
6019 error("invalid input character code %1 in `%2'", int(c), filename);
6020 else if (i < PS_LINE_MAX)
6021 buf[i++] = c;
6022 else if (!err) {
6023 err = 1;
6024 error("PostScript file `%1' is non-conforming "
6025 "because length of line exceeds 255", filename);
6026 }
6027 c = getc(fp);
6028 }
6029 buf[i++] = '\n';
6030 buf[i] = '\0';
6031 if (c == '\r') {
6032 c = getc(fp);
6033 if (c != EOF && c != '\n')
6034 ungetc(c, fp);
6035 }
6036 return 1;
6037}
6038
6039inline void assign_registers(int llx, int lly, int urx, int ury)
6040{
6041 llx_reg_contents = llx;
6042 lly_reg_contents = lly;
6043 urx_reg_contents = urx;
6044 ury_reg_contents = ury;
6045}
6046
6047void do_ps_file(FILE *fp, const char* filename)
6048{
6049 bounding_box bb;
6050 int bb_at_end = 0;
6051 char buf[PS_LINE_MAX];
6052 llx_reg_contents = lly_reg_contents =
6053 urx_reg_contents = ury_reg_contents = 0;
6054 if (!ps_get_line(buf, fp, filename)) {
6055 error("`%1' is empty", filename);
6056 return;
6057 }
6058 if (strncmp("%!PS-Adobe-", buf, 11) != 0) {
6059 error("`%1' is not conforming to the Document Structuring Conventions",
6060 filename);
6061 return;
6062 }
6063 while (ps_get_line(buf, fp, filename) != 0) {
4d3e9548
JL
6064 // in header comments, `%X' (`X' any printable character except
6065 // whitespace) is possible too
6066 if (buf[0] == '%') {
6067 if (strncmp(buf + 1, "%EndComments", 12) == 0)
6068 break;
6069 if (white_space(buf[1]))
6070 break;
6071 }
6072 else
92d0a6a6 6073 break;
4d3e9548 6074 if (strncmp(buf + 1, "%BoundingBox:", 13) == 0) {
92d0a6a6
JR
6075 int res = parse_bounding_box(buf + 14, &bb);
6076 if (res == 1) {
6077 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6078 return;
6079 }
6080 else if (res == 2) {
6081 bb_at_end = 1;
6082 break;
6083 }
6084 else {
6085 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6086 filename);
6087 return;
6088 }
6089 }
6090 }
6091 if (bb_at_end) {
6092 long offset;
6093 int last_try = 0;
4d3e9548 6094 // in the trailer, the last BoundingBox comment is significant
92d0a6a6
JR
6095 for (offset = 512; !last_try; offset *= 2) {
6096 int had_trailer = 0;
6097 int got_bb = 0;
6098 if (offset > 32768 || fseek(fp, -offset, 2) == -1) {
6099 last_try = 1;
6100 if (fseek(fp, 0L, 0) == -1)
6101 break;
6102 }
6103 while (ps_get_line(buf, fp, filename) != 0) {
6104 if (buf[0] == '%' && buf[1] == '%') {
6105 if (!had_trailer) {
6106 if (strncmp(buf + 2, "Trailer", 7) == 0)
6107 had_trailer = 1;
6108 }
6109 else {
6110 if (strncmp(buf + 2, "BoundingBox:", 12) == 0) {
6111 int res = parse_bounding_box(buf + 14, &bb);
6112 if (res == 1)
6113 got_bb = 1;
6114 else if (res == 2) {
6115 error("`(atend)' not allowed in trailer of `%1'", filename);
6116 return;
6117 }
6118 else {
6119 error("the arguments to the %%%%BoundingBox comment in `%1' are bad",
6120 filename);
6121 return;
6122 }
6123 }
6124 }
6125 }
6126 }
6127 if (got_bb) {
6128 assign_registers(bb.llx, bb.lly, bb.urx, bb.ury);
6129 return;
6130 }
6131 }
6132 }
6133 error("%%%%BoundingBox comment not found in `%1'", filename);
6134}
6135
6136void ps_bbox_request()
6137{
6138 symbol nm = get_long_name(1);
6139 if (nm.is_null())
6140 skip_line();
6141 else {
6142 while (!tok.newline() && !tok.eof())
6143 tok.next();
6144 errno = 0;
6145 // PS files might contain non-printable characters, such as ^Z
6146 // and CRs not followed by an LF, so open them in binary mode.
6147 FILE *fp = include_search_path.open_file_cautious(nm.contents(),
6148 0, FOPEN_RB);
6149 if (fp) {
6150 do_ps_file(fp, nm.contents());
6151 fclose(fp);
6152 }
6153 else
6154 error("can't open `%1': %2", nm.contents(), strerror(errno));
6155 tok.next();
6156 }
6157}
6158
6159const char *asciify(int c)
6160{
6161 static char buf[3];
6162 buf[0] = escape_char == '\0' ? '\\' : escape_char;
6163 buf[1] = buf[2] = '\0';
6164 switch (c) {
6165 case ESCAPE_QUESTION:
6166 buf[1] = '?';
6167 break;
6168 case ESCAPE_AMPERSAND:
6169 buf[1] = '&';
6170 break;
6171 case ESCAPE_RIGHT_PARENTHESIS:
6172 buf[1] = ')';
6173 break;
6174 case ESCAPE_UNDERSCORE:
6175 buf[1] = '_';
6176 break;
6177 case ESCAPE_BAR:
6178 buf[1] = '|';
6179 break;
6180 case ESCAPE_CIRCUMFLEX:
6181 buf[1] = '^';
6182 break;
6183 case ESCAPE_LEFT_BRACE:
6184 buf[1] = '{';
6185 break;
6186 case ESCAPE_RIGHT_BRACE:
6187 buf[1] = '}';
6188 break;
6189 case ESCAPE_LEFT_QUOTE:
6190 buf[1] = '`';
6191 break;
6192 case ESCAPE_RIGHT_QUOTE:
6193 buf[1] = '\'';
6194 break;
6195 case ESCAPE_HYPHEN:
6196 buf[1] = '-';
6197 break;
6198 case ESCAPE_BANG:
6199 buf[1] = '!';
6200 break;
6201 case ESCAPE_c:
6202 buf[1] = 'c';
6203 break;
6204 case ESCAPE_e:
6205 buf[1] = 'e';
6206 break;
6207 case ESCAPE_E:
6208 buf[1] = 'E';
6209 break;
6210 case ESCAPE_PERCENT:
6211 buf[1] = '%';
6212 break;
6213 case ESCAPE_SPACE:
6214 buf[1] = ' ';
6215 break;
6216 case ESCAPE_TILDE:
6217 buf[1] = '~';
6218 break;
6219 case ESCAPE_COLON:
6220 buf[1] = ':';
6221 break;
465b256c
JR
6222 case PUSH_GROFF_MODE:
6223 case PUSH_COMP_MODE:
6224 case POP_GROFFCOMP_MODE:
92d0a6a6
JR
6225 buf[0] = '\0';
6226 break;
6227 default:
6228 if (invalid_input_char(c))
6229 buf[0] = '\0';
6230 else
6231 buf[0] = c;
6232 break;
6233 }
6234 return buf;
6235}
6236
6237const char *input_char_description(int c)
6238{
6239 switch (c) {
6240 case '\n':
6241 return "a newline character";
6242 case '\b':
6243 return "a backspace character";
6244 case '\001':
6245 return "a leader character";
6246 case '\t':
6247 return "a tab character";
6248 case ' ':
6249 return "a space character";
6250 case '\0':
6251 return "a node";
6252 }
6253 static char buf[sizeof("magic character code ") + 1 + INT_DIGITS];
6254 if (invalid_input_char(c)) {
6255 const char *s = asciify(c);
6256 if (*s) {
6257 buf[0] = '`';
6258 strcpy(buf + 1, s);
6259 strcat(buf, "'");
6260 return buf;
6261 }
6262 sprintf(buf, "magic character code %d", c);
6263 return buf;
6264 }
6265 if (csprint(c)) {
6266 buf[0] = '`';
6267 buf[1] = c;
6268 buf[2] = '\'';
6269 return buf;
6270 }
6271 sprintf(buf, "character code %d", c);
6272 return buf;
6273}
6274
465b256c
JR
6275void tag()
6276{
6277 if (!tok.newline() && !tok.eof()) {
6278 string s;
6279 int c;
6280 for (;;) {
6281 c = get_copy(0);
6282 if (c == '"') {
6283 c = get_copy(0);
6284 break;
6285 }
6286 if (c != ' ' && c != '\t')
6287 break;
6288 }
6289 s = "x X ";
6290 for (; c != '\n' && c != EOF; c = get_copy(0))
6291 s += (char)c;
6292 s += '\n';
6293 curenv->add_node(new tag_node(s, 0));
6294 }
6295 tok.next();
6296}
6297
6298void taga()
6299{
6300 if (!tok.newline() && !tok.eof()) {
6301 string s;
6302 int c;
6303 for (;;) {
6304 c = get_copy(0);
6305 if (c == '"') {
6306 c = get_copy(0);
6307 break;
6308 }
6309 if (c != ' ' && c != '\t')
6310 break;
6311 }
6312 s = "x X ";
6313 for (; c != '\n' && c != EOF; c = get_copy(0))
6314 s += (char)c;
6315 s += '\n';
6316 curenv->add_node(new tag_node(s, 1));
6317 }
6318 tok.next();
6319}
6320
92d0a6a6
JR
6321// .tm, .tm1, and .tmc
6322
6323void do_terminal(int newline, int string_like)
6324{
6325 if (!tok.newline() && !tok.eof()) {
6326 int c;
6327 for (;;) {
6328 c = get_copy(0);
6329 if (string_like && c == '"') {
6330 c = get_copy(0);
6331 break;
6332 }
6333 if (c != ' ' && c != '\t')
6334 break;
6335 }
6336 for (; c != '\n' && c != EOF; c = get_copy(0))
6337 fputs(asciify(c), stderr);
6338 }
6339 if (newline)
6340 fputc('\n', stderr);
6341 fflush(stderr);
6342 tok.next();
6343}
6344
6345void terminal()
6346{
6347 do_terminal(1, 0);
6348}
6349
6350void terminal1()
6351{
6352 do_terminal(1, 1);
6353}
6354
6355void terminal_continue()
6356{
6357 do_terminal(0, 1);
6358}
6359
6360dictionary stream_dictionary(20);
6361
6362void do_open(int append)
6363{
6364 symbol stream = get_name(1);
6365 if (!stream.is_null()) {
6366 symbol filename = get_long_name(1);
6367 if (!filename.is_null()) {
6368 errno = 0;
6369 FILE *fp = fopen(filename.contents(), append ? "a" : "w");
6370 if (!fp) {
6371 error("can't open `%1' for %2: %3",
6372 filename.contents(),
6373 append ? "appending" : "writing",
6374 strerror(errno));
6375 fp = (FILE *)stream_dictionary.remove(stream);
6376 }
6377 else
6378 fp = (FILE *)stream_dictionary.lookup(stream, fp);
6379 if (fp)
6380 fclose(fp);
6381 }
6382 }
6383 skip_line();
6384}
6385
6386void open_request()
6387{
4d3e9548 6388 if (!unsafe_flag) {
92d0a6a6
JR
6389 error(".open request not allowed in safer mode");
6390 skip_line();
6391 }
6392 else
6393 do_open(0);
6394}
6395
6396void opena_request()
6397{
4d3e9548 6398 if (!unsafe_flag) {
92d0a6a6
JR
6399 error(".opena request not allowed in safer mode");
6400 skip_line();
6401 }
6402 else
6403 do_open(1);
6404}
6405
6406void close_request()
6407{
6408 symbol stream = get_name(1);
6409 if (!stream.is_null()) {
6410 FILE *fp = (FILE *)stream_dictionary.remove(stream);
6411 if (!fp)
6412 error("no stream named `%1'", stream.contents());
6413 else
6414 fclose(fp);
6415 }
6416 skip_line();
6417}
6418
6419// .write and .writec
6420
6421void do_write_request(int newline)
6422{
6423 symbol stream = get_name(1);
6424 if (stream.is_null()) {
6425 skip_line();
6426 return;
6427 }
6428 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6429 if (!fp) {
6430 error("no stream named `%1'", stream.contents());
6431 skip_line();
6432 return;
6433 }
6434 int c;
6435 while ((c = get_copy(0)) == ' ')
6436 ;
6437 if (c == '"')
6438 c = get_copy(0);
6439 for (; c != '\n' && c != EOF; c = get_copy(0))
6440 fputs(asciify(c), fp);
6441 if (newline)
6442 fputc('\n', fp);
6443 fflush(fp);
6444 tok.next();
6445}
6446
6447void write_request()
6448{
6449 do_write_request(1);
6450}
6451
6452void write_request_continue()
6453{
6454 do_write_request(0);
6455}
6456
6457void write_macro_request()
6458{
6459 symbol stream = get_name(1);
6460 if (stream.is_null()) {
6461 skip_line();
6462 return;
6463 }
6464 FILE *fp = (FILE *)stream_dictionary.lookup(stream);
6465 if (!fp) {
6466 error("no stream named `%1'", stream.contents());
6467 skip_line();
6468 return;
6469 }
6470 symbol s = get_name(1);
6471 if (s.is_null()) {
6472 skip_line();
6473 return;
6474 }
6475 request_or_macro *p = lookup_request(s);
6476 macro *m = p->to_macro();
6477 if (!m)
6478 error("cannot write request");
6479 else {
6480 string_iterator iter(*m);
6481 for (;;) {
6482 int c = iter.get(0);
6483 if (c == EOF)
6484 break;
6485 fputs(asciify(c), fp);
6486 }
6487 fflush(fp);
6488 }
6489 skip_line();
6490}
6491
6492void warnscale_request()
6493{
6494 if (has_arg()) {
6495 char c = tok.ch();
6496 if (c == 'u')
6497 warn_scale = 1.0;
6498 else if (c == 'i')
6499 warn_scale = (double)units_per_inch;
6500 else if (c == 'c')
6501 warn_scale = (double)units_per_inch / 2.54;
6502 else if (c == 'p')
6503 warn_scale = (double)units_per_inch / 72.0;
6504 else if (c == 'P')
6505 warn_scale = (double)units_per_inch / 6.0;
6506 else {
6507 warning(WARN_SCALE,
6508 "invalid scaling indicator `%1', using `i' instead", c);
6509 c = 'i';
6510 }
6511 warn_scaling_indicator = c;
6512 }
6513 skip_line();
6514}
6515
6516void spreadwarn_request()
6517{
6518 hunits n;
6519 if (has_arg() && get_hunits(&n, 'm')) {
6520 if (n < 0)
6521 n = 0;
6522 hunits em = curenv->get_size();
6523 spread_limit = (double)n.to_units()
6524 / (em.is_zero() ? hresolution : em.to_units());
6525 }
6526 else
6527 spread_limit = -spread_limit - 1; // no arg toggles on/off without
6528 // changing value; we mirror at
6529 // -0.5 to make zero a valid value
6530 skip_line();
6531}
6532
6533static void init_charset_table()
6534{
6535 char buf[16];
6536 strcpy(buf, "char");
6537 for (int i = 0; i < 256; i++) {
6538 strcpy(buf + 4, i_to_a(i));
6539 charset_table[i] = get_charinfo(symbol(buf));
6540 charset_table[i]->set_ascii_code(i);
6541 if (csalpha(i))
6542 charset_table[i]->set_hyphenation_code(cmlower(i));
6543 }
6544 charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
6545 charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
6546 charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
6547 charset_table['-']->set_flags(charinfo::BREAK_AFTER);
6548 charset_table['"']->set_flags(charinfo::TRANSPARENT);
6549 charset_table['\'']->set_flags(charinfo::TRANSPARENT);
6550 charset_table[')']->set_flags(charinfo::TRANSPARENT);
6551 charset_table[']']->set_flags(charinfo::TRANSPARENT);
6552 charset_table['*']->set_flags(charinfo::TRANSPARENT);
6553 get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
6554 get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
6555 get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
4d3e9548 6556 get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
92d0a6a6
JR
6557 get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6558 get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6559 get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6560 get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6561 get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
6562 get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
6563 page_character = charset_table['%'];
6564}
6565
6566static void init_hpf_code_table()
6567{
6568 for (int i = 0; i < 256; i++)
6569 hpf_code_table[i] = i;
6570}
6571
6572static void do_translate(int translate_transparent, int translate_input)
6573{
6574 tok.skip();
6575 while (!tok.newline() && !tok.eof()) {
6576 if (tok.space()) {
6577 // This is a really bizarre troff feature.
6578 tok.next();
6579 translate_space_to_dummy = tok.dummy();
6580 if (tok.newline() || tok.eof())
6581 break;
6582 tok.next();
6583 continue;
6584 }
6585 charinfo *ci1 = tok.get_char(1);
6586 if (ci1 == 0)
6587 break;
6588 tok.next();
6589 if (tok.newline() || tok.eof()) {
6590 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6591 translate_transparent);
6592 break;
6593 }
6594 if (tok.space())
6595 ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
6596 translate_transparent);
6597 else if (tok.stretchable_space())
6598 ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
6599 translate_transparent);
6600 else if (tok.dummy())
6601 ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
6602 translate_transparent);
6603 else if (tok.hyphen_indicator())
6604 ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
6605 translate_transparent);
6606 else {
6607 charinfo *ci2 = tok.get_char(1);
6608 if (ci2 == 0)
6609 break;
6610 if (ci1 == ci2)
6611 ci1->set_translation(0, translate_transparent, translate_input);
6612 else
6613 ci1->set_translation(ci2, translate_transparent, translate_input);
6614 }
6615 tok.next();
6616 }
6617 skip_line();
6618}
6619
6620void translate()
6621{
6622 do_translate(1, 0);
6623}
6624
6625void translate_no_transparent()
6626{
6627 do_translate(0, 0);
6628}
6629
6630void translate_input()
6631{
6632 do_translate(1, 1);
6633}
6634
6635void char_flags()
6636{
6637 int flags;
6638 if (get_integer(&flags))
6639 while (has_arg()) {
6640 charinfo *ci = tok.get_char(1);
6641 if (ci) {
6642 charinfo *tem = ci->get_translation();
6643 if (tem)
6644 ci = tem;
6645 ci->set_flags(flags);
6646 }
6647 tok.next();
6648 }
6649 skip_line();
6650}
6651
6652void hyphenation_code()
6653{
6654 tok.skip();
6655 while (!tok.newline() && !tok.eof()) {
6656 charinfo *ci = tok.get_char(1);
6657 if (ci == 0)
6658 break;
6659 tok.next();
6660 tok.skip();
6661 unsigned char c = tok.ch();
6662 if (c == 0) {
6663 error("hyphenation code must be ordinary character");
6664 break;
6665 }
6666 if (csdigit(c)) {
6667 error("hyphenation code cannot be digit");
6668 break;
6669 }
6670 ci->set_hyphenation_code(c);
6671 if (ci->get_translation()
6672 && ci->get_translation()->get_translation_input())
6673 ci->get_translation()->set_hyphenation_code(c);
6674 tok.next();
6675 tok.skip();
6676 }
6677 skip_line();
6678}
6679
6680void hyphenation_patterns_file_code()
6681{
6682 tok.skip();
6683 while (!tok.newline() && !tok.eof()) {
6684 int n1, n2;
6685 if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
6686 if (!has_arg()) {
6687 error("missing output hyphenation code");
6688 break;
6689 }
6690 if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
6691 hpf_code_table[n1] = n2;
6692 tok.skip();
6693 }
6694 else {
6695 error("output hyphenation code must be integer in the range 0..255");
6696 break;
6697 }
6698 }
6699 else {
6700 error("input hyphenation code must be integer in the range 0..255");
6701 break;
6702 }
6703 }
6704 skip_line();
6705}
6706
6707charinfo *token::get_char(int required)
6708{
6709 if (type == TOKEN_CHAR)
6710 return charset_table[c];
6711 if (type == TOKEN_SPECIAL)
6712 return get_charinfo(nm);
6713 if (type == TOKEN_NUMBERED_CHAR)
6714 return get_charinfo_by_number(val);
6715 if (type == TOKEN_ESCAPE) {
6716 if (escape_char != 0)
6717 return charset_table[escape_char];
6718 else {
6719 error("`\\e' used while no current escape character");
6720 return 0;
6721 }
6722 }
6723 if (required) {
6724 if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
6725 warning(WARN_MISSING, "missing normal or special character");
6726 else
6727 error("normal or special character expected (got %1)", description());
6728 }
6729 return 0;
6730}
6731
6732charinfo *get_optional_char()
6733{
6734 while (tok.space())
6735 tok.next();
6736 charinfo *ci = tok.get_char();
6737 if (!ci)
6738 check_missing_character();
6739 else
6740 tok.next();
6741 return ci;
6742}
6743
6744void check_missing_character()
6745{
6746 if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab())
6747 error("normal or special character expected (got %1): "
6748 "treated as missing",
6749 tok.description());
6750}
6751
6752// this is for \Z
6753
6754int token::add_to_node_list(node **pp)
6755{
6756 hunits w;
6757 int s;
6758 node *n = 0;
6759 switch (type) {
6760 case TOKEN_CHAR:
6761 *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
6762 break;
6763 case TOKEN_DUMMY:
6764 n = new dummy_node;
6765 break;
6766 case TOKEN_ESCAPE:
6767 if (escape_char != 0)
6768 *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
6769 break;
6770 case TOKEN_HYPHEN_INDICATOR:
6771 *pp = (*pp)->add_discretionary_hyphen();
6772 break;
6773 case TOKEN_ITALIC_CORRECTION:
6774 *pp = (*pp)->add_italic_correction(&w);
6775 break;
6776 case TOKEN_LEFT_BRACE:
6777 break;
6778 case TOKEN_MARK_INPUT:
6779 set_number_reg(nm, curenv->get_input_line_position().to_units());
6780 break;
6781 case TOKEN_NODE:
6782 n = nd;
6783 nd = 0;
6784 break;
6785 case TOKEN_NUMBERED_CHAR:
6786 *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
6787 break;
6788 case TOKEN_RIGHT_BRACE:
6789 break;
6790 case TOKEN_SPACE:
6791 n = new hmotion_node(curenv->get_space_width(),
6792 curenv->get_fill_color());
6793 break;
6794 case TOKEN_SPECIAL:
6795 *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
6796 break;
6797 case TOKEN_STRETCHABLE_SPACE:
6798 n = new unbreakable_space_node(curenv->get_space_width(),
6799 curenv->get_fill_color());
6800 break;
6801 case TOKEN_UNSTRETCHABLE_SPACE:
6802 n = new space_char_hmotion_node(curenv->get_space_width(),
6803 curenv->get_fill_color());
6804 break;
6805 case TOKEN_TRANSPARENT_DUMMY:
6806 n = new transparent_dummy_node;
6807 break;
6808 case TOKEN_ZERO_WIDTH_BREAK:
6809 n = new space_node(H0, curenv->get_fill_color());
6810 n->freeze_space();
6811 n->is_escape_colon();
6812 break;
6813 default:
6814 return 0;
6815 }
6816 if (n) {
6817 n->next = *pp;
6818 *pp = n;
6819 }
6820 return 1;
6821}
6822
6823void token::process()
6824{
6825 if (possibly_handle_first_page_transition())
6826 return;
6827 switch (type) {
6828 case TOKEN_BACKSPACE:
6829 curenv->add_node(new hmotion_node(-curenv->get_space_width(),
6830 curenv->get_fill_color()));
6831 break;
6832 case TOKEN_CHAR:
6833 curenv->add_char(charset_table[c]);
6834 break;
6835 case TOKEN_DUMMY:
6836 curenv->add_node(new dummy_node);
6837 break;
6838 case TOKEN_EMPTY:
6839 assert(0);
6840 break;
6841 case TOKEN_EOF:
6842 assert(0);
6843 break;
6844 case TOKEN_ESCAPE:
6845 if (escape_char != 0)
6846 curenv->add_char(charset_table[escape_char]);
6847 break;
6848 case TOKEN_BEGIN_TRAP:
6849 case TOKEN_END_TRAP:
6850 case TOKEN_PAGE_EJECTOR:
6851 // these are all handled in process_input_stack()
6852 break;
6853 case TOKEN_HYPHEN_INDICATOR:
6854 curenv->add_hyphen_indicator();
6855 break;
6856 case TOKEN_INTERRUPT:
6857 curenv->interrupt();
6858 break;
6859 case TOKEN_ITALIC_CORRECTION:
6860 curenv->add_italic_correction();
6861 break;
6862 case TOKEN_LEADER:
6863 curenv->handle_tab(1);
6864 break;
6865 case TOKEN_LEFT_BRACE:
6866 break;
6867 case TOKEN_MARK_INPUT:
6868 set_number_reg(nm, curenv->get_input_line_position().to_units());
6869 break;
6870 case TOKEN_NEWLINE:
6871 curenv->newline();
6872 break;
6873 case TOKEN_NODE:
6874 curenv->add_node(nd);
6875 nd = 0;
6876 break;
6877 case TOKEN_NUMBERED_CHAR:
6878 curenv->add_char(get_charinfo_by_number(val));
6879 break;
6880 case TOKEN_REQUEST:
6881 // handled in process_input_stack()
6882 break;
6883 case TOKEN_RIGHT_BRACE:
6884 break;
6885 case TOKEN_SPACE:
6886 curenv->space();
6887 break;
6888 case TOKEN_SPECIAL:
6889 curenv->add_char(get_charinfo(nm));
6890 break;
6891 case TOKEN_SPREAD:
6892 curenv->spread();
6893 break;
6894 case TOKEN_STRETCHABLE_SPACE:
6895 curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
6896 curenv->get_fill_color()));
6897 break;
6898 case TOKEN_UNSTRETCHABLE_SPACE:
6899 curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
6900 curenv->get_fill_color()));
6901 break;
6902 case TOKEN_TAB:
6903 curenv->handle_tab(0);
6904 break;
6905 case TOKEN_TRANSPARENT:
6906 break;
6907 case TOKEN_TRANSPARENT_DUMMY:
6908 curenv->add_node(new transparent_dummy_node);
6909 break;
6910 case TOKEN_ZERO_WIDTH_BREAK:
6911 {
6912 node *tmp = new space_node(H0, curenv->get_fill_color());
6913 tmp->freeze_space();
6914 tmp->is_escape_colon();
6915 curenv->add_node(tmp);
6916 break;
6917 }
6918 default:
6919 assert(0);
6920 }
6921}
6922
6923class nargs_reg : public reg {
6924public:
6925 const char *get_string();
6926};
6927
6928const char *nargs_reg::get_string()
6929{
6930 return i_to_a(input_stack::nargs());
6931}
6932
6933class lineno_reg : public reg {
6934public:
6935 const char *get_string();
6936};
6937
6938const char *lineno_reg::get_string()
6939{
6940 int line;
6941 const char *file;
6942 if (!input_stack::get_location(0, &file, &line))
6943 line = 0;
6944 return i_to_a(line);
6945}
6946
6947class writable_lineno_reg : public general_reg {
6948public:
6949 writable_lineno_reg();
6950 void set_value(units);
6951 int get_value(units *);
6952};
6953
6954writable_lineno_reg::writable_lineno_reg()
6955{
6956}
6957
6958int writable_lineno_reg::get_value(units *res)
6959{
6960 int line;
6961 const char *file;
6962 if (!input_stack::get_location(0, &file, &line))
6963 return 0;
6964 *res = line;
6965 return 1;
6966}
6967
6968void writable_lineno_reg::set_value(units n)
6969{
6970 input_stack::set_location(0, n);
6971}
6972
6973class filename_reg : public reg {
6974public:
6975 const char *get_string();
6976};
6977
6978const char *filename_reg::get_string()
6979{
6980 int line;
6981 const char *file;
6982 if (input_stack::get_location(0, &file, &line))
6983 return file;
6984 else
6985 return 0;
6986}
6987
4d3e9548
JL
6988class break_flag_reg : public reg {
6989public:
6990 const char *get_string();
6991};
6992
6993const char *break_flag_reg::get_string()
6994{
6995 return i_to_a(input_stack::get_break_flag());
6996}
6997
92d0a6a6
JR
6998class constant_reg : public reg {
6999 const char *s;
7000public:
7001 constant_reg(const char *);
7002 const char *get_string();
7003};
7004
7005constant_reg::constant_reg(const char *p) : s(p)
7006{
7007}
7008
7009const char *constant_reg::get_string()
7010{
7011 return s;
7012}
7013
7014constant_int_reg::constant_int_reg(int *q) : p(q)
7015{
7016}
7017
7018const char *constant_int_reg::get_string()
7019{
7020 return i_to_a(*p);
7021}
7022
7023void abort_request()
7024{
7025 int c;
7026 if (tok.eof())
7027 c = EOF;
7028 else if (tok.newline())
7029 c = '\n';
7030 else {
7031 while ((c = get_copy(0)) == ' ')
7032 ;
7033 }
7034 if (c == EOF || c == '\n')
7035 fputs("User Abort.", stderr);
7036 else {
7037 for (; c != '\n' && c != EOF; c = get_copy(0))
7038 fputs(asciify(c), stderr);
7039 }
7040 fputc('\n', stderr);
7041 cleanup_and_exit(1);
7042}
7043
7044char *read_string()
7045{
7046 int len = 256;
7047 char *s = new char[len];
7048 int c;
7049 while ((c = get_copy(0)) == ' ')
7050 ;
7051 int i = 0;
7052 while (c != '\n' && c != EOF) {
7053 if (!invalid_input_char(c)) {
7054 if (i + 2 > len) {
7055 char *tem = s;
7056 s = new char[len*2];
7057 memcpy(s, tem, len);
7058 len *= 2;
7059 a_delete tem;
7060 }
7061 s[i++] = c;
7062 }
7063 c = get_copy(0);
7064 }
7065 s[i] = '\0';
7066 tok.next();
7067 if (i == 0) {
7068 a_delete s;
7069 return 0;
7070 }
7071 return s;
7072}
7073
7074void pipe_output()
7075{
4d3e9548 7076 if (!unsafe_flag) {
92d0a6a6
JR
7077 error(".pi request not allowed in safer mode");
7078 skip_line();
7079 }
7080 else {
7081#ifdef POPEN_MISSING
7082 error("pipes not available on this system");
7083 skip_line();
7084#else /* not POPEN_MISSING */
7085 if (the_output) {
7086 error("can't pipe: output already started");
7087 skip_line();
7088 }
7089 else {
7090 char *pc;
7091 if ((pc = read_string()) == 0)
7092 error("can't pipe to empty command");
7093 if (pipe_command) {
7094 char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
7095 strcpy(s, pipe_command);
7096 strcat(s, "|");
7097 strcat(s, pc);
7098 a_delete pipe_command;
7099 a_delete pc;
7100 pipe_command = s;
7101 }
7102 else
4d3e9548 7103 pipe_command = pc;
92d0a6a6
JR
7104 }
7105#endif /* not POPEN_MISSING */
7106 }
7107}
7108
7109static int system_status;
7110
7111void system_request()
7112{
4d3e9548 7113 if (!unsafe_flag) {
92d0a6a6
JR
7114 error(".sy request not allowed in safer mode");
7115 skip_line();
7116 }
7117 else {
7118 char *command = read_string();
7119 if (!command)
7120 error("empty command");
7121 else {
7122 system_status = system(command);
7123 a_delete command;
7124 }
7125 }
7126}
7127
7128void copy_file()
7129{
7130 if (curdiv == topdiv && topdiv->before_first_page) {
7131 handle_initial_request(COPY_FILE_REQUEST);
7132 return;
7133 }
7134 symbol filename = get_long_name(1);
7135 while (!tok.newline() && !tok.eof())
7136 tok.next();
7137 if (break_flag)
7138 curenv->do_break();
7139 if (!filename.is_null())
7140 curdiv->copy_file(filename.contents());
7141 tok.next();
7142}
7143
7144#ifdef COLUMN
7145
7146void vjustify()
7147{
7148 if (curdiv == topdiv && topdiv->before_first_page) {
7149 handle_initial_request(VJUSTIFY_REQUEST);
7150 return;
7151 }
7152 symbol type = get_long_name(1);
7153 if (!type.is_null())
7154 curdiv->vjustify(type);
7155 skip_line();
7156}
7157
7158#endif /* COLUMN */
7159
7160void transparent_file()
7161{
7162 if (curdiv == topdiv && topdiv->before_first_page) {
7163 handle_initial_request(TRANSPARENT_FILE_REQUEST);
7164 return;
7165 }
7166 symbol filename = get_long_name(1);
7167 while (!tok.newline() && !tok.eof())
7168 tok.next();
7169 if (break_flag)
7170 curenv->do_break();
7171 if (!filename.is_null()) {
7172 errno = 0;
7173 FILE *fp = include_search_path.open_file_cautious(filename.contents());
7174 if (!fp)
7175 error("can't open `%1': %2", filename.contents(), strerror(errno));
7176 else {
7177 int bol = 1;
7178 for (;;) {
7179 int c = getc(fp);
7180 if (c == EOF)
7181 break;
7182 if (invalid_input_char(c))
7183 warning(WARN_INPUT, "invalid input character code %1", int(c));
7184 else {
7185 curdiv->transparent_output(c);
7186 bol = c == '\n';
7187 }
7188 }
7189 if (!bol)
7190 curdiv->transparent_output('\n');
7191 fclose(fp);
7192 }
7193 }
7194 tok.next();
7195}
7196
7197class page_range {
7198 int first;
7199 int last;
7200public:
7201 page_range *next;
7202 page_range(int, int, page_range *);
7203 int contains(int n);
7204};
7205
7206page_range::page_range(int i, int j, page_range *p)
7207: first(i), last(j), next(p)
7208{
7209}
7210
7211int page_range::contains(int n)
7212{
7213 return n >= first && (last <= 0 || n <= last);
7214}
7215
7216page_range *output_page_list = 0;
7217
7218int in_output_page_list(int n)
7219{
7220 if (!output_page_list)
7221 return 1;
7222 for (page_range *p = output_page_list; p; p = p->next)
7223 if (p->contains(n))
7224 return 1;
7225 return 0;
7226}
7227
7228static void parse_output_page_list(char *p)
7229{
7230 for (;;) {
7231 int i;
7232 if (*p == '-')
7233 i = 1;
7234 else if (csdigit(*p)) {
7235 i = 0;
7236 do
7237 i = i*10 + *p++ - '0';
7238 while (csdigit(*p));
7239 }
7240 else
7241 break;
7242 int j;
7243 if (*p == '-') {
7244 p++;
7245 j = 0;
7246 if (csdigit(*p)) {
7247 do
7248 j = j*10 + *p++ - '0';
7249 while (csdigit(*p));
7250 }
7251 }
7252 else
7253 j = i;
7254 if (j == 0)
7255 last_page_number = -1;
7256 else if (last_page_number >= 0 && j > last_page_number)
7257 last_page_number = j;
7258 output_page_list = new page_range(i, j, output_page_list);
7259 if (*p != ',')
7260 break;
7261 ++p;
7262 }
7263 if (*p != '\0') {
7264 error("bad output page list");
7265 output_page_list = 0;
7266 }
7267}
7268
7269static FILE *open_mac_file(const char *mac, char **path)
7270{
7271 // Try first FOOBAR.tmac, then tmac.FOOBAR
7272 char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
7273 strcpy(s1, mac);
7274 strcat(s1, MACRO_POSTFIX);
7275 FILE *fp = mac_path->open_file(s1, path);
7276 a_delete s1;
7277 if (!fp) {
7278 char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
7279 strcpy(s2, MACRO_PREFIX);
7280 strcat(s2, mac);
7281 fp = mac_path->open_file(s2, path);
7282 a_delete s2;
7283 }
7284 return fp;
7285}
7286
7287static void process_macro_file(const char *mac)
7288{
7289 char *path;
7290 FILE *fp = open_mac_file(mac, &path);
7291 if (!fp)
7292 fatal("can't find macro file %1", mac);
7293 const char *s = symbol(path).contents();
7294 a_delete path;
7295 input_stack::push(new file_iterator(fp, s));
7296 tok.next();
7297 process_input_stack();
7298}
7299
7300static void process_startup_file(const char *filename)
7301{
7302 char *path;
7303 search_path *orig_mac_path = mac_path;
7304 mac_path = &config_macro_path;
7305 FILE *fp = mac_path->open_file(filename, &path);
7306 if (fp) {
7307 input_stack::push(new file_iterator(fp, symbol(path).contents()));
7308 a_delete path;
7309 tok.next();
7310 process_input_stack();
7311 }
7312 mac_path = orig_mac_path;
7313}
7314
7315void macro_source()
7316{
7317 symbol nm = get_long_name(1);
7318 if (nm.is_null())
7319 skip_line();
7320 else {
7321 while (!tok.newline() && !tok.eof())
7322 tok.next();
7323 char *path;
7324 FILE *fp = mac_path->open_file(nm.contents(), &path);
7325 // .mso doesn't (and cannot) go through open_mac_file, so we
7326 // need to do it here manually: If we have tmac.FOOBAR, try
7327 // FOOBAR.tmac and vice versa
7328 if (!fp) {
7329 const char *fn = nm.contents();
7330 if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
7331 char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)];
7332 strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
7333 strcat(s, MACRO_POSTFIX);
7334 fp = mac_path->open_file(s, &path);
7335 a_delete s;
7336 }
7337 if (!fp) {
7338 if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1,
7339 MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
7340 char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)];
7341 strcpy(s, MACRO_PREFIX);
7342 strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1);
7343 fp = mac_path->open_file(s, &path);
7344 a_delete s;
7345 }
7346 }
7347 }
7348 if (fp) {
7349 input_stack::push(new file_iterator(fp, symbol(path).contents()));
7350 a_delete path;
7351 }
7352 else
7353 error("can't find macro file `%1'", nm.contents());
7354 tok.next();
7355 }
7356}
7357
7358static void process_input_file(const char *name)
7359{
7360 FILE *fp;
7361 if (strcmp(name, "-") == 0) {
7362 clearerr(stdin);
7363 fp = stdin;
7364 }
7365 else {
7366 errno = 0;
7367 fp = include_search_path.open_file_cautious(name);
7368 if (!fp)
7369 fatal("can't open `%1': %2", name, strerror(errno));
7370 }
7371 input_stack::push(new file_iterator(fp, name));
7372 tok.next();
7373 process_input_stack();
7374}
7375
7376// make sure the_input is empty before calling this
7377
7378static int evaluate_expression(const char *expr, units *res)
7379{
7380 input_stack::push(make_temp_iterator(expr));
7381 tok.next();
7382 int success = get_number(res, 'u');
7383 while (input_stack::get(0) != EOF)
7384 ;
7385 return success;
7386}
7387
7388static void do_register_assignment(const char *s)
7389{
7390 const char *p = strchr(s, '=');
7391 if (!p) {
7392 char buf[2];
7393 buf[0] = s[0];
7394 buf[1] = 0;
7395 units n;
7396 if (evaluate_expression(s + 1, &n))
7397 set_number_reg(buf, n);
7398 }
7399 else {
7400 char *buf = new char[p - s + 1];
7401 memcpy(buf, s, p - s);
7402 buf[p - s] = 0;
7403 units n;
7404 if (evaluate_expression(p + 1, &n))
7405 set_number_reg(buf, n);
7406 a_delete buf;
7407 }
7408}
7409
7410static void set_string(const char *name, const char *value)
7411{
7412 macro *m = new macro;
7413 for (const char *p = value; *p; p++)
7414 if (!invalid_input_char((unsigned char)*p))
7415 m->append(*p);
7416 request_dictionary.define(name, m);
7417}
7418
7419static void do_string_assignment(const char *s)
7420{
7421 const char *p = strchr(s, '=');
7422 if (!p) {
7423 char buf[2];
7424 buf[0] = s[0];
7425 buf[1] = 0;
7426 set_string(buf, s + 1);
7427 }
7428 else {
7429 char *buf = new char[p - s + 1];
7430 memcpy(buf, s, p - s);
7431 buf[p - s] = 0;
7432 set_string(buf, p + 1);
7433 a_delete buf;
7434 }
7435}
7436
7437struct string_list {
7438 const char *s;
7439 string_list *next;
7440 string_list(const char *ss) : s(ss), next(0) {}
7441};
7442
7443#if 0
7444static void prepend_string(const char *s, string_list **p)
7445{
7446 string_list *l = new string_list(s);
7447 l->next = *p;
7448 *p = l;
7449}
7450#endif
7451
7452static void add_string(const char *s, string_list **p)
7453{
7454 while (*p)
7455 p = &((*p)->next);
7456 *p = new string_list(s);
7457}
7458
7459void usage(FILE *stream, const char *prog)
7460{
7461 fprintf(stream,
7462"usage: %s -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n"
7463" -rcn -Tname -Fdir -Idir -Mdir [files...]\n",
7464 prog);
7465}
7466
7467int main(int argc, char **argv)
7468{
7469 program_name = argv[0];
7470 static char stderr_buf[BUFSIZ];
7471 setbuf(stderr, stderr_buf);
7472 int c;
7473 string_list *macros = 0;
7474 string_list *register_assignments = 0;
7475 string_list *string_assignments = 0;
7476 int iflag = 0;
7477 int tflag = 0;
7478 int fflag = 0;
7479 int nflag = 0;
7480 int no_rc = 0; // don't process troffrc and troffrc-end
7481 int next_page_number = 0; // pacify compiler
7482 opterr = 0;
7483 hresolution = vresolution = 1;
7484 // restore $PATH if called from groff
7485 char* groff_path = getenv("GROFF_PATH__");
7486 if (groff_path) {
7487 string e = "PATH";
7488 e += '=';
7489 if (*groff_path)
7490 e += groff_path;
7491 e += '\0';
7492 if (putenv(strsave(e.contents())))
7493 fatal("putenv failed");
7494 }
7495 static const struct option long_options[] = {
7496 { "help", no_argument, 0, CHAR_MAX + 1 },
7497 { "version", no_argument, 0, 'v' },
7498 { 0, 0, 0, 0 }
7499 };
465b256c
JR
7500#if defined(DEBUGGING)
7501#define DEBUG_OPTION "D"
7502#endif
7503 while ((c = getopt_long(argc, argv,
7504 "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
7505 DEBUG_OPTION, long_options, 0))
92d0a6a6
JR
7506 != EOF)
7507 switch(c) {
7508 case 'v':
7509 {
7510 printf("GNU troff (groff) version %s\n", Version_string);
7511 exit(0);
7512 break;
7513 }
7514 case 'I':
7515 // Search path for .psbb files
7516 // and most other non-system input files.
7517 include_search_path.command_line_dir(optarg);
7518 break;
7519 case 'T':
7520 device = optarg;
7521 tflag = 1;
7522 is_html = (strcmp(device, "html") == 0);
7523 break;
7524 case 'C':
7525 compatible_flag = 1;
7526 // fall through
7527 case 'c':
7528 color_flag = 0;
7529 break;
7530 case 'M':
7531 macro_path.command_line_dir(optarg);
7532 safer_macro_path.command_line_dir(optarg);
7533 config_macro_path.command_line_dir(optarg);
7534 break;
7535 case 'F':
7536 font::command_line_font_dir(optarg);
7537 break;
7538 case 'm':
7539 add_string(optarg, &macros);
7540 break;
7541 case 'E':
7542 inhibit_errors = 1;
7543 break;
7544 case 'R':
7545 no_rc = 1;
7546 break;
7547 case 'w':
7548 enable_warning(optarg);
7549 break;
7550 case 'W':
7551 disable_warning(optarg);