summaryrefslogtreecommitdiffstats
path: root/src/roff/troff/input.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/roff/troff/input.cpp')
-rw-r--r--src/roff/troff/input.cpp9209
1 files changed, 9209 insertions, 0 deletions
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
new file mode 100644
index 0000000..292ee73
--- /dev/null
+++ b/src/roff/troff/input.cpp
@@ -0,0 +1,9209 @@
+/* Copyright (C) 1989-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "macropath.h"
+#include "input.h"
+#include "defs.h"
+#include "unicode.h"
+#include "curtime.h"
+
+// Needed for getpid() and isatty()
+#include "posix.h"
+
+#include "nonposix.h"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+#define MACRO_PREFIX "tmac."
+#define MACRO_POSTFIX ".tmac"
+#define INITIAL_STARTUP_FILE "troffrc"
+#define FINAL_STARTUP_FILE "troffrc-end"
+#define DEFAULT_INPUT_STACK_LIMIT 1000
+
+#ifndef DEFAULT_WARNING_MASK
+// warnings that are enabled by default
+#define DEFAULT_WARNING_MASK \
+ (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT|WARN_FILE)
+#endif
+
+// initial size of buffer for reading names; expanded as necessary
+#define ABUF_SIZE 16
+
+extern "C" const char *program_name;
+extern "C" const char *Version_string;
+
+#ifdef COLUMN
+void init_column_requests();
+#endif /* COLUMN */
+
+static node *read_draw_node();
+static void read_color_draw_node(token &);
+static void push_token(const token &);
+void copy_file();
+#ifdef COLUMN
+void vjustify();
+#endif /* COLUMN */
+void transparent_file();
+
+token tok;
+int break_flag = 0;
+int class_flag = 0;
+int color_flag = 1; // colors are on by default
+static int backtrace_flag = 0;
+#ifndef POPEN_MISSING
+char *pipe_command = 0;
+#endif
+charinfo *charset_table[256];
+unsigned char hpf_code_table[256];
+
+static int warning_mask = DEFAULT_WARNING_MASK;
+static int inhibit_errors = 0;
+static int ignoring = 0;
+
+static void enable_warning(const char *);
+static void disable_warning(const char *);
+
+static int escape_char = '\\';
+static symbol end_of_input_macro_name;
+static symbol blank_line_macro_name;
+static symbol leading_spaces_macro_name;
+static int compatible_flag = 0;
+static int do_old_compatible_flag = -1; // for .do request
+int ascii_output_flag = 0;
+int suppress_output_flag = 0;
+int is_html = 0;
+int begin_level = 0; // number of nested \O escapes
+
+int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
+ // \O[345], \R, \s, or \S has been processed
+ // in token::next()
+int old_have_input = 0; // value of have_input right before \n
+bool device_has_tcommand = false; // 't' output command supported
+int unsafe_flag = 0; // safer by default
+
+bool have_multiple_params = false; // e.g., \[e aa], \*[foo bar]
+
+double spread_limit = -3.0 - 1.0; // negative means deactivated
+
+double warn_scale;
+char warn_scaling_indicator;
+int debug_state = 0; // turns on debugging of the html troff state
+
+search_path *mac_path = &safer_macro_path;
+
+// Defaults to the current directory.
+search_path include_search_path(0, 0, 0, 1);
+
+static int get_copy(node**, bool = false, bool = false);
+static void copy_mode_error(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
+static symbol read_escape_parameter(read_mode = NO_ARGS);
+static symbol read_long_escape_parameters(read_mode = NO_ARGS);
+static void interpolate_string(symbol);
+static void interpolate_string_with_args(symbol);
+static void interpolate_macro(symbol, bool = false);
+static void interpolate_number_format(symbol);
+static void interpolate_environment_variable(symbol);
+
+static symbol composite_glyph_name(symbol);
+static void interpolate_arg(symbol);
+static request_or_macro *lookup_request(symbol);
+static int get_delim_number(units *, unsigned char);
+static int get_delim_number(units *, unsigned char, units);
+static symbol do_get_long_name(bool, char);
+static int get_line_arg(units *res, unsigned char si, charinfo **cp);
+static bool read_size(int *);
+static symbol get_delim_name();
+static void init_registers();
+static void trapping_blank_line();
+
+class input_iterator;
+input_iterator *make_temp_iterator(const char *);
+const char *input_char_description(int);
+
+void process_input_stack();
+void chop_macro(); // declare to avoid friend name injection
+
+
+void set_escape_char()
+{
+ if (has_arg()) {
+ if (tok.ch() == 0) {
+ error("cannot select invalid escape character; using '\\'");
+ escape_char = '\\';
+ }
+ else
+ escape_char = tok.ch();
+ }
+ else
+ escape_char = '\\';
+ skip_line();
+}
+
+void escape_off()
+{
+ escape_char = 0;
+ skip_line();
+}
+
+static int saved_escape_char = '\\';
+
+void save_escape_char()
+{
+ saved_escape_char = escape_char;
+ skip_line();
+}
+
+void restore_escape_char()
+{
+ escape_char = saved_escape_char;
+ skip_line();
+}
+
+struct arg_list;
+
+class input_iterator {
+public:
+ input_iterator();
+ input_iterator(int is_div);
+ virtual ~input_iterator() {}
+ int get(node **);
+ friend class input_stack;
+ int is_diversion;
+ statem *diversion_state;
+protected:
+ const unsigned char *ptr;
+ const unsigned char *eptr;
+ input_iterator *next;
+private:
+ virtual int fill(node **);
+ virtual int peek();
+ virtual int has_args() { return 0; }
+ virtual int nargs() { return 0; }
+ virtual input_iterator *get_arg(int) { return 0; }
+ virtual arg_list *get_arg_list() { return 0; }
+ virtual symbol get_macro_name() { return NULL_SYMBOL; }
+ virtual int space_follows_arg(int) { return 0; }
+ virtual int get_break_flag() { return 0; }
+ virtual int get_location(int, const char **, int *) { return 0; }
+ virtual void backtrace() {}
+ virtual int set_location(const char *, int) { return 0; }
+ virtual int next_file(FILE *, const char *) { return 0; }
+ virtual void shift(int) {}
+ virtual int is_boundary() {return 0; }
+ virtual int is_file() { return 0; }
+ virtual int is_macro() { return 0; }
+ virtual void save_compatible_flag(int) {}
+ virtual int get_compatible_flag() { return 0; }
+};
+
+input_iterator::input_iterator()
+: is_diversion(0), ptr(0), eptr(0)
+{
+}
+
+input_iterator::input_iterator(int is_div)
+: is_diversion(is_div), ptr(0), eptr(0)
+{
+}
+
+int input_iterator::fill(node **)
+{
+ return EOF;
+}
+
+int input_iterator::peek()
+{
+ return EOF;
+}
+
+inline int input_iterator::get(node **p)
+{
+ return ptr < eptr ? *ptr++ : fill(p);
+}
+
+class input_boundary : public input_iterator {
+public:
+ int is_boundary() { return 1; }
+};
+
+class input_return_boundary : public input_iterator {
+public:
+ int is_boundary() { return 2; }
+};
+
+class file_iterator : public input_iterator {
+ FILE *fp;
+ int lineno;
+ const char *filename;
+ int popened;
+ int newline_flag;
+ int seen_escape;
+ enum { BUF_SIZE = 512 };
+ unsigned char buf[BUF_SIZE];
+ void close();
+public:
+ file_iterator(FILE *, const char *, int = 0);
+ ~file_iterator();
+ int fill(node **);
+ int peek();
+ int get_location(int, const char **, int *);
+ void backtrace();
+ int set_location(const char *, int);
+ int next_file(FILE *, const char *);
+ int is_file();
+};
+
+file_iterator::file_iterator(FILE *f, const char *fn, int po)
+: fp(f), lineno(1), filename(fn), popened(po),
+ newline_flag(0), seen_escape(0)
+{
+ if ((font::use_charnames_in_special) && (fn != 0)) {
+ if (!the_output)
+ init_output();
+ the_output->put_filename(fn, po);
+ }
+}
+
+file_iterator::~file_iterator()
+{
+ close();
+}
+
+void file_iterator::close()
+{
+ if (fp == stdin)
+ clearerr(stdin);
+#ifndef POPEN_MISSING
+ else if (popened)
+ pclose(fp);
+#endif /* not POPEN_MISSING */
+ else
+ fclose(fp);
+}
+
+int file_iterator::is_file()
+{
+ return 1;
+}
+
+int file_iterator::next_file(FILE *f, const char *s)
+{
+ close();
+ filename = s;
+ fp = f;
+ lineno = 1;
+ newline_flag = 0;
+ seen_escape = 0;
+ popened = 0;
+ ptr = 0;
+ eptr = 0;
+ return 1;
+}
+
+int file_iterator::fill(node **)
+{
+ if (newline_flag)
+ lineno++;
+ newline_flag = 0;
+ unsigned char *p = buf;
+ ptr = p;
+ unsigned char *e = p + BUF_SIZE;
+ while (p < e) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ *p++ = c;
+ if (c == '\n') {
+ seen_escape = 0;
+ newline_flag = 1;
+ break;
+ }
+ seen_escape = (c == '\\');
+ }
+ }
+ if (p > buf) {
+ eptr = p;
+ return *ptr++;
+ }
+ else {
+ eptr = p;
+ return EOF;
+ }
+}
+
+int file_iterator::peek()
+{
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ c = getc(fp);
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ return c;
+}
+
+int file_iterator::get_location(int /*allow_macro*/,
+ const char **filenamep, int *linenop)
+{
+ *linenop = lineno;
+ if (filename != 0 && strcmp(filename, "-") == 0)
+ *filenamep = "<standard input>";
+ else
+ *filenamep = filename;
+ return 1;
+}
+
+void file_iterator::backtrace()
+{
+ const char *f;
+ int n;
+ // Get side effect of filename rewrite if stdin.
+ (void) get_location(0, &f, &n);
+ if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ errprint("backtrace: %3 '%1':%2\n", f, n, popened ? "pipe" : "file");
+}
+
+int file_iterator::set_location(const char *f, int ln)
+{
+ if (f) {
+ filename = f;
+ if (!the_output)
+ init_output();
+ the_output->put_filename(f, 0);
+ }
+ lineno = ln;
+ return 1;
+}
+
+input_iterator nil_iterator;
+
+class input_stack {
+public:
+ static int get(node **);
+ static int peek();
+ static void push(input_iterator *);
+ static input_iterator *get_arg(int);
+ static arg_list *get_arg_list();
+ static symbol get_macro_name();
+ static int space_follows_arg(int);
+ static int get_break_flag();
+ static int nargs();
+ static int get_location(int, const char **, int *);
+ static int set_location(const char *, int);
+ static void backtrace();
+ static void next_file(FILE *, const char *);
+ static void end_file();
+ static void shift(int n);
+ static void add_boundary();
+ static void add_return_boundary();
+ static int is_return_boundary();
+ static void remove_boundary();
+ static int get_level();
+ static int get_div_level();
+ static void increase_level();
+ static void decrease_level();
+ static void clear();
+ static void pop_macro();
+ static void save_compatible_flag(int);
+ static int get_compatible_flag();
+ static statem *get_diversion_state();
+ static void check_end_diversion(input_iterator *t);
+ static int limit;
+ static int div_level;
+ static statem *diversion_state;
+private:
+ static input_iterator *top;
+ static int level;
+ static int finish_get(node **);
+ static int finish_peek();
+};
+
+input_iterator *input_stack::top = &nil_iterator;
+int input_stack::level = 0;
+int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
+int input_stack::div_level = 0;
+statem *input_stack::diversion_state = 0;
+int suppress_push=0;
+
+
+inline int input_stack::get_level()
+{
+ return level;
+}
+
+inline void input_stack::increase_level()
+{
+ level++;
+}
+
+inline void input_stack::decrease_level()
+{
+ level--;
+}
+
+inline int input_stack::get_div_level()
+{
+ return div_level;
+}
+
+inline int input_stack::get(node **np)
+{
+ int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
+ if (res == '\n') {
+ old_have_input = have_input;
+ have_input = 0;
+ }
+ return res;
+}
+
+int input_stack::finish_get(node **np)
+{
+ for (;;) {
+ int c = top->fill(np);
+ if (c != EOF || top->is_boundary())
+ return c;
+ if (top == &nil_iterator)
+ break;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+#if defined(DEBUGGING)
+ if (debug_state)
+ if (tem->is_diversion)
+ fprintf(stderr,
+ "in diversion level = %d\n", input_stack::get_div_level());
+#endif
+ top = top->next;
+ level--;
+ delete tem;
+ if (top->ptr < top->eptr)
+ return *top->ptr++;
+ }
+ assert(level == 0);
+ return EOF;
+}
+
+inline int input_stack::peek()
+{
+ return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
+}
+
+void input_stack::check_end_diversion(input_iterator *t)
+{
+ if (t->is_diversion) {
+ div_level--;
+ if (diversion_state)
+ delete diversion_state;
+ diversion_state = t->diversion_state;
+ }
+}
+
+int input_stack::finish_peek()
+{
+ for (;;) {
+ int c = top->peek();
+ if (c != EOF || top->is_boundary())
+ return c;
+ if (top == &nil_iterator)
+ break;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ if (top->ptr < top->eptr)
+ return *top->ptr;
+ }
+ assert(level == 0);
+ return EOF;
+}
+
+void input_stack::add_boundary()
+{
+ push(new input_boundary);
+}
+
+void input_stack::add_return_boundary()
+{
+ push(new input_return_boundary);
+}
+
+int input_stack::is_return_boundary()
+{
+ return top->is_boundary() == 2;
+}
+
+void input_stack::remove_boundary()
+{
+ assert(top->is_boundary());
+ input_iterator *temp = top->next;
+ check_end_diversion(top);
+
+ delete top;
+ top = temp;
+ level--;
+}
+
+void input_stack::push(input_iterator *in)
+{
+ if (in == 0)
+ return;
+ if (++level > limit && limit > 0)
+ fatal("input stack limit exceeded (probable infinite loop)");
+ in->next = top;
+ top = in;
+ if (top->is_diversion) {
+ div_level++;
+ in->diversion_state = diversion_state;
+ diversion_state = curenv->construct_state(0);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ curenv->dump_troff_state();
+ fflush(stderr);
+ }
+#endif
+ }
+#if defined(DEBUGGING)
+ if (debug_state)
+ if (top->is_diversion) {
+ fprintf(stderr,
+ "in diversion level = %d\n", input_stack::get_div_level());
+ fflush(stderr);
+ }
+#endif
+}
+
+statem *get_diversion_state()
+{
+ return input_stack::get_diversion_state();
+}
+
+statem *input_stack::get_diversion_state()
+{
+ if (0 == diversion_state)
+ return 0;
+ else
+ return new statem(diversion_state);
+}
+
+input_iterator *input_stack::get_arg(int i)
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_arg(i);
+ return 0;
+}
+
+arg_list *input_stack::get_arg_list()
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_arg_list();
+ return 0;
+}
+
+symbol input_stack::get_macro_name()
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_macro_name();
+ return NULL_SYMBOL;
+}
+
+int input_stack::space_follows_arg(int i)
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->space_follows_arg(i);
+ return 0;
+}
+
+int input_stack::get_break_flag()
+{
+ return top->get_break_flag();
+}
+
+void input_stack::shift(int n)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->has_args()) {
+ p->shift(n);
+ return;
+ }
+}
+
+int input_stack::nargs()
+{
+ for (input_iterator *p =top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->nargs();
+ return 0;
+}
+
+int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->get_location(allow_macro, filenamep, linenop))
+ return 1;
+ return 0;
+}
+
+void input_stack::backtrace()
+{
+ for (input_iterator *p = top; p; p = p->next)
+ p->backtrace();
+}
+
+int input_stack::set_location(const char *filename, int lineno)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->set_location(filename, lineno))
+ return 1;
+ return 0;
+}
+
+void input_stack::next_file(FILE *fp, const char *s)
+{
+ input_iterator **pp;
+ for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
+ if ((*pp)->next_file(fp, s))
+ return;
+ if (++level > limit && limit > 0)
+ fatal("input stack limit exceeded");
+ *pp = new file_iterator(fp, s);
+ (*pp)->next = &nil_iterator;
+}
+
+void input_stack::end_file()
+{
+ for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
+ if ((*pp)->is_file()) {
+ input_iterator *tem = *pp;
+ check_end_diversion(tem);
+ *pp = (*pp)->next;
+ delete tem;
+ level--;
+ return;
+ }
+}
+
+void input_stack::clear()
+{
+ int nboundaries = 0;
+ while (top != &nil_iterator) {
+ if (top->is_boundary())
+ nboundaries++;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ }
+ // Keep while_request happy.
+ for (; nboundaries > 0; --nboundaries)
+ add_return_boundary();
+}
+
+void input_stack::pop_macro()
+{
+ int nboundaries = 0;
+ int is_macro = 0;
+ do {
+ if (top->next == &nil_iterator)
+ break;
+ if (top->is_boundary())
+ nboundaries++;
+ is_macro = top->is_macro();
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ } while (!is_macro);
+ // Keep while_request happy.
+ for (; nboundaries > 0; --nboundaries)
+ add_return_boundary();
+}
+
+inline void input_stack::save_compatible_flag(int f)
+{
+ top->save_compatible_flag(f);
+}
+
+inline int input_stack::get_compatible_flag()
+{
+ return top->get_compatible_flag();
+}
+
+void backtrace_request()
+{
+ input_stack::backtrace();
+ fflush(stderr);
+ skip_line();
+}
+
+void next_file()
+{
+ symbol nm = get_long_name();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (nm.is_null())
+ input_stack::end_file();
+ else {
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(nm.contents());
+ if (!fp)
+ error("can't open '%1': %2", nm.contents(), strerror(errno));
+ else
+ input_stack::next_file(fp, nm.contents());
+ }
+ tok.next();
+}
+
+void shift()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ input_stack::shift(n);
+ skip_line();
+}
+
+static char get_char_for_escape_parameter(bool allow_space = false)
+{
+ int c = get_copy(0 /* nullptr */, false /* is defining */,
+ true /* handle \E */);
+ switch (c) {
+ case EOF:
+ copy_mode_error("end of input in escape sequence");
+ return '\0';
+ default:
+ if (!is_invalid_input_char(c))
+ break;
+ // fall through
+ case '\n':
+ if (c == '\n')
+ input_stack::push(make_temp_iterator("\n"));
+ // fall through
+ case ' ':
+ if (c == ' ' && allow_space)
+ break;
+ // fall through
+ case '\t':
+ case '\001':
+ case '\b':
+ copy_mode_error("%1 is not allowed in an escape sequence parameter",
+ input_char_description(c));
+ return '\0';
+ }
+ return c;
+}
+
+static symbol read_two_char_escape_parameter()
+{
+ char buf[3];
+ buf[0] = get_char_for_escape_parameter();
+ if (buf[0] != '\0') {
+ buf[1] = get_char_for_escape_parameter();
+ if (buf[1] == '\0')
+ buf[0] = 0;
+ else
+ buf[2] = 0;
+ }
+ return symbol(buf);
+}
+
+static symbol read_long_escape_parameters(read_mode mode)
+{
+ int start_level = input_stack::get_level();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ char c;
+ int have_char = 0;
+ for (;;) {
+ c = get_char_for_escape_parameter(have_char && mode == WITH_ARGS);
+ if (c == 0) {
+ if (buf != abuf)
+ delete[] buf;
+ return NULL_SYMBOL;
+ }
+ have_char = 1;
+ if (mode == WITH_ARGS && c == ' ')
+ break;
+ if (i + 2 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ if (c == ']' && input_stack::get_level() == start_level)
+ break;
+ buf[i++] = c;
+ }
+ buf[i] = 0;
+ if (c == ' ')
+ have_multiple_params = true;
+ if (buf == abuf) {
+ if (i == 0) {
+ if (mode != ALLOW_EMPTY)
+ copy_mode_error("empty escape name");
+ return EMPTY_SYMBOL;
+ }
+ return symbol(abuf);
+ }
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+static symbol read_escape_parameter(read_mode mode)
+{
+ char c = get_char_for_escape_parameter();
+ if (c == 0)
+ return NULL_SYMBOL;
+ if (c == '(')
+ return read_two_char_escape_parameter();
+ if (c == '[' && !compatible_flag)
+ return read_long_escape_parameters(mode);
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ return symbol(buf);
+}
+
+static symbol read_increment_and_escape_parameter(int *incp)
+{
+ char c = get_char_for_escape_parameter();
+ switch (c) {
+ case 0:
+ *incp = 0;
+ return NULL_SYMBOL;
+ case '(':
+ *incp = 0;
+ return read_two_char_escape_parameter();
+ case '+':
+ *incp = 1;
+ return read_escape_parameter();
+ case '-':
+ *incp = -1;
+ return read_escape_parameter();
+ case '[':
+ if (!compatible_flag) {
+ *incp = 0;
+ return read_long_escape_parameters();
+ }
+ break;
+ }
+ *incp = 0;
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ return symbol(buf);
+}
+
+static int get_copy(node **nd, bool is_defining, bool handle_escape_E)
+{
+ for (;;) {
+ int c = input_stack::get(nd);
+ if (c == PUSH_GROFF_MODE) {
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 0;
+ continue;
+ }
+ if (c == PUSH_COMP_MODE) {
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 1;
+ continue;
+ }
+ if (c == POP_GROFFCOMP_MODE) {
+ compatible_flag = input_stack::get_compatible_flag();
+ continue;
+ }
+ if (c == BEGIN_QUOTE) {
+ input_stack::increase_level();
+ continue;
+ }
+ if (c == END_QUOTE) {
+ input_stack::decrease_level();
+ continue;
+ }
+ if (c == DOUBLE_QUOTE)
+ continue;
+ if (c == ESCAPE_E && handle_escape_E)
+ c = escape_char;
+ if (c == ESCAPE_NEWLINE) {
+ if (is_defining)
+ return c;
+ do {
+ c = input_stack::get(nd);
+ } while (c == ESCAPE_NEWLINE);
+ }
+ if (c != escape_char || escape_char <= 0)
+ return c;
+ again:
+ c = input_stack::peek();
+ switch(c) {
+ case 0:
+ return escape_char;
+ case '"':
+ (void)input_stack::get(0);
+ while ((c = input_stack::get(0)) != '\n' && c != EOF)
+ ;
+ return c;
+ case '#': // Like \" but newline is ignored.
+ (void)input_stack::get(0);
+ while ((c = input_stack::get(0)) != '\n')
+ if (c == EOF)
+ return EOF;
+ break;
+ case '$':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_arg(s);
+ break;
+ }
+ case '*':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter(WITH_ARGS);
+ if (!(s.is_null() || s.is_empty())) {
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ interpolate_string_with_args(s);
+ }
+ else
+ interpolate_string(s);
+ }
+ break;
+ }
+ case 'a':
+ (void)input_stack::get(0);
+ return '\001';
+ case 'e':
+ (void)input_stack::get(0);
+ return ESCAPE_e;
+ case 'E':
+ (void)input_stack::get(0);
+ if (handle_escape_E)
+ goto again;
+ return ESCAPE_E;
+ case 'n':
+ {
+ (void)input_stack::get(0);
+ int inc;
+ symbol s = read_increment_and_escape_parameter(&inc);
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_reg(s, inc);
+ break;
+ }
+ case 'g':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_format(s);
+ break;
+ }
+ case 't':
+ (void)input_stack::get(0);
+ return '\t';
+ case 'V':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_environment_variable(s);
+ break;
+ }
+ case '\n':
+ (void)input_stack::get(0);
+ if (is_defining)
+ return ESCAPE_NEWLINE;
+ break;
+ case ' ':
+ (void)input_stack::get(0);
+ return ESCAPE_SPACE;
+ case '~':
+ (void)input_stack::get(0);
+ return ESCAPE_TILDE;
+ case ':':
+ (void)input_stack::get(0);
+ return ESCAPE_COLON;
+ case '|':
+ (void)input_stack::get(0);
+ return ESCAPE_BAR;
+ case '^':
+ (void)input_stack::get(0);
+ return ESCAPE_CIRCUMFLEX;
+ case '{':
+ (void)input_stack::get(0);
+ return ESCAPE_LEFT_BRACE;
+ case '}':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_BRACE;
+ case '`':
+ (void)input_stack::get(0);
+ return ESCAPE_LEFT_QUOTE;
+ case '\'':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_QUOTE;
+ case '-':
+ (void)input_stack::get(0);
+ return ESCAPE_HYPHEN;
+ case '_':
+ (void)input_stack::get(0);
+ return ESCAPE_UNDERSCORE;
+ case 'c':
+ (void)input_stack::get(0);
+ return ESCAPE_c;
+ case '!':
+ (void)input_stack::get(0);
+ return ESCAPE_BANG;
+ case '?':
+ (void)input_stack::get(0);
+ return ESCAPE_QUESTION;
+ case '&':
+ (void)input_stack::get(0);
+ return ESCAPE_AMPERSAND;
+ case ')':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_PARENTHESIS;
+ case '.':
+ (void)input_stack::get(0);
+ return c;
+ case '%':
+ (void)input_stack::get(0);
+ return ESCAPE_PERCENT;
+ default:
+ if (c == escape_char) {
+ (void)input_stack::get(0);
+ return c;
+ }
+ else
+ return escape_char;
+ }
+ }
+}
+
+class non_interpreted_char_node : public node {
+ unsigned char c;
+public:
+ non_interpreted_char_node(unsigned char);
+ node *copy();
+ int interpret(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+int non_interpreted_char_node::same(node *nd)
+{
+ return c == ((non_interpreted_char_node *)nd)->c;
+}
+
+const char *non_interpreted_char_node::type()
+{
+ return "non_interpreted_char_node";
+}
+
+int non_interpreted_char_node::force_tprint()
+{
+ return 0;
+}
+
+int non_interpreted_char_node::is_tag()
+{
+ return 0;
+}
+
+non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
+{
+ assert(n != 0);
+}
+
+node *non_interpreted_char_node::copy()
+{
+ return new non_interpreted_char_node(c);
+}
+
+int non_interpreted_char_node::interpret(macro *mac)
+{
+ mac->append(c);
+ return 1;
+}
+
+static void do_width();
+static node *do_non_interpreted();
+static node *do_special();
+static node *do_suppress(symbol nm);
+static void do_register();
+
+dictionary color_dictionary(501);
+
+static color *lookup_color(symbol nm)
+{
+ assert(!nm.is_null());
+ if (nm == default_symbol)
+ return &default_color;
+ color *c = (color *)color_dictionary.lookup(nm);
+ if (c == 0)
+ warning(WARN_COLOR, "color '%1' not defined", nm.contents());
+ return c;
+}
+
+void do_glyph_color(symbol nm)
+{
+ if (nm.is_null())
+ return;
+ if (nm.is_empty())
+ curenv->set_glyph_color(curenv->get_prev_glyph_color());
+ else {
+ color *tem = lookup_color(nm);
+ if (tem)
+ curenv->set_glyph_color(tem);
+ else
+ (void)color_dictionary.lookup(nm, new color(nm));
+ }
+}
+
+void do_fill_color(symbol nm)
+{
+ if (nm.is_null())
+ return;
+ if (nm.is_empty())
+ curenv->set_fill_color(curenv->get_prev_fill_color());
+ else {
+ color *tem = lookup_color(nm);
+ if (tem)
+ curenv->set_fill_color(tem);
+ else
+ (void)color_dictionary.lookup(nm, new color(nm));
+ }
+}
+
+static unsigned int get_color_element(const char *scheme, const char *col)
+{
+ units val;
+ if (!get_number(&val, 'f')) {
+ warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
+ tok.next();
+ return 0;
+ }
+ if (val < 0) {
+ warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
+ return 0;
+ }
+ if (val > color::MAX_COLOR_VAL+1) {
+ warning(WARN_RANGE, "%1 cannot be greater than 1", col);
+ // we change 0x10000 to 0xffff
+ return color::MAX_COLOR_VAL;
+ }
+ return (unsigned int)val;
+}
+
+static color *read_rgb(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing rgb color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_rgb(s)) {
+ warning(WARN_COLOR, "expecting rgb color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int r = get_color_element("rgb color", "red component");
+ unsigned int g = get_color_element("rgb color", "green component");
+ unsigned int b = get_color_element("rgb color", "blue component");
+ col->set_rgb(r, g, b);
+ }
+ return col;
+}
+
+static color *read_cmy(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing cmy color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_cmy(s)) {
+ warning(WARN_COLOR, "expecting cmy color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int c = get_color_element("cmy color", "cyan component");
+ unsigned int m = get_color_element("cmy color", "magenta component");
+ unsigned int y = get_color_element("cmy color", "yellow component");
+ col->set_cmy(c, m, y);
+ }
+ return col;
+}
+
+static color *read_cmyk(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing cmyk color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_cmyk(s)) {
+ warning(WARN_COLOR, "expecting cmyk color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int c = get_color_element("cmyk color", "cyan component");
+ unsigned int m = get_color_element("cmyk color", "magenta component");
+ unsigned int y = get_color_element("cmyk color", "yellow component");
+ unsigned int k = get_color_element("cmyk color", "black component");
+ col->set_cmyk(c, m, y, k);
+ }
+ return col;
+}
+
+static color *read_gray(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing gray value");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_gray(s)) {
+ warning(WARN_COLOR, "expecting gray definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator("\n"));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int g = get_color_element("gray", "gray value");
+ col->set_gray(g);
+ }
+ return col;
+}
+
+static void activate_color()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ color_flag = n != 0;
+ else
+ color_flag = 1;
+ skip_line();
+}
+
+static void define_color()
+{
+ symbol color_name = get_long_name(true /* required */);
+ if (color_name.is_null()) {
+ skip_line();
+ return;
+ }
+ if (color_name == default_symbol) {
+ warning(WARN_COLOR, "default color can't be redefined");
+ skip_line();
+ return;
+ }
+ symbol style = get_long_name(true /* required */);
+ if (style.is_null()) {
+ skip_line();
+ return;
+ }
+ color *col;
+ if (strcmp(style.contents(), "rgb") == 0)
+ col = read_rgb();
+ else if (strcmp(style.contents(), "cmyk") == 0)
+ col = read_cmyk();
+ else if (strcmp(style.contents(), "gray") == 0)
+ col = read_gray();
+ else if (strcmp(style.contents(), "grey") == 0)
+ col = read_gray();
+ else if (strcmp(style.contents(), "cmy") == 0)
+ col = read_cmy();
+ else {
+ warning(WARN_COLOR, "unknown color space '%1';"
+ " use 'rgb', 'cmyk', 'gray' or 'cmy'", style.contents());
+ skip_line();
+ return;
+ }
+ if (col) {
+ col->nm = color_name;
+ (void)color_dictionary.lookup(color_name, col);
+ }
+ skip_line();
+}
+
+node *do_overstrike()
+{
+ overstrike_node *on = new overstrike_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in overstrike"
+ " escape sequence (got %1)", tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (tok.is_horizontal_space())
+ on->overstrike(tok.nd->copy());
+ else if (tok.is_unstretchable_space())
+ {
+ node *n = new hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ on->overstrike(n);
+ }
+ else {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ node *n = curenv->make_char_node(ci);
+ if (n)
+ on->overstrike(n);
+ }
+ }
+ }
+ return on;
+}
+
+static node *do_bracket()
+{
+ bracket_node *bn = new bracket_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " bracket-building escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ node *n = curenv->make_char_node(ci);
+ if (n)
+ bn->bracket(n);
+ }
+ }
+ return bn;
+}
+
+static int do_name_test()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ bool got_bad_char = false;
+ bool got_some_char = false;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in identifier"
+ " validation escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (!tok.ch())
+ got_bad_char = true;
+ got_some_char = true;
+ }
+ return (got_some_char && !got_bad_char);
+}
+
+static int do_expr_test()
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return 0;
+ tok.next();
+ // disable all warning and error messages temporarily
+ int saved_warning_mask = warning_mask;
+ int saved_inhibit_errors = inhibit_errors;
+ warning_mask = 0;
+ inhibit_errors = 1;
+ int dummy;
+ int result = get_number_rigidly(&dummy, 'u');
+ warning_mask = saved_warning_mask;
+ inhibit_errors = saved_inhibit_errors;
+ if (tok == start && input_stack::get_level() == start_level)
+ return result;
+ // ignore everything up to the delimiter in case we aren't right there
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " expression test escape sequence (got %1)",
+ tok.description());
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start && input_stack::get_level() == start_level)
+ break;
+ }
+ return 0;
+}
+
+#if 0
+static node *do_zero_width()
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ error("missing closing delimiter");
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ tok.process();
+ }
+ curenv = oldenv;
+ node *rev = env.extract_output_line();
+ node *n = 0;
+ while (rev) {
+ node *tem = rev;
+ rev = rev->next;
+ tem->next = n;
+ n = tem;
+ }
+ return new zero_width_node(n);
+}
+
+#else
+
+// It's undesirable for \Z to change environments, because then
+// \n(.w won't work as expected.
+
+static node *do_zero_width()
+{
+ node *rev = new dummy_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " zero-width escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (!tok.add_to_zero_width_node_list(&rev))
+ error("invalid token in argument to escaped 'Z'");
+ }
+ node *n = 0;
+ while (rev) {
+ node *tem = rev;
+ rev = rev->next;
+ tem->next = n;
+ n = tem;
+ }
+ return new zero_width_node(n);
+}
+
+#endif
+
+token_node *node::get_token_node()
+{
+ return 0;
+}
+
+class token_node : public node {
+public:
+ token tk;
+ token_node(const token &t);
+ node *copy();
+ token_node *get_token_node();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+token_node::token_node(const token &t) : tk(t)
+{
+}
+
+node *token_node::copy()
+{
+ return new token_node(tk);
+}
+
+token_node *token_node::get_token_node()
+{
+ return this;
+}
+
+int token_node::same(node *nd)
+{
+ return tk == ((token_node *)nd)->tk;
+}
+
+const char *token_node::type()
+{
+ return "token_node";
+}
+
+int token_node::force_tprint()
+{
+ return 0;
+}
+
+int token_node::is_tag()
+{
+ return 0;
+}
+
+token::token() : nd(0), type(TOKEN_EMPTY)
+{
+}
+
+token::~token()
+{
+ delete nd;
+}
+
+token::token(const token &t)
+: nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
+{
+ // Use two statements to work around bug in SGI C++.
+ node *tem = t.nd;
+ nd = tem ? tem->copy() : 0;
+}
+
+void token::operator=(const token &t)
+{
+ delete nd;
+ nm = t.nm;
+ // Use two statements to work around bug in SGI C++.
+ node *tem = t.nd;
+ nd = tem ? tem->copy() : 0;
+ c = t.c;
+ val = t.val;
+ dim = t.dim;
+ type = t.type;
+}
+
+void token::skip()
+{
+ while (is_space())
+ next();
+}
+
+bool has_arg()
+{
+ while (tok.is_space())
+ tok.next();
+ return !tok.is_newline();
+}
+
+void token::make_space()
+{
+ type = TOKEN_SPACE;
+}
+
+void token::make_newline()
+{
+ type = TOKEN_NEWLINE;
+}
+
+void token::next()
+{
+ if (nd) {
+ delete nd;
+ nd = 0;
+ }
+ units x;
+ for (;;) {
+ node *n = 0;
+ int cc = input_stack::get(&n);
+ if (cc != escape_char || escape_char == 0) {
+ handle_ordinary_char:
+ switch(cc) {
+ case INPUT_NO_BREAK_SPACE:
+ type = TOKEN_STRETCHABLE_SPACE;
+ return;
+ case INPUT_SOFT_HYPHEN:
+ type = TOKEN_HYPHEN_INDICATOR;
+ return;
+ case PUSH_GROFF_MODE:
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 0;
+ continue;
+ case PUSH_COMP_MODE:
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 1;
+ continue;
+ case POP_GROFFCOMP_MODE:
+ compatible_flag = input_stack::get_compatible_flag();
+ continue;
+ case BEGIN_QUOTE:
+ input_stack::increase_level();
+ continue;
+ case END_QUOTE:
+ input_stack::decrease_level();
+ continue;
+ case DOUBLE_QUOTE:
+ continue;
+ case EOF:
+ type = TOKEN_EOF;
+ return;
+ case TRANSPARENT_FILE_REQUEST:
+ case TITLE_REQUEST:
+ case COPY_FILE_REQUEST:
+#ifdef COLUMN
+ case VJUSTIFY_REQUEST:
+#endif /* COLUMN */
+ type = TOKEN_REQUEST;
+ c = cc;
+ return;
+ case BEGIN_TRAP:
+ type = TOKEN_BEGIN_TRAP;
+ return;
+ case END_TRAP:
+ type = TOKEN_END_TRAP;
+ return;
+ case LAST_PAGE_EJECTOR:
+ seen_last_page_ejector = 1;
+ // fall through
+ case PAGE_EJECTOR:
+ type = TOKEN_PAGE_EJECTOR;
+ return;
+ case ESCAPE_PERCENT:
+ ESCAPE_PERCENT:
+ type = TOKEN_HYPHEN_INDICATOR;
+ return;
+ case ESCAPE_SPACE:
+ ESCAPE_SPACE:
+ type = TOKEN_UNSTRETCHABLE_SPACE;
+ return;
+ case ESCAPE_TILDE:
+ ESCAPE_TILDE:
+ type = TOKEN_STRETCHABLE_SPACE;
+ return;
+ case ESCAPE_COLON:
+ ESCAPE_COLON:
+ type = TOKEN_ZERO_WIDTH_BREAK;
+ return;
+ case ESCAPE_e:
+ ESCAPE_e:
+ type = TOKEN_ESCAPE;
+ return;
+ case ESCAPE_E:
+ goto handle_escape_char;
+ case ESCAPE_BAR:
+ ESCAPE_BAR:
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(curenv->get_narrow_space_width(),
+ curenv->get_fill_color());
+ return;
+ case ESCAPE_CIRCUMFLEX:
+ ESCAPE_CIRCUMFLEX:
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(curenv->get_half_narrow_space_width(),
+ curenv->get_fill_color());
+ return;
+ case ESCAPE_NEWLINE:
+ have_input = 0;
+ break;
+ case ESCAPE_LEFT_BRACE:
+ ESCAPE_LEFT_BRACE:
+ type = TOKEN_LEFT_BRACE;
+ return;
+ case ESCAPE_RIGHT_BRACE:
+ ESCAPE_RIGHT_BRACE:
+ type = TOKEN_RIGHT_BRACE;
+ return;
+ case ESCAPE_LEFT_QUOTE:
+ ESCAPE_LEFT_QUOTE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("ga");
+ return;
+ case ESCAPE_RIGHT_QUOTE:
+ ESCAPE_RIGHT_QUOTE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("aa");
+ return;
+ case ESCAPE_HYPHEN:
+ ESCAPE_HYPHEN:
+ type = TOKEN_SPECIAL;
+ nm = symbol("-");
+ return;
+ case ESCAPE_UNDERSCORE:
+ ESCAPE_UNDERSCORE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("ul");
+ return;
+ case ESCAPE_c:
+ ESCAPE_c:
+ type = TOKEN_INTERRUPT;
+ return;
+ case ESCAPE_BANG:
+ ESCAPE_BANG:
+ type = TOKEN_TRANSPARENT;
+ return;
+ case ESCAPE_QUESTION:
+ ESCAPE_QUESTION:
+ nd = do_non_interpreted();
+ if (nd) {
+ type = TOKEN_NODE;
+ return;
+ }
+ break;
+ case ESCAPE_AMPERSAND:
+ ESCAPE_AMPERSAND:
+ type = TOKEN_DUMMY;
+ return;
+ case ESCAPE_RIGHT_PARENTHESIS:
+ ESCAPE_RIGHT_PARENTHESIS:
+ type = TOKEN_TRANSPARENT_DUMMY;
+ return;
+ case '\b':
+ type = TOKEN_BACKSPACE;
+ return;
+ case ' ':
+ type = TOKEN_SPACE;
+ return;
+ case '\t':
+ type = TOKEN_TAB;
+ return;
+ case '\n':
+ type = TOKEN_NEWLINE;
+ return;
+ case '\001':
+ type = TOKEN_LEADER;
+ return;
+ case 0:
+ {
+ assert(n != 0);
+ token_node *tn = n->get_token_node();
+ if (tn) {
+ *this = tn->tk;
+ delete tn;
+ }
+ else {
+ nd = n;
+ type = TOKEN_NODE;
+ }
+ }
+ return;
+ default:
+ type = TOKEN_CHAR;
+ c = cc;
+ return;
+ }
+ }
+ else {
+ handle_escape_char:
+ cc = input_stack::get(&n);
+ switch(cc) {
+ case '(':
+ nm = read_two_char_escape_parameter();
+ type = TOKEN_SPECIAL;
+ return;
+ case EOF:
+ type = TOKEN_EOF;
+ error("end of input after escape character");
+ return;
+ case '`':
+ goto ESCAPE_LEFT_QUOTE;
+ case '\'':
+ goto ESCAPE_RIGHT_QUOTE;
+ case '-':
+ goto ESCAPE_HYPHEN;
+ case '_':
+ goto ESCAPE_UNDERSCORE;
+ case '%':
+ goto ESCAPE_PERCENT;
+ case ' ':
+ goto ESCAPE_SPACE;
+ case '0':
+ nd = new hmotion_node(curenv->get_digit_width(),
+ curenv->get_fill_color());
+ type = TOKEN_HORIZONTAL_SPACE;
+ return;
+ case '|':
+ goto ESCAPE_BAR;
+ case '^':
+ goto ESCAPE_CIRCUMFLEX;
+ case '/':
+ type = TOKEN_ITALIC_CORRECTION;
+ return;
+ case ',':
+ type = TOKEN_NODE;
+ nd = new left_italic_corrected_node;
+ return;
+ case '&':
+ goto ESCAPE_AMPERSAND;
+ case ')':
+ goto ESCAPE_RIGHT_PARENTHESIS;
+ case '!':
+ goto ESCAPE_BANG;
+ case '?':
+ goto ESCAPE_QUESTION;
+ case '~':
+ goto ESCAPE_TILDE;
+ case ':':
+ goto ESCAPE_COLON;
+ case '"':
+ while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
+ ;
+ if (cc == '\n')
+ type = TOKEN_NEWLINE;
+ else
+ type = TOKEN_EOF;
+ return;
+ case '#': // Like \" but newline is ignored.
+ while ((cc = input_stack::get(0)) != '\n')
+ if (cc == EOF) {
+ type = TOKEN_EOF;
+ return;
+ }
+ break;
+ case '$':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_arg(s);
+ break;
+ }
+ case '*':
+ {
+ symbol s = read_escape_parameter(WITH_ARGS);
+ if (!(s.is_null() || s.is_empty())) {
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ interpolate_string_with_args(s);
+ }
+ else
+ interpolate_string(s);
+ }
+ break;
+ }
+ case 'a':
+ nd = new non_interpreted_char_node('\001');
+ type = TOKEN_NODE;
+ return;
+ case 'A':
+ c = '0' + do_name_test();
+ type = TOKEN_CHAR;
+ return;
+ case 'b':
+ nd = do_bracket();
+ type = TOKEN_NODE;
+ return;
+ case 'B':
+ c = '0' + do_expr_test();
+ type = TOKEN_CHAR;
+ return;
+ case 'c':
+ goto ESCAPE_c;
+ case 'C':
+ nm = get_delim_name();
+ if (nm.is_null())
+ break;
+ type = TOKEN_SPECIAL;
+ return;
+ case 'd':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(curenv->get_size() / 2,
+ curenv->get_fill_color());
+ return;
+ case 'D':
+ nd = read_draw_node();
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'e':
+ goto ESCAPE_e;
+ case 'E':
+ goto handle_escape_char;
+ case 'f':
+ {
+ symbol s = read_escape_parameter(ALLOW_EMPTY);
+ if (s.is_null())
+ break;
+ const char *p;
+ for (p = s.contents(); *p != '\0'; p++)
+ if (!csdigit(*p))
+ break;
+ // environment::set_font warns if a bogus mounting position is
+ // requested. We must warn here if a bogus font name is
+ // selected.
+ if (*p != '\0' || s.is_empty()) {
+ if (!curenv->set_font(s))
+ warning(WARN_FONT, "cannot select font '%1'",
+ s.contents());
+ }
+ else
+ (void) curenv->set_font(atoi(s.contents()));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ }
+ case 'F':
+ {
+ symbol s = read_escape_parameter(ALLOW_EMPTY);
+ if (s.is_null())
+ break;
+ curenv->set_family(s);
+ have_input = 1;
+ break;
+ }
+ case 'g':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_format(s);
+ break;
+ }
+ case 'h':
+ if (!get_delim_number(&x, 'm'))
+ break;
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(x, curenv->get_fill_color());
+ return;
+ case 'H':
+ // don't take height increments relative to previous height if
+ // in compatibility mode
+ if (!compatible_flag && curenv->get_char_height()) {
+ if (get_delim_number(&x, 'z', curenv->get_char_height()))
+ curenv->set_char_height(x);
+ }
+ else {
+ if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
+ curenv->set_char_height(x);
+ }
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'k':
+ nm = read_escape_parameter();
+ if (nm.is_null() || nm.is_empty())
+ break;
+ type = TOKEN_MARK_INPUT;
+ return;
+ case 'l':
+ case 'L':
+ {
+ charinfo *s = 0;
+ if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
+ break;
+ if (s == 0)
+ s = get_charinfo(cc == 'l' ? "ru" : "br");
+ type = TOKEN_NODE;
+ node *char_node = curenv->make_char_node(s);
+ if (cc == 'l')
+ nd = new hline_node(x, char_node);
+ else
+ nd = new vline_node(x, char_node);
+ return;
+ }
+ case 'm':
+ do_glyph_color(read_escape_parameter(ALLOW_EMPTY));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'M':
+ do_fill_color(read_escape_parameter(ALLOW_EMPTY));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'n':
+ {
+ int inc;
+ symbol s = read_increment_and_escape_parameter(&inc);
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_reg(s, inc);
+ break;
+ }
+ case 'N':
+ if (!get_delim_number(&val, 0))
+ break;
+ if (val < 0) {
+ warning(WARN_CHAR, "invalid numbered character %1", val);
+ break;
+ }
+ type = TOKEN_NUMBERED_CHAR;
+ return;
+ case 'o':
+ nd = do_overstrike();
+ type = TOKEN_NODE;
+ return;
+ case 'O':
+ nd = do_suppress(read_escape_parameter());
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'p':
+ type = TOKEN_SPREAD;
+ return;
+ case 'r':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
+ return;
+ case 'R':
+ do_register();
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 's':
+ if (read_size(&x))
+ curenv->set_size(x);
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'S':
+ if (get_delim_number(&x, 0))
+ curenv->set_char_slant(x);
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 't':
+ type = TOKEN_NODE;
+ nd = new non_interpreted_char_node('\t');
+ return;
+ case 'u':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(-curenv->get_size() / 2,
+ curenv->get_fill_color());
+ return;
+ case 'v':
+ if (!get_delim_number(&x, 'v'))
+ break;
+ type = TOKEN_NODE;
+ nd = new vmotion_node(x, curenv->get_fill_color());
+ return;
+ case 'V':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_environment_variable(s);
+ break;
+ }
+ case 'w':
+ do_width();
+ break;
+ case 'x':
+ if (!get_delim_number(&x, 'v'))
+ break;
+ type = TOKEN_NODE;
+ nd = new extra_size_node(x);
+ return;
+ case 'X':
+ nd = do_special();
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'Y':
+ {
+ symbol s = read_escape_parameter();
+ if (s.is_null() || s.is_empty())
+ break;
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m) {
+ error("can't transparently throughput a request");
+ break;
+ }
+ nd = new special_node(*m);
+ type = TOKEN_NODE;
+ return;
+ }
+ case 'z':
+ {
+ next();
+ if (type == TOKEN_NODE || type == TOKEN_HORIZONTAL_SPACE)
+ nd = new zero_width_node(nd);
+ else {
+ charinfo *ci = get_char(true /* required */);
+ if (ci == 0)
+ break;
+ node *gn = curenv->make_char_node(ci);
+ if (gn == 0)
+ break;
+ nd = new zero_width_node(gn);
+ type = TOKEN_NODE;
+ }
+ return;
+ }
+ case 'Z':
+ nd = do_zero_width();
+ if (nd == 0)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case '{':
+ goto ESCAPE_LEFT_BRACE;
+ case '}':
+ goto ESCAPE_RIGHT_BRACE;
+ case '\n':
+ break;
+ case '[':
+ if (!compatible_flag) {
+ symbol s = read_long_escape_parameters(WITH_ARGS);
+ if (s.is_null() || s.is_empty())
+ break;
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ nm = composite_glyph_name(s);
+ }
+ else {
+ const char *gn = check_unicode_name(s.contents());
+ if (gn) {
+ const char *gn_decomposed = decompose_unicode(gn);
+ if (gn_decomposed)
+ gn = &gn_decomposed[1];
+ const char *groff_gn = unicode_to_glyph_name(gn);
+ if (groff_gn)
+ nm = symbol(groff_gn);
+ else {
+ char *buf = new char[strlen(gn) + 1 + 1];
+ strcpy(buf, "u");
+ strcat(buf, gn);
+ nm = symbol(buf);
+ delete[] buf;
+ }
+ }
+ else
+ nm = symbol(s.contents());
+ }
+ type = TOKEN_SPECIAL;
+ return;
+ }
+ goto handle_ordinary_char;
+ default:
+ if (cc != escape_char && cc != '.')
+ warning(WARN_ESCAPE, "escape character ignored before %1",
+ input_char_description(cc));
+ goto handle_ordinary_char;
+ }
+ }
+ }
+}
+
+int token::operator==(const token &t)
+{
+ if (type != t.type)
+ return 0;
+ switch(type) {
+ case TOKEN_CHAR:
+ return c == t.c;
+ case TOKEN_SPECIAL:
+ return nm == t.nm;
+ case TOKEN_NUMBERED_CHAR:
+ return val == t.val;
+ default:
+ return 1;
+ }
+}
+
+int token::operator!=(const token &t)
+{
+ return !(*this == t);
+}
+
+// is token a suitable delimiter (like ')?
+
+bool token::usable_as_delimiter(bool report_error)
+{
+ switch(type) {
+ case TOKEN_CHAR:
+ switch(c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '+':
+ case '-':
+ case '/':
+ case '*':
+ case '%':
+ case '<':
+ case '>':
+ case '=':
+ case '&':
+ case ':':
+ case '(':
+ case ')':
+ case '.':
+ if (report_error)
+ error("character '%1' is not allowed as a starting delimiter",
+ char(c));
+ return false;
+ default:
+ return true;
+ }
+ case TOKEN_NODE:
+ // the user doesn't know what a node is
+ if (report_error)
+ error("missing argument or invalid starting delimiter");
+ return false;
+ case TOKEN_SPACE:
+ case TOKEN_STRETCHABLE_SPACE:
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ case TOKEN_HORIZONTAL_SPACE:
+ case TOKEN_TAB:
+ case TOKEN_NEWLINE:
+ if (report_error)
+ error("%1 is not allowed as a starting delimiter", description());
+ return false;
+ default:
+ return true;
+ }
+}
+
+const char *token::description()
+{
+ static char buf[4];
+ switch (type) {
+ case TOKEN_BACKSPACE:
+ return "a backspace character";
+ case TOKEN_CHAR:
+ if (c == INPUT_DELETE)
+ return "a delete character";
+ else {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ buf[3] = '\0';
+ return buf;
+ }
+ case TOKEN_DUMMY:
+ return "an escaped '&'";
+ case TOKEN_ESCAPE:
+ return "an escaped 'e'";
+ case TOKEN_HYPHEN_INDICATOR:
+ return "an escaped '%'";
+ case TOKEN_INTERRUPT:
+ return "an escaped 'c'";
+ case TOKEN_ITALIC_CORRECTION:
+ return "an escaped '/'";
+ case TOKEN_LEADER:
+ return "a leader character";
+ case TOKEN_LEFT_BRACE:
+ return "an escaped '{'";
+ case TOKEN_MARK_INPUT:
+ return "an escaped 'k'";
+ case TOKEN_NEWLINE:
+ return "a newline";
+ case TOKEN_NODE:
+ return "a node";
+ case TOKEN_NUMBERED_CHAR:
+ return "an escaped 'N'";
+ case TOKEN_RIGHT_BRACE:
+ return "an escaped '}'";
+ case TOKEN_SPACE:
+ return "a space";
+ case TOKEN_SPECIAL:
+ return "a special character";
+ case TOKEN_SPREAD:
+ return "an escaped 'p'";
+ case TOKEN_STRETCHABLE_SPACE:
+ return "an escaped '~'";
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ return "an escaped ' '";
+ case TOKEN_HORIZONTAL_SPACE:
+ return "a horizontal motion";
+ case TOKEN_TAB:
+ return "a tab character";
+ case TOKEN_TRANSPARENT:
+ return "an escaped '!'";
+ case TOKEN_TRANSPARENT_DUMMY:
+ return "an escaped ')'";
+ case TOKEN_ZERO_WIDTH_BREAK:
+ return "an escaped ':'";
+ case TOKEN_EOF:
+ return "end of input";
+ default:
+ break;
+ }
+ return "a magic token";
+}
+
+void skip_line()
+{
+ while (!tok.is_newline())
+ if (tok.is_eof())
+ return;
+ else
+ tok.next();
+ tok.next();
+}
+
+void compatible()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ compatible_flag = n != 0;
+ else
+ compatible_flag = 1;
+ skip_line();
+}
+
+static void diagnose_missing_identifier(bool required)
+{
+ if (tok.is_newline() || tok.is_eof()) {
+ if (required)
+ warning(WARN_MISSING, "missing identifier");
+ }
+ else if (tok.is_right_brace() || tok.is_tab()) {
+ const char *start = tok.description();
+ do {
+ tok.next();
+ } while (tok.is_space() || tok.is_right_brace() || tok.is_tab());
+ if (!tok.is_newline() && !tok.is_eof())
+ error("%1 is not allowed before an argument", start);
+ else if (required)
+ warning(WARN_MISSING, "missing identifier");
+ }
+ else if (required)
+ error("expected identifier, got %1", tok.description());
+ else
+ error("expected identifier, got %1; treated as missing",
+ tok.description());
+}
+
+static void diagnose_invalid_identifier()
+{
+ if (!tok.is_newline() && !tok.is_eof() && !tok.is_space()
+ && !tok.is_tab() && !tok.is_right_brace()
+ // We don't want to give a warning for .el\{
+ && !tok.is_left_brace())
+ error("%1 is not allowed in an identifier", tok.description());
+}
+
+symbol get_name(bool required)
+{
+ if (compatible_flag) {
+ char buf[3];
+ tok.skip();
+ if ((buf[0] = tok.ch()) != 0) {
+ tok.next();
+ if ((buf[1] = tok.ch()) != 0) {
+ buf[2] = 0;
+ tok.make_space();
+ }
+ else
+ diagnose_invalid_identifier();
+ return symbol(buf);
+ }
+ else {
+ diagnose_missing_identifier(required);
+ return NULL_SYMBOL;
+ }
+ }
+ else
+ return get_long_name(required);
+}
+
+symbol get_long_name(bool required)
+{
+ return do_get_long_name(required, 0);
+}
+
+static symbol do_get_long_name(bool required, char end)
+{
+ while (tok.is_space())
+ tok.next();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ for (;;) {
+ // If end != 0 we normally have to append a null byte
+ if (i + 2 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
+ break;
+ i++;
+ tok.next();
+ }
+ if (i == 0) {
+ diagnose_missing_identifier(required);
+ return NULL_SYMBOL;
+ }
+ if (end && buf[i] == end)
+ buf[i+1] = '\0';
+ else
+ diagnose_invalid_identifier();
+ if (buf == abuf)
+ return symbol(buf);
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+void exit_troff()
+{
+ is_exit_underway = true;
+ topdiv->set_last_page();
+ if (!end_of_input_macro_name.is_null()) {
+ spring_trap(end_of_input_macro_name);
+ tok.next();
+ process_input_stack();
+ }
+ curenv->final_break();
+ tok.next();
+ process_input_stack();
+ end_diversions();
+ if (topdiv->get_page_length() > 0) {
+ is_eoi_macro_finished = true;
+ topdiv->set_ejecting();
+ static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
+ input_stack::push(make_temp_iterator((char *)buf));
+ topdiv->space(topdiv->get_page_length(), 1);
+ tok.next();
+ process_input_stack();
+ seen_last_page_ejector = 1; // should be set already
+ topdiv->set_ejecting();
+ push_page_ejector();
+ topdiv->space(topdiv->get_page_length(), 1);
+ tok.next();
+ process_input_stack();
+ }
+ cleanup_and_exit(EXIT_SUCCESS);
+}
+
+// This implements .ex. The input stack must be cleared before calling
+// exit_troff().
+
+void exit_request()
+{
+ input_stack::clear();
+ if (is_exit_underway)
+ tok.next();
+ else
+ exit_troff();
+}
+
+void return_macro_request()
+{
+ if (has_arg() && tok.ch())
+ input_stack::pop_macro();
+ input_stack::pop_macro();
+ tok.next();
+}
+
+void eoi_macro()
+{
+ end_of_input_macro_name = get_name();
+ skip_line();
+}
+
+void blank_line_macro()
+{
+ blank_line_macro_name = get_name();
+ skip_line();
+}
+
+void leading_spaces_macro()
+{
+ leading_spaces_macro_name = get_name();
+ skip_line();
+}
+
+static void trapping_blank_line()
+{
+ if (!blank_line_macro_name.is_null())
+ spring_trap(blank_line_macro_name);
+ else
+ blank_line();
+}
+
+void do_request()
+{
+ assert(do_old_compatible_flag == -1);
+ do_old_compatible_flag = compatible_flag;
+ compatible_flag = 0;
+ symbol nm = get_name();
+ if (nm.is_null())
+ skip_line();
+ else
+ interpolate_macro(nm, true /* don't want next token */);
+ compatible_flag = do_old_compatible_flag;
+ do_old_compatible_flag = -1;
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (m)
+ tok.next();
+}
+
+inline int possibly_handle_first_page_transition()
+{
+ if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
+ handle_first_page_transition();
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static int transparent_translate(int cc)
+{
+ if (!is_invalid_input_char(cc)) {
+ charinfo *ci = charset_table[cc];
+ switch (ci->get_special_translation(1)) {
+ case charinfo::TRANSLATE_SPACE:
+ return ' ';
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ return ESCAPE_TILDE;
+ case charinfo::TRANSLATE_DUMMY:
+ return ESCAPE_AMPERSAND;
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ return ESCAPE_PERCENT;
+ }
+ // This is really ugly.
+ ci = ci->get_translation(1);
+ if (ci) {
+ int c = ci->get_ascii_code();
+ if (c != '\0')
+ return c;
+ if (getenv("GROFF_ENABLE_TRANSPARENCY_WARNINGS")
+ != 0 /* nullptr */)
+ error("can't translate %1 to special character '%2'"
+ " in transparent throughput",
+ input_char_description(cc),
+ ci->nm.contents());
+ }
+ }
+ return cc;
+}
+
+class int_stack {
+ struct int_stack_element {
+ int n;
+ int_stack_element *next;
+ } *top;
+public:
+ int_stack();
+ ~int_stack();
+ void push(int);
+ int is_empty();
+ int pop();
+};
+
+int_stack::int_stack()
+{
+ top = 0;
+}
+
+int_stack::~int_stack()
+{
+ while (top != 0) {
+ int_stack_element *temp = top;
+ top = top->next;
+ delete temp;
+ }
+}
+
+int int_stack::is_empty()
+{
+ return top == 0;
+}
+
+void int_stack::push(int n)
+{
+ int_stack_element *p = new int_stack_element;
+ p->next = top;
+ p->n = n;
+ top = p;
+}
+
+int int_stack::pop()
+{
+ assert(top != 0);
+ int_stack_element *p = top;
+ top = top->next;
+ int n = p->n;
+ delete p;
+ return n;
+}
+
+int node::reread(int *)
+{
+ return 0;
+}
+
+int global_diverted_space = 0;
+
+int diverted_space_node::reread(int *bolp)
+{
+ global_diverted_space = 1;
+ if (curenv->get_fill())
+ trapping_blank_line();
+ else
+ curdiv->space(n);
+ global_diverted_space = 0;
+ *bolp = 1;
+ return 1;
+}
+
+int diverted_copy_file_node::reread(int *bolp)
+{
+ curdiv->copy_file(filename.contents());
+ *bolp = 1;
+ return 1;
+}
+
+int word_space_node::reread(int *)
+{
+ if (unformat) {
+ for (width_list *w = orig_width; w; w = w->next)
+ curenv->space(w->width, w->sentence_width);
+ unformat = 0;
+ return 1;
+ }
+ return 0;
+}
+
+int unbreakable_space_node::reread(int *)
+{
+ return 0;
+}
+
+int hmotion_node::reread(int *)
+{
+ if (unformat && was_tab) {
+ curenv->handle_tab(0);
+ unformat = 0;
+ return 1;
+ }
+ return 0;
+}
+
+static int leading_spaces_number = 0;
+static int leading_spaces_space = 0;
+
+void process_input_stack()
+{
+ int_stack trap_bol_stack;
+ int bol = 1;
+ for (;;) {
+ int suppress_next = 0;
+ switch (tok.type) {
+ case token::TOKEN_CHAR:
+ {
+ unsigned char ch = tok.c;
+ if (bol && !have_input
+ && (ch == curenv->control_char
+ || ch == curenv->no_break_control_char)) {
+ break_flag = ch == curenv->control_char;
+ // skip tabs as well as spaces here
+ do {
+ tok.next();
+ } while (tok.is_white_space());
+ symbol nm = get_name();
+#if defined(DEBUGGING)
+ if (debug_state) {
+ if (! nm.is_null()) {
+ if (strcmp(nm.contents(), "test") == 0) {
+ fprintf(stderr, "found it!\n");
+ fflush(stderr);
+ }
+ fprintf(stderr, "interpreting [%s]", nm.contents());
+ if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
+ fprintf(stderr, " currently in diversion: %s",
+ curdiv->get_diversion_name());
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ }
+ }
+#endif
+ if (nm.is_null())
+ skip_line();
+ else {
+ interpolate_macro(nm);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
+ curenv->dump_troff_state();
+ }
+#endif
+ }
+ suppress_next = 1;
+ }
+ else {
+ if (possibly_handle_first_page_transition())
+ ;
+ else {
+ for (;;) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
+ }
+#endif
+ curenv->add_char(charset_table[ch]);
+ tok.next();
+ if (tok.type != token::TOKEN_CHAR)
+ break;
+ ch = tok.c;
+ }
+ suppress_next = 1;
+ bol = 0;
+ }
+ }
+ break;
+ }
+ case token::TOKEN_TRANSPARENT:
+ {
+ if (bol) {
+ if (possibly_handle_first_page_transition())
+ ;
+ else {
+ int cc;
+ do {
+ node *n;
+ cc = get_copy(&n);
+ if (cc != EOF) {
+ if (cc != '\0')
+ curdiv->transparent_output(transparent_translate(cc));
+ else
+ curdiv->transparent_output(n);
+ }
+ } while (cc != '\n' && cc != EOF);
+ if (cc == EOF)
+ curdiv->transparent_output('\n');
+ }
+ }
+ break;
+ }
+ case token::TOKEN_NEWLINE:
+ {
+ if (bol && !old_have_input
+ && !curenv->get_prev_line_interrupted())
+ trapping_blank_line();
+ else {
+ curenv->newline();
+ bol = 1;
+ }
+ break;
+ }
+ case token::TOKEN_REQUEST:
+ {
+ int request_code = tok.c;
+ tok.next();
+ switch (request_code) {
+ case TITLE_REQUEST:
+ title();
+ break;
+ case COPY_FILE_REQUEST:
+ copy_file();
+ break;
+ case TRANSPARENT_FILE_REQUEST:
+ transparent_file();
+ break;
+#ifdef COLUMN
+ case VJUSTIFY_REQUEST:
+ vjustify();
+ break;
+#endif /* COLUMN */
+ default:
+ assert(0);
+ break;
+ }
+ suppress_next = 1;
+ break;
+ }
+ case token::TOKEN_SPACE:
+ {
+ if (possibly_handle_first_page_transition())
+ ;
+ else if (bol && !curenv->get_prev_line_interrupted()) {
+ int nspaces = 0;
+ // save space_width now so that it isn't changed by \f or \s
+ // which we wouldn't notice here
+ hunits space_width = curenv->get_space_width();
+ do {
+ nspaces += tok.nspaces();
+ tok.next();
+ } while (tok.is_space());
+ if (tok.is_newline())
+ trapping_blank_line();
+ else {
+ push_token(tok);
+ leading_spaces_number = nspaces;
+ leading_spaces_space = space_width.to_units() * nspaces;
+ if (!leading_spaces_macro_name.is_null())
+ spring_trap(leading_spaces_macro_name);
+ else {
+ curenv->do_break();
+ curenv->add_node(new hmotion_node(space_width * nspaces,
+ curenv->get_fill_color()));
+ }
+ bol = 0;
+ }
+ }
+ else {
+ curenv->space();
+ bol = 0;
+ }
+ break;
+ }
+ case token::TOKEN_EOF:
+ return;
+ case token::TOKEN_NODE:
+ case token::TOKEN_HORIZONTAL_SPACE:
+ {
+ if (possibly_handle_first_page_transition())
+ ;
+ else if (tok.nd->reread(&bol)) {
+ delete tok.nd;
+ tok.nd = 0;
+ }
+ else {
+ curenv->add_node(tok.nd);
+ tok.nd = 0;
+ bol = 0;
+ curenv->possibly_break_line(1);
+ }
+ break;
+ }
+ case token::TOKEN_PAGE_EJECTOR:
+ {
+ continue_page_eject();
+ // I think we just want to preserve bol.
+ // bol = 1;
+ break;
+ }
+ case token::TOKEN_BEGIN_TRAP:
+ {
+ trap_bol_stack.push(bol);
+ bol = 1;
+ have_input = 0;
+ break;
+ }
+ case token::TOKEN_END_TRAP:
+ {
+ if (trap_bol_stack.is_empty())
+ error("spurious end trap token detected!");
+ else
+ bol = trap_bol_stack.pop();
+ have_input = 0;
+
+ /* I'm not totally happy about this. But I can't think of any other
+ way to do it. Doing an output_pending_lines() whenever a
+ TOKEN_END_TRAP is detected doesn't work: for example,
+
+ .wh -1i x
+ .de x
+ 'bp
+ ..
+ .wh -.5i y
+ .de y
+ .tl ''-%-''
+ ..
+ .br
+ .ll .5i
+ .sp |\n(.pu-1i-.5v
+ a\%very\%very\%long\%word
+
+ will print all but the first lines from the word immediately
+ after the footer, rather than on the next page. */
+
+ if (trap_bol_stack.is_empty())
+ curenv->output_pending_lines();
+ break;
+ }
+ default:
+ {
+ bol = 0;
+ tok.process();
+ break;
+ }
+ }
+ if (!suppress_next)
+ tok.next();
+ trap_sprung_flag = 0;
+ }
+}
+
+#ifdef WIDOW_CONTROL
+
+void flush_pending_lines()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ curenv->output_pending_lines();
+ tok.next();
+}
+
+#endif /* WIDOW_CONTROL */
+
+request_or_macro::request_or_macro()
+{
+}
+
+macro *request_or_macro::to_macro()
+{
+ return 0;
+}
+
+request::request(REQUEST_FUNCP pp) : p(pp)
+{
+}
+
+void request::invoke(symbol, bool)
+{
+ (*p)();
+}
+
+struct char_block {
+ enum { SIZE = 128 };
+ unsigned char s[SIZE];
+ char_block *next;
+ char_block();
+};
+
+char_block::char_block()
+: next(0)
+{
+}
+
+class char_list {
+public:
+ char_list();
+ ~char_list();
+ void append(unsigned char);
+ void set(unsigned char, int);
+ unsigned char get(int);
+ int length();
+private:
+ unsigned char *ptr;
+ int len;
+ char_block *head;
+ char_block *tail;
+ friend class macro_header;
+ friend class string_iterator;
+};
+
+char_list::char_list()
+: ptr(0), len(0), head(0), tail(0)
+{
+}
+
+char_list::~char_list()
+{
+ while (head != 0) {
+ char_block *tem = head;
+ head = head->next;
+ delete tem;
+ }
+}
+
+int char_list::length()
+{
+ return len;
+}
+
+void char_list::append(unsigned char c)
+{
+ if (tail == 0) {
+ head = tail = new char_block;
+ ptr = tail->s;
+ }
+ else {
+ if (ptr >= tail->s + char_block::SIZE) {
+ tail->next = new char_block;
+ tail = tail->next;
+ ptr = tail->s;
+ }
+ }
+ *ptr++ = c;
+ len++;
+}
+
+void char_list::set(unsigned char c, int offset)
+{
+ assert(len > offset);
+ // optimization for access at the end
+ int boundary = len - len % char_block::SIZE;
+ if (offset >= boundary) {
+ *(tail->s + offset - boundary) = c;
+ return;
+ }
+ char_block *tem = head;
+ int l = 0;
+ for (;;) {
+ l += char_block::SIZE;
+ if (l > offset) {
+ *(tem->s + offset % char_block::SIZE) = c;
+ return;
+ }
+ tem = tem->next;
+ }
+}
+
+unsigned char char_list::get(int offset)
+{
+ assert(len > offset);
+ // optimization for access at the end
+ int boundary = len - len % char_block::SIZE;
+ if (offset >= boundary)
+ return *(tail->s + offset - boundary);
+ char_block *tem = head;
+ int l = 0;
+ for (;;) {
+ l += char_block::SIZE;
+ if (l > offset)
+ return *(tem->s + offset % char_block::SIZE);
+ tem = tem->next;
+ }
+}
+
+class node_list {
+ node *head;
+ node *tail;
+public:
+ node_list();
+ ~node_list();
+ void append(node *);
+ int length();
+ node *extract();
+
+ friend class macro_header;
+ friend class string_iterator;
+};
+
+void node_list::append(node *n)
+{
+ if (head == 0) {
+ n->next = 0;
+ head = tail = n;
+ }
+ else {
+ n->next = 0;
+ tail = tail->next = n;
+ }
+}
+
+int node_list::length()
+{
+ int total = 0;
+ for (node *n = head; n != 0; n = n->next)
+ ++total;
+ return total;
+}
+
+node_list::node_list()
+{
+ head = tail = 0;
+}
+
+node *node_list::extract()
+{
+ node *temp = head;
+ head = tail = 0;
+ return temp;
+}
+
+node_list::~node_list()
+{
+ delete_node_list(head);
+}
+
+class macro_header {
+public:
+ int count;
+ char_list cl;
+ node_list nl;
+ macro_header() { count = 1; }
+ macro_header *copy(int);
+};
+
+macro::~macro()
+{
+ if (p != 0 && --(p->count) <= 0)
+ delete p;
+}
+
+macro::macro()
+: is_a_diversion(0), is_a_string(1)
+{
+ if (!input_stack::get_location(1, &filename, &lineno)) {
+ filename = 0;
+ lineno = 0;
+ }
+ len = 0;
+ empty_macro = 1;
+ p = 0;
+}
+
+macro::macro(const macro &m)
+: filename(m.filename), lineno(m.lineno), len(m.len),
+ empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion),
+ is_a_string(m.is_a_string), p(m.p)
+{
+ if (p != 0)
+ p->count++;
+}
+
+macro::macro(int is_div)
+: is_a_diversion(is_div)
+{
+ if (!input_stack::get_location(1, &filename, &lineno)) {
+ filename = 0;
+ lineno = 0;
+ }
+ len = 0;
+ empty_macro = 1;
+ is_a_string = 1;
+ p = 0;
+}
+
+int macro::is_diversion()
+{
+ return is_a_diversion;
+}
+
+int macro::is_string()
+{
+ return is_a_string;
+}
+
+void macro::clear_string_flag()
+{
+ is_a_string = 0;
+}
+
+macro &macro::operator=(const macro &m)
+{
+ // don't assign object
+ if (m.p != 0)
+ m.p->count++;
+ if (p != 0 && --(p->count) <= 0)
+ delete p;
+ p = m.p;
+ filename = m.filename;
+ lineno = m.lineno;
+ len = m.len;
+ empty_macro = m.empty_macro;
+ is_a_diversion = m.is_a_diversion;
+ is_a_string = m.is_a_string;
+ return *this;
+}
+
+void macro::append(unsigned char c)
+{
+ assert(c != 0);
+ if (p == 0)
+ p = new macro_header;
+ if (p->cl.length() != len) {
+ macro_header *tem = p->copy(len);
+ if (--(p->count) <= 0)
+ delete p;
+ p = tem;
+ }
+ p->cl.append(c);
+ ++len;
+ if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
+ empty_macro = 0;
+}
+
+void macro::set(unsigned char c, int offset)
+{
+ assert(p != 0);
+ assert(c != 0);
+ p->cl.set(c, offset);
+}
+
+unsigned char macro::get(int offset)
+{
+ assert(p != 0);
+ return p->cl.get(offset);
+}
+
+int macro::length()
+{
+ return len;
+}
+
+void macro::append_str(const char *s)
+{
+ int i = 0;
+
+ if (s) {
+ while (s[i] != (char)0) {
+ append(s[i]);
+ i++;
+ }
+ }
+}
+
+void macro::append(node *n)
+{
+ assert(n != 0);
+ if (p == 0)
+ p = new macro_header;
+ if (p->cl.length() != len) {
+ macro_header *tem = p->copy(len);
+ if (--(p->count) <= 0)
+ delete p;
+ p = tem;
+ }
+ p->cl.append(0);
+ p->nl.append(n);
+ ++len;
+ empty_macro = 0;
+}
+
+void macro::append_unsigned(unsigned int i)
+{
+ unsigned int j = i / 10;
+ if (j != 0)
+ append_unsigned(j);
+ append(((unsigned char)(((int)'0') + i % 10)));
+}
+
+void macro::append_int(int i)
+{
+ if (i < 0) {
+ append('-');
+ i = -i;
+ }
+ append_unsigned((unsigned int)i);
+}
+
+void macro::print_size()
+{
+ errprint("%1", len);
+}
+
+// make a copy of the first n bytes
+
+macro_header *macro_header::copy(int n)
+{
+ macro_header *p = new macro_header;
+ char_block *bp = cl.head;
+ unsigned char *ptr = bp->s;
+ node *nd = nl.head;
+ while (--n >= 0) {
+ if (ptr >= bp->s + char_block::SIZE) {
+ bp = bp->next;
+ ptr = bp->s;
+ }
+ unsigned char c = *ptr++;
+ p->cl.append(c);
+ if (c == 0) {
+ p->nl.append(nd->copy());
+ nd = nd->next;
+ }
+ }
+ return p;
+}
+
+void print_macros()
+{
+ object_dictionary_iterator iter(request_dictionary);
+ request_or_macro *rm;
+ symbol s;
+ while (iter.get(&s, (object **)&rm)) {
+ assert(!s.is_null());
+ macro *m = rm->to_macro();
+ if (m) {
+ errprint("%1\t", s.contents());
+ m->print_size();
+ errprint("\n");
+ }
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+class string_iterator : public input_iterator {
+ macro mac;
+ const char *how_invoked;
+ int newline_flag;
+ int lineno;
+ char_block *bp;
+ int count; // of characters remaining
+ node *nd;
+ int saved_compatible_flag;
+ int with_break; // inherited from the caller
+protected:
+ symbol nm;
+ string_iterator();
+public:
+ string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
+ int fill(node **);
+ int peek();
+ int get_location(int, const char **, int *);
+ void backtrace();
+ int get_break_flag() { return with_break; }
+ void save_compatible_flag(int f) { saved_compatible_flag = f; }
+ int get_compatible_flag() { return saved_compatible_flag; }
+ int is_diversion();
+};
+
+string_iterator::string_iterator(const macro &m, const char *p, symbol s)
+: input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
+ lineno(1), nm(s)
+{
+ count = mac.len;
+ if (count != 0) {
+ bp = mac.p->cl.head;
+ nd = mac.p->nl.head;
+ ptr = eptr = bp->s;
+ }
+ else {
+ bp = 0;
+ nd = 0;
+ ptr = eptr = 0;
+ }
+ with_break = input_stack::get_break_flag();
+}
+
+string_iterator::string_iterator()
+{
+ bp = 0;
+ nd = 0;
+ ptr = eptr = 0;
+ newline_flag = 0;
+ how_invoked = 0;
+ lineno = 1;
+ count = 0;
+ with_break = input_stack::get_break_flag();
+}
+
+int string_iterator::is_diversion()
+{
+ return mac.is_diversion();
+}
+
+int string_iterator::fill(node **np)
+{
+ if (newline_flag)
+ lineno++;
+ newline_flag = 0;
+ if (count <= 0)
+ return EOF;
+ const unsigned char *p = eptr;
+ if (p >= bp->s + char_block::SIZE) {
+ bp = bp->next;
+ p = bp->s;
+ }
+ if (*p == '\0') {
+ if (np) {
+ *np = nd->copy();
+ if (is_diversion())
+ (*np)->div_nest_level = input_stack::get_div_level();
+ else
+ (*np)->div_nest_level = 0;
+ }
+ nd = nd->next;
+ eptr = ptr = p + 1;
+ count--;
+ return 0;
+ }
+ const unsigned char *e = bp->s + char_block::SIZE;
+ if (e - p > count)
+ e = p + count;
+ ptr = p;
+ while (p < e) {
+ unsigned char c = *p;
+ if (c == '\n' || c == ESCAPE_NEWLINE) {
+ newline_flag = 1;
+ p++;
+ break;
+ }
+ if (c == '\0')
+ break;
+ p++;
+ }
+ eptr = p;
+ count -= p - ptr;
+ return *ptr++;
+}
+
+int string_iterator::peek()
+{
+ if (count <= 0)
+ return EOF;
+ const unsigned char *p = eptr;
+ if (p >= bp->s + char_block::SIZE) {
+ p = bp->next->s;
+ }
+ return *p;
+}
+
+int string_iterator::get_location(int allow_macro,
+ const char **filep, int *linep)
+{
+ if (!allow_macro)
+ return 0;
+ if (mac.filename == 0)
+ return 0;
+ *filep = mac.filename;
+ *linep = mac.lineno + lineno - 1;
+ return 1;
+}
+
+void string_iterator::backtrace()
+{
+ if (mac.filename) {
+ if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ errprint("backtrace: '%1':%2", mac.filename,
+ mac.lineno + lineno - 1);
+ if (how_invoked) {
+ if (!nm.is_null())
+ errprint(": %1 '%2'\n", how_invoked, nm.contents());
+ else
+ errprint(": %1\n", how_invoked);
+ }
+ else
+ errprint("\n");
+ }
+}
+
+class temp_iterator : public input_iterator {
+ unsigned char *base;
+ temp_iterator(const char *, int len);
+public:
+ ~temp_iterator();
+ friend input_iterator *make_temp_iterator(const char *);
+};
+
+#ifdef __GNUG__
+inline
+#endif
+temp_iterator::temp_iterator(const char *s, int len)
+{
+ base = new unsigned char[len];
+ if (len > 0)
+ memcpy(base, s, len);
+ ptr = base;
+ eptr = base + len;
+}
+
+temp_iterator::~temp_iterator()
+{
+ delete[] base;
+}
+
+
+input_iterator *make_temp_iterator(const char *s)
+{
+ if (s == 0)
+ return new temp_iterator(s, 0);
+ else {
+ int n = strlen(s);
+ return new temp_iterator(s, n);
+ }
+}
+
+// this is used when macros with arguments are interpolated
+
+struct arg_list {
+ macro mac;
+ int space_follows;
+ arg_list *next;
+ arg_list(const macro &, int);
+ arg_list(const arg_list *);
+ ~arg_list();
+};
+
+arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
+{
+}
+
+arg_list::arg_list(const arg_list *al)
+: next(0)
+{
+ mac = al->mac;
+ space_follows = al->space_follows;
+ arg_list **a = &next;
+ arg_list *p = al->next;
+ while (p) {
+ *a = new arg_list(p->mac, p->space_follows);
+ p = p->next;
+ a = &(*a)->next;
+ }
+}
+
+arg_list::~arg_list()
+{
+}
+
+class macro_iterator : public string_iterator {
+ arg_list *args;
+ int argc;
+ int with_break; // whether called as .foo or 'foo
+public:
+ macro_iterator(symbol, macro &, const char * = "macro", int = 0);
+ macro_iterator();
+ ~macro_iterator();
+ int has_args() { return 1; }
+ input_iterator *get_arg(int);
+ arg_list *get_arg_list();
+ symbol get_macro_name();
+ int space_follows_arg(int);
+ int get_break_flag() { return with_break; }
+ int nargs() { return argc; }
+ void add_arg(const macro &, int);
+ void shift(int);
+ int is_macro() { return 1; }
+ int is_diversion();
+};
+
+input_iterator *macro_iterator::get_arg(int i)
+{
+ if (i == 0)
+ return make_temp_iterator(nm.contents());
+ if (i > 0 && i <= argc) {
+ arg_list *p = args;
+ for (int j = 1; j < i; j++) {
+ assert(p != 0);
+ p = p->next;
+ }
+ return new string_iterator(p->mac);
+ }
+ else
+ return 0;
+}
+
+arg_list *macro_iterator::get_arg_list()
+{
+ return args;
+}
+
+symbol macro_iterator::get_macro_name()
+{
+ return nm;
+}
+
+int macro_iterator::space_follows_arg(int i)
+{
+ if (i > 0 && i <= argc) {
+ arg_list *p = args;
+ for (int j = 1; j < i; j++) {
+ assert(p != 0);
+ p = p->next;
+ }
+ return p->space_follows;
+ }
+ else
+ return 0;
+}
+
+void macro_iterator::add_arg(const macro &m, int s)
+{
+ arg_list **p;
+ for (p = &args; *p; p = &((*p)->next))
+ ;
+ *p = new arg_list(m, s);
+ ++argc;
+}
+
+void macro_iterator::shift(int n)
+{
+ while (n > 0 && argc > 0) {
+ arg_list *tem = args;
+ args = args->next;
+ delete tem;
+ --argc;
+ --n;
+ }
+}
+
+// This gets used by, e.g., .if '\?xxx\?''.
+
+int operator==(const macro &m1, const macro &m2)
+{
+ if (m1.len != m2.len)
+ return 0;
+ string_iterator iter1(m1);
+ string_iterator iter2(m2);
+ int n = m1.len;
+ while (--n >= 0) {
+ node *nd1 = 0;
+ int c1 = iter1.get(&nd1);
+ assert(c1 != EOF);
+ node *nd2 = 0;
+ int c2 = iter2.get(&nd2);
+ assert(c2 != EOF);
+ if (c1 != c2) {
+ if (c1 == 0)
+ delete nd1;
+ else if (c2 == 0)
+ delete nd2;
+ return 0;
+ }
+ if (c1 == 0) {
+ assert(nd1 != 0);
+ assert(nd2 != 0);
+ int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
+ delete nd1;
+ delete nd2;
+ if (!are_same)
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void interpolate_macro(symbol nm, bool do_not_want_next_token)
+{
+ request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
+ if (p == 0) {
+ int warned = 0;
+ const char *s = nm.contents();
+ if (strlen(s) > 2) {
+ request_or_macro *r;
+ char buf[3];
+ buf[0] = s[0];
+ buf[1] = s[1];
+ buf[2] = '\0';
+ r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
+ if (r) {
+ macro *m = r->to_macro();
+ if (!m || !m->empty())
+ warned = warning(WARN_SPACE,
+ "macro '%1' not defined "
+ "(possibly missing space after '%2')",
+ nm.contents(), buf);
+ }
+ }
+ if (!warned) {
+ warning(WARN_MAC, "macro '%1' not defined", nm.contents());
+ p = new macro;
+ request_dictionary.define(nm, p);
+ }
+ }
+ if (p)
+ p->invoke(nm, do_not_want_next_token);
+ else {
+ skip_line();
+ return;
+ }
+}
+
+static void decode_args(macro_iterator *mi)
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ node *n;
+ int c = get_copy(&n);
+ for (;;) {
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '\n' || c == EOF)
+ break;
+ macro arg;
+ int quote_input_level = 0;
+ int done_tab_warning = 0;
+ arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
+ // we store discarded double quotes for \$^
+ if (c == '"') {
+ arg.append(DOUBLE_QUOTE);
+ quote_input_level = input_stack::get_level();
+ c = get_copy(&n);
+ }
+ while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
+ if (quote_input_level > 0 && c == '"'
+ && (compatible_flag
+ || input_stack::get_level() == quote_input_level)) {
+ arg.append(DOUBLE_QUOTE);
+ c = get_copy(&n);
+ if (c == '"') {
+ arg.append(c);
+ c = get_copy(&n);
+ }
+ else
+ break;
+ }
+ else {
+ if (c == 0)
+ arg.append(n);
+ else {
+ if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
+ warning(WARN_TAB, "tab character in unquoted macro argument");
+ done_tab_warning = 1;
+ }
+ arg.append(c);
+ }
+ c = get_copy(&n);
+ }
+ }
+ arg.append(POP_GROFFCOMP_MODE);
+ mi->add_arg(arg, (c == ' '));
+ }
+ }
+}
+
+static void decode_string_args(macro_iterator *mi)
+{
+ node *n;
+ int c = get_copy(&n);
+ for (;;) {
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '\n' || c == EOF) {
+ error("missing ']'");
+ break;
+ }
+ if (c == ']')
+ break;
+ macro arg;
+ int quote_input_level = 0;
+ int done_tab_warning = 0;
+ if (c == '"') {
+ quote_input_level = input_stack::get_level();
+ c = get_copy(&n);
+ }
+ while (c != EOF && c != '\n'
+ && !(c == ']' && quote_input_level == 0)
+ && !(c == ' ' && quote_input_level == 0)) {
+ if (quote_input_level > 0 && c == '"'
+ && input_stack::get_level() == quote_input_level) {
+ c = get_copy(&n);
+ if (c == '"') {
+ arg.append(c);
+ c = get_copy(&n);
+ }
+ else
+ break;
+ }
+ else {
+ if (c == 0)
+ arg.append(n);
+ else {
+ if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
+ warning(WARN_TAB, "tab character in unquoted string argument");
+ done_tab_warning = 1;
+ }
+ arg.append(c);
+ }
+ c = get_copy(&n);
+ }
+ }
+ mi->add_arg(arg, (c == ' '));
+ }
+}
+
+void macro::invoke(symbol nm, bool do_not_want_next_token)
+{
+ macro_iterator *mi = new macro_iterator(nm, *this);
+ decode_args(mi);
+ input_stack::push(mi);
+ // we must delay tok.next() in case the function has been called by
+ // do_request to assure proper handling of compatible_flag
+ if (!do_not_want_next_token)
+ tok.next();
+}
+
+macro *macro::to_macro()
+{
+ return this;
+}
+
+int macro::empty()
+{
+ return empty_macro == 1;
+}
+
+macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called,
+ int init_args)
+: string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
+{
+ if (init_args) {
+ arg_list *al = input_stack::get_arg_list();
+ if (al) {
+ args = new arg_list(al);
+ argc = input_stack::nargs();
+ }
+ }
+}
+
+macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
+{
+}
+
+macro_iterator::~macro_iterator()
+{
+ while (args != 0) {
+ arg_list *tem = args;
+ args = args->next;
+ delete tem;
+ }
+}
+
+dictionary composite_dictionary(17);
+
+void composite_request()
+{
+ symbol from = get_name(true /* required */);
+ if (!from.is_null()) {
+ const char *from_gn = glyph_name_to_unicode(from.contents());
+ if (!from_gn) {
+ from_gn = check_unicode_name(from.contents());
+ if (!from_gn) {
+ error("invalid composite glyph name '%1'", from.contents());
+ skip_line();
+ return;
+ }
+ }
+ const char *from_decomposed = decompose_unicode(from_gn);
+ if (from_decomposed)
+ from_gn = &from_decomposed[1];
+ symbol to = get_name(true /* required */);
+ if (to.is_null())
+ composite_dictionary.remove(symbol(from_gn));
+ else {
+ const char *to_gn = glyph_name_to_unicode(to.contents());
+ if (!to_gn) {
+ to_gn = check_unicode_name(to.contents());
+ if (!to_gn) {
+ error("invalid composite glyph name '%1'", to.contents());
+ skip_line();
+ return;
+ }
+ }
+ const char *to_decomposed = decompose_unicode(to_gn);
+ if (to_decomposed)
+ to_gn = &to_decomposed[1];
+ if (strcmp(from_gn, to_gn) == 0)
+ composite_dictionary.remove(symbol(from_gn));
+ else
+ (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
+ }
+ }
+ skip_line();
+}
+
+static symbol composite_glyph_name(symbol nm)
+{
+ macro_iterator *mi = new macro_iterator();
+ decode_string_args(mi);
+ input_stack::push(mi);
+ const char *gn = glyph_name_to_unicode(nm.contents());
+ if (!gn) {
+ gn = check_unicode_name(nm.contents());
+ if (!gn) {
+ error("invalid base glyph '%1' in composite glyph name", nm.contents());
+ return EMPTY_SYMBOL;
+ }
+ }
+ const char *gn_decomposed = decompose_unicode(gn);
+ string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
+ string gl;
+ int n = input_stack::nargs();
+ for (int i = 1; i <= n; i++) {
+ glyph_name += '_';
+ input_iterator *p = input_stack::get_arg(i);
+ gl.clear();
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ gl += c;
+ gl += '\0';
+ const char *u = glyph_name_to_unicode(gl.contents());
+ if (!u) {
+ u = check_unicode_name(gl.contents());
+ if (!u) {
+ error("invalid component '%1' in composite glyph name",
+ gl.contents());
+ return EMPTY_SYMBOL;
+ }
+ }
+ const char *decomposed = decompose_unicode(u);
+ if (decomposed)
+ u = &decomposed[1];
+ void *mapped_composite = composite_dictionary.lookup(symbol(u));
+ if (mapped_composite)
+ u = (const char *)mapped_composite;
+ glyph_name += u;
+ }
+ glyph_name += '\0';
+ const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
+ if (groff_gn)
+ return symbol(groff_gn);
+ gl.clear();
+ gl += 'u';
+ gl += glyph_name;
+ return symbol(gl.contents());
+}
+
+int trap_sprung_flag = 0;
+int postpone_traps_flag = 0;
+symbol postponed_trap;
+
+void spring_trap(symbol nm)
+{
+ assert(!nm.is_null());
+ trap_sprung_flag = 1;
+ if (postpone_traps_flag) {
+ postponed_trap = nm;
+ return;
+ }
+ static char buf[2] = { BEGIN_TRAP, '\0' };
+ static char buf2[2] = { END_TRAP, '\0' };
+ input_stack::push(make_temp_iterator(buf2));
+ request_or_macro *p = lookup_request(nm);
+ // We don't perform this validation at the time the trap is planted
+ // because a request name might be replaced by a macro by the time the
+ // trap springs.
+ macro *m = p->to_macro();
+ if (m)
+ input_stack::push(new macro_iterator(nm, *m, "trap-called macro"));
+ else
+ error("trap failed to spring: '%1' is a request", nm.contents());
+ input_stack::push(make_temp_iterator(buf));
+}
+
+void postpone_traps()
+{
+ postpone_traps_flag = 1;
+}
+
+int unpostpone_traps()
+{
+ postpone_traps_flag = 0;
+ if (!postponed_trap.is_null()) {
+ spring_trap(postponed_trap);
+ postponed_trap = NULL_SYMBOL;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void read_request()
+{
+ macro_iterator *mi = new macro_iterator;
+ int reading_from_terminal = isatty(fileno(stdin));
+ int had_prompt = 0;
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c = get_copy(0);
+ while (c == ' ')
+ c = get_copy(0);
+ while (c != EOF && c != '\n' && c != ' ') {
+ if (!is_invalid_input_char(c)) {
+ if (reading_from_terminal)
+ fputc(c, stderr);
+ had_prompt = 1;
+ }
+ c = get_copy(0);
+ }
+ if (c == ' ') {
+ tok.make_space();
+ decode_args(mi);
+ }
+ }
+ if (reading_from_terminal) {
+ fputc(had_prompt ? ':' : '\a', stderr);
+ fflush(stderr);
+ }
+ input_stack::push(mi);
+ macro mac;
+ int nl = 0;
+ int c;
+ while ((c = getchar()) != EOF) {
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ if (c == '\n') {
+ if (nl)
+ break;
+ else
+ nl = 1;
+ }
+ else
+ nl = 0;
+ mac.append(c);
+ }
+ }
+ if (reading_from_terminal)
+ clearerr(stdin);
+ input_stack::push(new string_iterator(mac));
+ tok.next();
+}
+
+enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
+enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
+enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
+
+void do_define_string(define_mode mode, comp_mode comp)
+{
+ symbol nm;
+ node *n = 0; // pacify compiler
+ int c;
+ nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad string definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ macro mac;
+ request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
+ macro *mm = rm ? rm->to_macro() : 0;
+ if (mode == DEFINE_APPEND && mm)
+ mac = *mm;
+ if (comp == COMP_DISABLE)
+ mac.append(PUSH_GROFF_MODE);
+ else if (comp == COMP_ENABLE)
+ mac.append(PUSH_COMP_MODE);
+ while (c != '\n' && c != EOF) {
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append((unsigned char)c);
+ c = get_copy(&n);
+ }
+ if (comp == COMP_DISABLE || comp == COMP_ENABLE)
+ mac.append(POP_GROFFCOMP_MODE);
+ if (!mm) {
+ mm = new macro;
+ request_dictionary.define(nm, mm);
+ }
+ *mm = mac;
+ tok.next();
+}
+
+void define_string()
+{
+ do_define_string(DEFINE_NORMAL,
+ compatible_flag ? COMP_ENABLE: COMP_IGNORE);
+}
+
+void define_nocomp_string()
+{
+ do_define_string(DEFINE_NORMAL, COMP_DISABLE);
+}
+
+void append_string()
+{
+ do_define_string(DEFINE_APPEND,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_nocomp_string()
+{
+ do_define_string(DEFINE_APPEND, COMP_DISABLE);
+}
+
+void do_define_character(char_mode mode, const char *font_name)
+{
+ node *n = 0; // pacify compiler
+ int c;
+ tok.skip();
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_line();
+ return;
+ }
+ if (font_name) {
+ string s(font_name);
+ s += ' ';
+ s += ci->nm.contents();
+ s += '\0';
+ ci = get_charinfo(symbol(s.contents()));
+ }
+ tok.next();
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad character definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ' || c == '\t')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ macro *m = new macro;
+ while (c != '\n' && c != EOF) {
+ if (c == 0)
+ m->append(n);
+ else
+ m->append((unsigned char)c);
+ c = get_copy(&n);
+ }
+ m = ci->setx_macro(m, mode);
+ if (m)
+ delete m;
+ tok.next();
+}
+
+void define_character()
+{
+ do_define_character(CHAR_NORMAL);
+}
+
+void define_fallback_character()
+{
+ do_define_character(CHAR_FALLBACK);
+}
+
+void define_special_character()
+{
+ do_define_character(CHAR_SPECIAL);
+}
+
+static void remove_character()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (!tok.is_space() && !tok.is_tab()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (!ci)
+ break;
+ macro *m = ci->set_macro(0);
+ if (m)
+ delete m;
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+static void interpolate_string(symbol nm)
+{
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot interpolate request '%1'", nm.contents());
+ else {
+ if (m->is_string()) {
+ string_iterator *si = new string_iterator(*m, "string", nm);
+ input_stack::push(si);
+ }
+ else {
+ // if a macro is called as a string, \$0 doesn't get changed
+ macro_iterator *mi = new macro_iterator(input_stack::get_macro_name(),
+ *m, "string", 1);
+ input_stack::push(mi);
+ }
+ }
+}
+
+static void interpolate_string_with_args(symbol nm)
+{
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot interpolate request '%1'", nm.contents());
+ else {
+ macro_iterator *mi = new macro_iterator(nm, *m);
+ decode_string_args(mi);
+ input_stack::push(mi);
+ }
+}
+
+static void interpolate_arg(symbol nm)
+{
+ const char *s = nm.contents();
+ if (!s || *s == '\0')
+ copy_mode_error("missing positional argument number");
+ else if (s[1] == 0 && csdigit(s[0]))
+ input_stack::push(input_stack::get_arg(s[0] - '0'));
+ else if (s[0] == '*' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ for (int i = 1; i <= limit; i++) {
+ input_iterator *p = input_stack::get_arg(i);
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ args += c;
+ if (i != limit)
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else if (s[0] == '@' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ for (int i = 1; i <= limit; i++) {
+ args += '"';
+ args += char(BEGIN_QUOTE);
+ input_iterator *p = input_stack::get_arg(i);
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ args += c;
+ args += char(END_QUOTE);
+ args += '"';
+ if (i != limit)
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else if (s[0] == '^' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ int c = input_stack::peek();
+ for (int i = 1; i <= limit; i++) {
+ input_iterator *p = input_stack::get_arg(i);
+ while ((c = p->get(0)) != EOF) {
+ if (c == DOUBLE_QUOTE)
+ c = '"';
+ args += c;
+ }
+ if (input_stack::space_follows_arg(i))
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else {
+ const char *p;
+ for (p = s; *p && csdigit(*p); p++)
+ ;
+ if (*p)
+ copy_mode_error("invalid positional argument number '%1'", s);
+ else
+ input_stack::push(input_stack::get_arg(atoi(s)));
+ }
+}
+
+void handle_first_page_transition()
+{
+ push_token(tok);
+ topdiv->begin_page();
+}
+
+// We push back a token by wrapping it up in a token_node, and
+// wrapping that up in a string_iterator.
+
+static void push_token(const token &t)
+{
+ macro m;
+ m.append(new token_node(t));
+ input_stack::push(new string_iterator(m));
+}
+
+void push_page_ejector()
+{
+ static char buf[2] = { PAGE_EJECTOR, '\0' };
+ input_stack::push(make_temp_iterator(buf));
+}
+
+void handle_initial_request(unsigned char code)
+{
+ char buf[2];
+ buf[0] = code;
+ buf[1] = '\0';
+ macro mac;
+ mac.append(new token_node(tok));
+ input_stack::push(new string_iterator(mac));
+ input_stack::push(make_temp_iterator(buf));
+ topdiv->begin_page();
+ tok.next();
+}
+
+void handle_initial_title()
+{
+ handle_initial_request(TITLE_REQUEST);
+}
+
+void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
+{
+ symbol nm, term, dot_symbol(".");
+ if (calling == CALLING_INDIRECT) {
+ symbol temp1 = get_name(true /* required */);
+ if (temp1.is_null()) {
+ skip_line();
+ return;
+ }
+ symbol temp2 = get_name();
+ input_stack::push(make_temp_iterator("\n"));
+ if (!temp2.is_null()) {
+ interpolate_string(temp2);
+ input_stack::push(make_temp_iterator(" "));
+ }
+ interpolate_string(temp1);
+ input_stack::push(make_temp_iterator(" "));
+ tok.next();
+ }
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ }
+ term = get_name(); // the request that terminates the definition
+ if (term.is_null())
+ term = dot_symbol;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ const char *start_filename;
+ int start_lineno;
+ int have_start_location = input_stack::get_location(0, &start_filename,
+ &start_lineno);
+ node *n;
+ // doing this here makes the line numbers come out right
+ int c = get_copy(&n, true /* is defining*/);
+ macro mac;
+ macro *mm = 0;
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ request_or_macro *rm =
+ (request_or_macro *)request_dictionary.lookup(nm);
+ if (rm)
+ mm = rm->to_macro();
+ if (mm && mode == DEFINE_APPEND)
+ mac = *mm;
+ }
+ int bol = 1;
+ if (comp == COMP_DISABLE)
+ mac.append(PUSH_GROFF_MODE);
+ else if (comp == COMP_ENABLE)
+ mac.append(PUSH_COMP_MODE);
+ for (;;) {
+ if (c == '\n')
+ mac.clear_string_flag();
+ while (c == ESCAPE_NEWLINE) {
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
+ mac.append(c);
+ c = get_copy(&n, true /* is defining */);
+ }
+ if (bol && c == '.') {
+ const char *s = term.contents();
+ int d = 0;
+ // see if it matches term
+ int i = 0;
+ if (s[0] != 0) {
+ while ((d = get_copy(&n)) == ' ' || d == '\t')
+ ;
+ if ((unsigned char)s[0] == d) {
+ for (i = 1; s[i] != 0; i++) {
+ d = get_copy(&n);
+ if ((unsigned char)s[i] != d)
+ break;
+ }
+ }
+ }
+ if (s[i] == 0
+ && ((i == 2 && compatible_flag)
+ || (d = get_copy(&n)) == ' '
+ || d == '\n')) { // we found it
+ if (d == '\n')
+ tok.make_newline();
+ else
+ tok.make_space();
+ if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
+ if (!mm) {
+ mm = new macro;
+ request_dictionary.define(nm, mm);
+ }
+ if (comp == COMP_DISABLE || comp == COMP_ENABLE)
+ mac.append(POP_GROFFCOMP_MODE);
+ *mm = mac;
+ }
+ if (term != dot_symbol) {
+ ignoring = 0;
+ interpolate_macro(term);
+ }
+ else
+ skip_line();
+ return;
+ }
+ if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
+ mac.append(c);
+ for (int j = 0; j < i; j++)
+ mac.append(s[j]);
+ }
+ c = d;
+ }
+ if (c == EOF) {
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ if (have_start_location)
+ error_with_file_and_line(start_filename, start_lineno,
+ "end of file while defining macro '%1'",
+ nm.contents());
+ else
+ error("end of file while defining macro '%1'", nm.contents());
+ }
+ else {
+ if (have_start_location)
+ error_with_file_and_line(start_filename, start_lineno,
+ "end of file while ignoring input lines");
+ else
+ error("end of file while ignoring input lines");
+ }
+ tok.next();
+ return;
+ }
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append(c);
+ }
+ bol = (c == '\n');
+ c = get_copy(&n, true /* is defining */);
+ }
+}
+
+void define_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void define_nocomp_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
+}
+
+void define_indirect_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void define_indirect_nocomp_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
+}
+
+void append_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_nocomp_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
+}
+
+void append_indirect_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_indirect_nocomp_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
+}
+
+void ignore()
+{
+ ignoring = 1;
+ do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
+ ignoring = 0;
+}
+
+void remove_macro()
+{
+ for (;;) {
+ symbol s = get_name();
+ if (s.is_null())
+ break;
+ request_dictionary.remove(s);
+ }
+ skip_line();
+}
+
+void rename_macro()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null())
+ request_dictionary.rename(s1, s2);
+ }
+ skip_line();
+}
+
+void alias_macro()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null()) {
+ if (!request_dictionary.alias(s1, s2))
+ warning(WARN_MAC, "macro '%1' not defined", s2.contents());
+ }
+ }
+ skip_line();
+}
+
+void chop_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot chop request");
+ else if (m->empty())
+ error("cannot chop empty macro");
+ else {
+ int have_restore = 0;
+ // we have to check for additional save/restore pairs which could be
+ // there due to empty am1 requests.
+ for (;;) {
+ if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
+ break;
+ have_restore = 1;
+ m->len -= 1;
+ if (m->get(m->len - 1) != PUSH_GROFF_MODE
+ && m->get(m->len - 1) != PUSH_COMP_MODE)
+ break;
+ have_restore = 0;
+ m->len -= 1;
+ if (m->len == 0)
+ break;
+ }
+ if (m->len == 0)
+ error("cannot chop empty macro");
+ else {
+ if (have_restore)
+ m->set(POP_GROFFCOMP_MODE, m->len - 1);
+ else
+ m->len -= 1;
+ }
+ }
+ }
+ skip_line();
+}
+
+enum case_xform_mode { STRING_UPCASE, STRING_DOWNCASE };
+
+// Case-transform each byte of the string argument's contents.
+void do_string_case_transform(case_xform_mode mode)
+{
+ assert((mode == STRING_DOWNCASE) || (mode == STRING_UPCASE));
+ symbol s = get_name(true /* required */);
+ if (s.is_null()) {
+ skip_line();
+ return;
+ }
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m) {
+ error("cannot apply string case transformation to a request ('%1')",
+ s.contents());
+ skip_line();
+ return;
+ }
+ string_iterator iter1(*m);
+ macro *mac = new macro;
+ int len = m->macro::length();
+ for (int l = 0; l < len; l++) {
+ int nc, c = iter1.get(0);
+ if (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ nc = c;
+ else if (c == EOF)
+ break;
+ else
+ if (mode == STRING_DOWNCASE)
+ nc = tolower(c);
+ else
+ nc = toupper(c);
+ mac->append(nc);
+ }
+ request_dictionary.define(s, mac);
+ tok.next();
+}
+
+// Uppercase-transform each byte of the string argument's contents.
+void stringdown_request() {
+ do_string_case_transform(STRING_DOWNCASE);
+}
+
+// Lowercase-transform each byte of the string argument's contents.
+void stringup_request() {
+ do_string_case_transform(STRING_UPCASE);
+}
+
+void substring_request()
+{
+ int start; // 0, 1, ..., n-1 or -1, -2, ...
+ symbol s = get_name(true /* required */);
+ if (!s.is_null() && get_integer(&start)) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot apply 'substring' on a request");
+ else {
+ int end = -1;
+ if (!has_arg() || get_integer(&end)) {
+ int real_length = 0; // 1, 2, ..., n
+ string_iterator iter1(*m);
+ for (int l = 0; l < m->len; l++) {
+ int c = iter1.get(0);
+ if (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ continue;
+ if (c == EOF)
+ break;
+ real_length++;
+ }
+ if (start < 0)
+ start += real_length;
+ if (end < 0)
+ end += real_length;
+ if (start > end) {
+ int tem = start;
+ start = end;
+ end = tem;
+ }
+ if (start >= real_length || end < 0) {
+ warning(WARN_RANGE,
+ "start and end index of substring out of range");
+ m->len = 0;
+ if (m->p) {
+ if (--(m->p->count) <= 0)
+ delete m->p;
+ m->p = 0;
+ }
+ skip_line();
+ return;
+ }
+ if (start < 0) {
+ warning(WARN_RANGE,
+ "start index of substring out of range, set to 0");
+ start = 0;
+ }
+ if (end >= real_length) {
+ warning(WARN_RANGE,
+ "end index of substring out of range, set to string length");
+ end = real_length - 1;
+ }
+ // now extract the substring
+ string_iterator iter(*m);
+ int i;
+ for (i = 0; i < start; i++) {
+ int c = iter.get(0);
+ while (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ c = iter.get(0);
+ if (c == EOF)
+ break;
+ }
+ macro mac;
+ for (; i <= end; i++) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ while (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ c = iter.get(0);
+ if (c == EOF)
+ break;
+ if (c == 0)
+ mac.append(nd);
+ else
+ mac.append((unsigned char)c);
+ }
+ *m = mac;
+ }
+ }
+ }
+ skip_line();
+}
+
+void length_request()
+{
+ symbol ret;
+ ret = get_name(true /* required */);
+ if (ret.is_null()) {
+ skip_line();
+ return;
+ }
+ int c;
+ node *n;
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad string definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ int len = 0;
+ while (c != '\n' && c != EOF) {
+ ++len;
+ c = get_copy(&n);
+ }
+ reg *r = (reg*)register_dictionary.lookup(ret);
+ if (r)
+ r->set_value(len);
+ else
+ set_number_reg(ret, len);
+ tok.next();
+}
+
+void asciify_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot asciify request");
+ else {
+ macro am;
+ string_iterator iter(*m);
+ for (;;) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ if (c == EOF)
+ break;
+ if (c != 0)
+ am.append(c);
+ else
+ nd->asciify(&am);
+ }
+ *m = am;
+ }
+ }
+ skip_line();
+}
+
+void unformat_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot unformat request");
+ else {
+ macro am;
+ string_iterator iter(*m);
+ for (;;) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ if (c == EOF)
+ break;
+ if (c != 0)
+ am.append(c);
+ else {
+ if (nd->set_unformat_flag())
+ am.append(nd);
+ }
+ }
+ *m = am;
+ }
+ }
+ skip_line();
+}
+
+static void interpolate_environment_variable(symbol nm)
+{
+ const char *s = getenv(nm.contents());
+ if (s && *s)
+ input_stack::push(make_temp_iterator(s));
+}
+
+void interpolate_number_reg(symbol nm, int inc)
+{
+ reg *r = lookup_number_reg(nm);
+ if (inc < 0)
+ r->decrement();
+ else if (inc > 0)
+ r->increment();
+ input_stack::push(make_temp_iterator(r->get_string()));
+}
+
+static void interpolate_number_format(symbol nm)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r)
+ input_stack::push(make_temp_iterator(r->get_format()));
+}
+
+static int get_delim_number(units *n, unsigned char si, int prev_value)
+{
+ token start;
+ start.next();
+ if (start.usable_as_delimiter(true /* report error */)) {
+ tok.next();
+ if (get_number(n, si, prev_value)) {
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int get_delim_number(units *n, unsigned char si)
+{
+ token start;
+ start.next();
+ if (start.usable_as_delimiter(true /* report error */)) {
+ tok.next();
+ if (get_number(n, si)) {
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int get_line_arg(units *n, unsigned char si, charinfo **cp)
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return 0;
+ tok.next();
+ if (get_number(n, si)) {
+ if (tok.is_dummy() || tok.is_transparent_dummy())
+ tok.next();
+ if (!(start == tok && input_stack::get_level() == start_level)) {
+ *cp = tok.get_char(true /* required */);
+ tok.next();
+ }
+ if (!(start == tok && input_stack::get_level() == start_level))
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ return 0;
+}
+
+static bool read_size(int *x)
+{
+ tok.next();
+ int c = tok.ch();
+ int inc = 0;
+ if (c == '-') {
+ inc = -1;
+ tok.next();
+ c = tok.ch();
+ }
+ else if (c == '+') {
+ inc = 1;
+ tok.next();
+ c = tok.ch();
+ }
+ int val = 0; // pacify compiler
+ bool contains_invalid_digit = false;
+ if (c == '(') {
+ tok.next();
+ c = tok.ch();
+ if (!inc) {
+ // allow an increment either before or after the left parenthesis
+ if (c == '-') {
+ inc = -1;
+ tok.next();
+ c = tok.ch();
+ }
+ else if (c == '+') {
+ inc = 1;
+ tok.next();
+ c = tok.ch();
+ }
+ }
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = c - '0';
+ tok.next();
+ c = tok.ch();
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = val*10 + (c - '0');
+ val *= sizescale;
+ }
+ }
+ }
+ else if (csdigit(c)) {
+ val = c - '0';
+ if (compatible_flag && !inc && c != '0' && c < '4') {
+ // Support legacy \sNN syntax.
+ tok.next();
+ c = tok.ch();
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = val*10 + (c - '0');
+ error("ambiguous type size in escape sequence; rewrite to use"
+ " '%1s(%2' or similar", static_cast<char>(escape_char),
+ val);
+ }
+ }
+ val *= sizescale;
+ }
+ else if (!tok.usable_as_delimiter(true /* report error */))
+ return false;
+ else {
+ token start(tok);
+ tok.next();
+ c = tok.ch();
+ if (!inc && (c == '-' || c == '+')) {
+ inc = c == '+' ? 1 : -1;
+ tok.next();
+ }
+ if (!get_number(&val, 'z'))
+ return false;
+ if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
+ if (start.ch() == '[')
+ error("missing ']' in type size escape sequence");
+ else
+ error("missing closing delimiter in type size escape sequence");
+ return false;
+ }
+ }
+ if (contains_invalid_digit) {
+ if (c)
+ error("expected valid digit in type size escape sequence, got %1",
+ input_char_description(c));
+ else
+ error("invalid digit in type size escape sequence");
+ return false;
+ }
+ else {
+ switch (inc) {
+ case 0:
+ if (val == 0) {
+ // special case -- point size 0 means "revert to previous size"
+ *x = 0;
+ return true;
+ }
+ *x = val;
+ break;
+ case 1:
+ *x = curenv->get_requested_point_size() + val;
+ break;
+ case -1:
+ *x = curenv->get_requested_point_size() - val;
+ break;
+ default:
+ assert(0);
+ }
+ if (*x <= 0) {
+ warning(WARN_RANGE,
+ "type size escape sequence results in non-positive size"
+ " %1u; setting it to 1u", *x);
+ *x = 1;
+ }
+ return true;
+ }
+}
+
+static symbol get_delim_name()
+{
+ token start;
+ start.next();
+ if (start.is_eof()) {
+ error("end of input at start of delimited name");
+ return NULL_SYMBOL;
+ }
+ if (start.is_newline()) {
+ error("can't delimit name with a newline");
+ return NULL_SYMBOL;
+ }
+ int start_level = input_stack::get_level();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ for (;;) {
+ if (i + 1 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ tok.next();
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if ((buf[i] = tok.ch()) == 0) {
+ error("missing delimiter (got %1)", tok.description());
+ if (buf != abuf)
+ delete[] buf;
+ return NULL_SYMBOL;
+ }
+ i++;
+ }
+ buf[i] = '\0';
+ if (buf == abuf) {
+ if (i == 0) {
+ error("empty delimited name");
+ return NULL_SYMBOL;
+ }
+ else
+ return symbol(buf);
+ }
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+// Implement \R
+
+static void do_register()
+{
+ token start;
+ start.next();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return;
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ return;
+ while (tok.is_space())
+ tok.next();
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ int prev_value;
+ if (!r || !r->get_value(&prev_value))
+ prev_value = 0;
+ int val;
+ if (!get_number(&val, 'u', prev_value))
+ return;
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ if (r)
+ r->set_value(val);
+ else
+ set_number_reg(nm, val);
+}
+
+// this implements the \w escape sequence
+
+static void do_width()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " width computation escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ tok.process();
+ }
+ env.wrap_up_tab();
+ units x = env.get_input_line_position().to_units();
+ input_stack::push(make_temp_iterator(i_to_a(x)));
+ env.width_registers();
+ curenv = oldenv;
+ have_input = 0;
+}
+
+charinfo *page_character;
+
+void set_page_character()
+{
+ page_character = get_optional_char();
+ skip_line();
+}
+
+static const symbol percent_symbol("%");
+
+void read_title_parts(node **part, hunits *part_width)
+{
+ tok.skip();
+ if (tok.is_newline() || tok.is_eof())
+ return;
+ token start(tok);
+ int start_level = input_stack::get_level();
+ tok.next();
+ for (int i = 0; i < 3; i++) {
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level)) {
+ tok.next();
+ break;
+ }
+ if (page_character != 0 && tok.get_char() == page_character)
+ interpolate_number_reg(percent_symbol, 0);
+ else
+ tok.process();
+ tok.next();
+ }
+ curenv->wrap_up_tab();
+ part_width[i] = curenv->get_input_line_position();
+ part[i] = curenv->extract_output_line();
+ }
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+}
+
+class non_interpreted_node : public node {
+ macro mac;
+public:
+ non_interpreted_node(const macro &);
+ int interpret(macro *);
+ node *copy();
+ int ends_sentence();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
+{
+}
+
+int non_interpreted_node::ends_sentence()
+{
+ return 2;
+}
+
+int non_interpreted_node::same(node *nd)
+{
+ return mac == ((non_interpreted_node *)nd)->mac;
+}
+
+const char *non_interpreted_node::type()
+{
+ return "non_interpreted_node";
+}
+
+int non_interpreted_node::force_tprint()
+{
+ return 0;
+}
+
+int non_interpreted_node::is_tag()
+{
+ return 0;
+}
+
+node *non_interpreted_node::copy()
+{
+ return new non_interpreted_node(mac);
+}
+
+int non_interpreted_node::interpret(macro *m)
+{
+ string_iterator si(mac);
+ node *n = 0; // pacify compiler
+ for (;;) {
+ int c = si.get(&n);
+ if (c == EOF)
+ break;
+ if (c == 0)
+ m->append(n);
+ else
+ m->append(c);
+ }
+ return 1;
+}
+
+static node *do_non_interpreted()
+{
+ node *n;
+ int c;
+ macro mac;
+ while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append(c);
+ if (c == EOF || c == '\n') {
+ error("unterminated transparent embedding escape sequence");
+ return 0;
+ }
+ return new non_interpreted_node(mac);
+}
+
+static void encode_char(macro *mac, char c)
+{
+ if (c == '\0') {
+ if (tok.is_stretchable_space()
+ || tok.is_unstretchable_space())
+ mac->append(' ');
+ else if (tok.is_special()) {
+ const char *sc;
+ if (font::use_charnames_in_special) {
+ charinfo *ci = tok.get_char(true /* required */);
+ sc = ci->get_symbol()->contents();
+ }
+ else
+ sc = tok.get_char()->get_symbol()->contents();
+ if (strcmp("-", sc) == 0)
+ mac->append('-');
+ else if (strcmp("aq", sc) == 0)
+ mac->append('\'');
+ else if (strcmp("dq", sc) == 0)
+ mac->append('"');
+ else if (strcmp("ga", sc) == 0)
+ mac->append('`');
+ else if (strcmp("ha", sc) == 0)
+ mac->append('^');
+ else if (strcmp("rs", sc) == 0)
+ mac->append('\\');
+ else if (strcmp("ti", sc) == 0)
+ mac->append('~');
+ else {
+ if (font::use_charnames_in_special) {
+ if (sc[0] != (char)0) {
+ mac->append('\\');
+ mac->append('[');
+ int i = 0;
+ while (sc[i] != (char)0) {
+ mac->append(sc[i]);
+ i++;
+ }
+ mac->append(']');
+ }
+ else
+ error("special character '%1' cannot be used within"
+ " device control escape sequence", sc);
+ }
+ }
+ }
+ else if (!(tok.is_hyphen_indicator()
+ || tok.is_dummy()
+ || tok.is_transparent_dummy()
+ || tok.is_zero_width_break()))
+ error("%1 is invalid within device control escape sequence",
+ tok.description());
+ }
+ else {
+ if ((font::use_charnames_in_special) && (c == '\\')) {
+ /*
+ * add escape escape sequence
+ */
+ mac->append(c);
+ }
+ mac->append(c);
+ }
+}
+
+static node *do_special()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ macro mac;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in device control"
+ " escape sequence (got %1)", tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ unsigned char c;
+ if (tok.is_space())
+ c = ' ';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (tok.is_leader())
+ c = '\001';
+ else if (tok.is_backspace())
+ c = '\b';
+ else
+ c = tok.ch();
+ encode_char(&mac, c);
+ }
+ return new special_node(mac);
+}
+
+void device_request()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ macro mac;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ mac.append(c);
+ curenv->add_node(new special_node(mac));
+ }
+ tok.next();
+}
+
+void device_macro_request()
+{
+ symbol s = get_name(true /* required */);
+ if (!(s.is_null() || s.is_empty())) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (m)
+ curenv->add_node(new special_node(*m));
+ else
+ error("can't transparently throughput a request");
+ }
+ skip_line();
+}
+
+void output_request()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ topdiv->transparent_output(c);
+ topdiv->transparent_output('\n');
+ }
+ tok.next();
+}
+
+extern int image_no; // from node.cpp
+
+static node *do_suppress(symbol nm)
+{
+ if (nm.is_null() || nm.is_empty()) {
+ error("output suppression escape sequence requires an argument");
+ return 0;
+ }
+ const char *s = nm.contents();
+ switch (*s) {
+ case '0':
+ if (begin_level == 0)
+ // suppress generation of glyphs
+ return new suppress_node(0, 0);
+ break;
+ case '1':
+ if (begin_level == 0)
+ // enable generation of glyphs
+ return new suppress_node(1, 0);
+ break;
+ case '2':
+ if (begin_level == 0)
+ return new suppress_node(1, 1);
+ break;
+ case '3':
+ have_input = 1;
+ begin_level++;
+ break;
+ case '4':
+ have_input = 1;
+ begin_level--;
+ break;
+ case '5':
+ {
+ s++; // move over '5'
+ char position = *s;
+ if (*s == (char)0) {
+ error("missing position and filename in output suppression"
+ " escape sequence");
+ return 0;
+ }
+ if (!(position == 'l'
+ || position == 'r'
+ || position == 'c'
+ || position == 'i')) {
+ error("expected position 'l', 'r', 'c', or 'i' in output"
+ " suppression escape sequence, got '%1'", position);
+ return 0;
+ }
+ s++; // onto image name
+ if (s == (char *)0) {
+ error("missing image name in output suppression escape"
+ " sequence");
+ return 0;
+ }
+ image_no++;
+ if (begin_level == 0)
+ return new suppress_node(symbol(s), position, image_no);
+ else
+ have_input = 1;
+ }
+ break;
+ default:
+ error("invalid argument '%1' to output suppression escape sequence",
+ *s);
+ }
+ return 0;
+}
+
+void special_node::tprint(troff_output_file *out)
+{
+ tprint_start(out);
+ string_iterator iter(mac);
+ for (;;) {
+ int c = iter.get(0);
+ if (c == EOF)
+ break;
+ for (const char *s = ::asciify(c); *s; s++)
+ tprint_char(out, *s);
+ }
+ tprint_end(out);
+}
+
+int get_file_line(const char **filename, int *lineno)
+{
+ return input_stack::get_location(0, filename, lineno);
+}
+
+void line_file()
+{
+ int n;
+ if (get_integer(&n)) {
+ const char *filename = 0;
+ if (has_arg()) {
+ symbol s = get_long_name();
+ filename = s.contents();
+ }
+ (void)input_stack::set_location(filename, n-1);
+ }
+ skip_line();
+}
+
+static int nroff_mode = 0;
+
+static void nroff_request()
+{
+ nroff_mode = 1;
+ skip_line();
+}
+
+static void troff_request()
+{
+ nroff_mode = 0;
+ skip_line();
+}
+
+static void skip_alternative()
+{
+ int level = 0;
+ // ensure that ".if 0\{" works as expected
+ if (tok.is_left_brace())
+ level++;
+ int c;
+ for (;;) {
+ c = input_stack::get(0);
+ if (c == EOF)
+ break;
+ if (c == ESCAPE_LEFT_BRACE)
+ ++level;
+ else if (c == ESCAPE_RIGHT_BRACE)
+ --level;
+ else if (c == escape_char && escape_char > 0)
+ switch(input_stack::get(0)) {
+ case '{':
+ ++level;
+ break;
+ case '}':
+ --level;
+ break;
+ case '"':
+ while ((c = input_stack::get(0)) != '\n' && c != EOF)
+ ;
+ }
+ /*
+ Note that the level can properly be < 0, e.g.
+
+ .if 1 \{\
+ .if 0 \{\
+ .\}\}
+
+ So don't give an error message in this case.
+ */
+ if (level <= 0 && c == '\n')
+ break;
+ }
+ tok.next();
+}
+
+static void begin_alternative()
+{
+ while (tok.is_space() || tok.is_left_brace())
+ tok.next();
+}
+
+void nop_request()
+{
+ while (tok.is_space())
+ tok.next();
+}
+
+static int_stack if_else_stack;
+
+int do_if_request()
+{
+ int invert = 0;
+ while (tok.is_space())
+ tok.next();
+ while (tok.ch() == '!') {
+ tok.next();
+ invert = !invert;
+ }
+ int result;
+ unsigned char c = tok.ch();
+ if (compatible_flag)
+ switch (c) {
+ case 'F':
+ case 'S':
+ case 'c':
+ case 'd':
+ case 'm':
+ case 'r':
+ warning(WARN_SYNTAX,
+ "conditional operator '%1' used in compatibility mode",
+ c);
+ break;
+ default:
+ break;
+ }
+ if (c == 't') {
+ tok.next();
+ result = !nroff_mode;
+ }
+ else if (c == 'n') {
+ tok.next();
+ result = nroff_mode;
+ }
+ else if (c == 'v') {
+ tok.next();
+ result = 0;
+ }
+ else if (c == 'o') {
+ result = (topdiv->get_page_number() & 1);
+ tok.next();
+ }
+ else if (c == 'e') {
+ result = !(topdiv->get_page_number() & 1);
+ tok.next();
+ }
+ else if (c == 'd' || c == 'r') {
+ tok.next();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = (c == 'd'
+ ? request_dictionary.lookup(nm) != 0
+ : register_dictionary.lookup(nm) != 0);
+ }
+ else if (c == 'm') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = (nm == default_symbol
+ || color_dictionary.lookup(nm) != 0);
+ }
+ else if (c == 'c') {
+ tok.next();
+ tok.skip();
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_alternative();
+ return 0;
+ }
+ result = character_exists(ci, curenv);
+ tok.next();
+ }
+ else if (c == 'F') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = check_font(curenv->get_family()->nm, nm);
+ }
+ else if (c == 'S') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = check_style(nm);
+ }
+ else if (tok.is_space())
+ result = 0;
+ else if (tok.usable_as_delimiter()) {
+ token delim = tok;
+ int delim_level = input_stack::get_level();
+ environment env1(curenv);
+ environment env2(curenv);
+ environment *oldenv = curenv;
+ curenv = &env1;
+ suppress_push = 1;
+ for (int i = 0; i < 2; i++) {
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in output"
+ " comparison operator (got %1)", tok.description());
+ tok.next();
+ curenv = oldenv;
+ return 0;
+ }
+ if (tok == delim
+ && (compatible_flag || input_stack::get_level() == delim_level))
+ break;
+ tok.process();
+ }
+ curenv = &env2;
+ }
+ node *n1 = env1.extract_output_line();
+ node *n2 = env2.extract_output_line();
+ result = same_node_list(n1, n2);
+ delete_node_list(n1);
+ delete_node_list(n2);
+ curenv = oldenv;
+ have_input = 0;
+ suppress_push = 0;
+ tok.next();
+ }
+ else {
+ units n;
+ if (!get_number(&n, 'u')) {
+ skip_alternative();
+ return 0;
+ }
+ else
+ result = n > 0;
+ }
+ if (invert)
+ result = !result;
+ if (result)
+ begin_alternative();
+ else
+ skip_alternative();
+ return result;
+}
+
+void if_else_request()
+{
+ if_else_stack.push(do_if_request());
+}
+
+void if_request()
+{
+ do_if_request();
+}
+
+void else_request()
+{
+ if (if_else_stack.is_empty()) {
+ warning(WARN_EL, "unbalanced 'el' request");
+ skip_alternative();
+ }
+ else {
+ if (if_else_stack.pop())
+ skip_alternative();
+ else
+ begin_alternative();
+ }
+}
+
+static int while_depth = 0;
+static int while_break_flag = 0;
+
+void while_request()
+{
+ macro mac;
+ int escaped = 0;
+ int level = 0;
+ mac.append(new token_node(tok));
+ for (;;) {
+ node *n = 0; // pacify compiler
+ int c = input_stack::get(&n);
+ if (c == EOF)
+ break;
+ if (c == 0) {
+ escaped = 0;
+ mac.append(n);
+ }
+ else if (escaped) {
+ if (c == '{')
+ level += 1;
+ else if (c == '}')
+ level -= 1;
+ escaped = 0;
+ mac.append(c);
+ }
+ else {
+ if (c == ESCAPE_LEFT_BRACE)
+ level += 1;
+ else if (c == ESCAPE_RIGHT_BRACE)
+ level -= 1;
+ else if (c == escape_char)
+ escaped = 1;
+ mac.append(c);
+ if (c == '\n' && level <= 0)
+ break;
+ }
+ }
+ if (level != 0)
+ error("unbalanced brace escape sequences");
+ else {
+ while_depth++;
+ input_stack::add_boundary();
+ for (;;) {
+ input_stack::push(new string_iterator(mac, "while loop"));
+ tok.next();
+ if (!do_if_request()) {
+ while (input_stack::get(0) != EOF)
+ ;
+ break;
+ }
+ process_input_stack();
+ if (while_break_flag || input_stack::is_return_boundary()) {
+ while_break_flag = 0;
+ break;
+ }
+ }
+ input_stack::remove_boundary();
+ while_depth--;
+ }
+ tok.next();
+}
+
+void while_break_request()
+{
+ if (!while_depth) {
+ error("no while loop");
+ skip_line();
+ }
+ else {
+ while_break_flag = 1;
+ while (input_stack::get(0) != EOF)
+ ;
+ tok.next();
+ }
+}
+
+void while_continue_request()
+{
+ if (!while_depth) {
+ error("no while loop");
+ skip_line();
+ }
+ else {
+ while (input_stack::get(0) != EOF)
+ ;
+ tok.next();
+ }
+}
+
+void do_source(bool quietly)
+{
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ skip_line();
+ else {
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(nm.contents());
+ if (fp)
+ input_stack::push(new file_iterator(fp, nm.contents()));
+ else
+ // Suppress diagnostic only if we're operating quietly and it's an
+ // expected problem.
+ if (!(quietly && (ENOENT == errno)))
+ error("can't open '%1': %2", nm.contents(), strerror(errno));
+ tok.next();
+ }
+}
+
+// .so
+
+void source()
+{
+ do_source(false /* not quietly*/ );
+}
+
+// .soquiet: like .so, but silently ignore files that can't be opened
+// due to their nonexistence
+
+void source_quietly()
+{
+ do_source(true /* quietly */ );
+}
+
+// like .so but use popen()
+
+void pipe_source()
+{
+ if (!unsafe_flag) {
+ error("'pso' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+#ifdef POPEN_MISSING
+ error("pipes not available on this system");
+ skip_line();
+#else /* not POPEN_MISSING */
+ if (tok.is_newline() || tok.is_eof())
+ error("missing command");
+ else {
+ int c;
+ while ((c = get_copy(0)) == ' ' || c == '\t')
+ ;
+ int buf_size = 24;
+ char *buf = new char[buf_size];
+ int buf_used = 0;
+ for (; c != '\n' && c != EOF; c = get_copy(0)) {
+ const char *s = asciify(c);
+ int slen = strlen(s);
+ if (buf_used + slen + 1> buf_size) {
+ char *old_buf = buf;
+ int old_buf_size = buf_size;
+ buf_size *= 2;
+ buf = new char[buf_size];
+ memcpy(buf, old_buf, old_buf_size);
+ delete[] old_buf;
+ }
+ strcpy(buf + buf_used, s);
+ buf_used += slen;
+ }
+ buf[buf_used] = '\0';
+ errno = 0;
+ FILE *fp = popen(buf, POPEN_RT);
+ if (fp)
+ input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
+ else
+ error("can't open pipe to process '%1': %2", buf, strerror(errno));
+ delete[] buf;
+ }
+ tok.next();
+#endif /* not POPEN_MISSING */
+ }
+}
+
+// .psbb
+//
+// Extract bounding box limits from PostScript file, and assign
+// them to the following four gtroff registers:--
+//
+static int llx_reg_contents = 0;
+static int lly_reg_contents = 0;
+static int urx_reg_contents = 0;
+static int ury_reg_contents = 0;
+
+// Manifest constants to specify the status of bounding box range
+// acquisition; (note that PSBB_RANGE_IS_BAD is also suitable for
+// assignment as a default ordinate property value).
+//
+#define PSBB_RANGE_IS_BAD 0
+#define PSBB_RANGE_IS_SET 1
+#define PSBB_RANGE_AT_END 2
+
+// Maximum input line length, for DSC conformance, and options to
+// control how it will be enforced; caller should select either of
+// DSC_LINE_MAX_IGNORED, to allow partial line collection spread
+// across multiple calls, or DSC_LINE_MAX_ENFORCE, to truncate
+// excess length lines at the DSC limit.
+//
+// Note that DSC_LINE_MAX_CHECKED is reserved for internal use by
+// ps_locator::get_line(), and should not be specified in any call;
+// also, handling of DSC_LINE_MAX_IGNORED, as a get_line() option,
+// is currently unimplemented.
+//
+#define DSC_LINE_MAX 255
+#define DSC_LINE_MAX_IGNORED -1
+#define DSC_LINE_MAX_ENFORCE 0
+#define DSC_LINE_MAX_CHECKED 1
+
+// Input characters to be considered as white space, when reading
+// PostScript file comments.
+//
+cset white_space("\n\r \t");
+
+// Class psbb_locator
+//
+// This locally declared and implemented class provides the methods
+// to be used for retrieval of bounding box properties from a specified
+// PostScript or PDF file.
+//
+class psbb_locator
+{
+ public:
+ // Only the class constructor is exposed publicly; instantiation of
+ // a class object will retrieve the requisite bounding box properties
+ // from the specified file, and assign them to gtroff registers.
+ //
+ psbb_locator(const char *);
+
+ private:
+ FILE *fp;
+ const char *filename;
+ char buf[2 + DSC_LINE_MAX];
+ int llx, lly, urx, ury;
+
+ // CRLF handling hook, for get_line() function.
+ //
+ int lastc;
+
+ // Private method functions facilitate implementation of the
+ // class constructor; none are used in any other context.
+ //
+ int get_line(int);
+ inline bool get_header_comment(void);
+ inline const char *context_args(const char *);
+ inline const char *context_args(const char *, const char *);
+ inline const char *bounding_box_args(void);
+ int parse_bounding_box(const char *);
+ inline void assign_registers(void);
+ inline int skip_to_trailer(void);
+};
+
+// psbb_locator class constructor.
+//
+psbb_locator::psbb_locator(const char *fname):
+filename(fname), llx(0), lly(0), urx(0), ury(0), lastc(EOF)
+{
+ // PS files might contain non-printable characters, such as ^Z
+ // and CRs not followed by an LF, so open them in binary mode.
+ //
+ fp = include_search_path.open_file_cautious(filename, 0, FOPEN_RB);
+ if (fp) {
+ // After successfully opening the file, acquire the first
+ // line, whence we may determine the file format...
+ //
+ if (get_line(DSC_LINE_MAX_ENFORCE) == 0)
+ //
+ // ...except in the case of an empty file, which we are
+ // unable to process further.
+ //
+ error("'%1' is empty", filename);
+
+# if 0
+ else if (context_args("%PDF-")) {
+ // TODO: PDF files specify a /MediaBox, as the equivalent
+ // of %%BoundingBox; we must implement a handler for this.
+ }
+# endif
+
+ else if (context_args("%!PS-Adobe-")) {
+ //
+ // PostScript files -- strictly, we expect EPS -- should
+ // specify a %%BoundingBox comment; locate it, initially
+ // expecting to find it in the comments header...
+ //
+ const char *context = NULL;
+ while ((context == NULL) && get_header_comment()) {
+ if ((context = bounding_box_args()) != NULL) {
+
+ // When the "%%BoundingBox" comment is found, it may simply
+ // specify the bounding box property values, or it may defer
+ // assignment to a similar trailer comment...
+ //
+ int status = parse_bounding_box(context);
+ if (status == PSBB_RANGE_AT_END) {
+ //
+ // ...in which case we must locate the trailer, and search
+ // for the appropriate specification within it.
+ //
+ if (skip_to_trailer() > 0) {
+ while ((context = bounding_box_args()) == NULL
+ && get_line(DSC_LINE_MAX_ENFORCE) > 0)
+ ;
+ if (context != NULL) {
+ //
+ // When we find a bounding box specification here...
+ //
+ if ((status = parse_bounding_box(context)) == PSBB_RANGE_AT_END)
+ //
+ // ...we must ensure it is not a further attempt to defer
+ // assignment to a trailer, (which we are already parsing).
+ //
+ error("'(atend)' is not allowed in trailer of '%1'",
+ filename);
+ }
+ }
+ else
+ // The trailer could not be found, so there is no context in
+ // which a trailing %%BoundingBox comment might be located.
+ //
+ context = NULL;
+ }
+ if (status == PSBB_RANGE_IS_BAD) {
+ //
+ // This arises when we found a %%BoundingBox comment, but
+ // we were unable to extract a valid set of range values from
+ // it; all we can do is diagnose this.
+ //
+ error("the arguments to the %%%%BoundingBox comment in '%1' are bad",
+ filename);
+ }
+ }
+ }
+ if (context == NULL)
+ //
+ // Conversely, this arises when no value specifying %%BoundingBox
+ // comment has been found, in any appropriate location...
+ //
+ error("%%%%BoundingBox comment not found in '%1'", filename);
+ }
+ else
+ // ...while this indicates that there was no appropriate file format
+ // identifier, on the first line of the input file.
+ //
+ error("'%1' does not conform to the Document Structuring Conventions",
+ filename);
+
+ // Regardless of success or failure of bounding box property acquisition,
+ // we did successfully open an input file, so we must now close it...
+ //
+ fclose(fp);
+ }
+ else
+ // ...but in this case, we did not successfully open any input file.
+ //
+ error("can't open '%1': %2", filename, strerror(errno));
+
+ // Irrespective of whether or not we were able to successfully acquire the
+ // bounding box properties, we ALWAYS update the associated gtroff registers.
+ //
+ assign_registers();
+}
+
+// psbb_locator::parse_bounding_box()
+//
+// Parse the argument to a %%BoundingBox comment, returning:
+// PSBB_RANGE_IS_SET if it contains four numbers,
+// PSBB_RANGE_AT_END if it contains "(atend)", or
+// PSBB_RANGE_IS_BAD otherwise.
+//
+int psbb_locator::parse_bounding_box(const char *context)
+{
+ // The Document Structuring Conventions say that the numbers
+ // should be integers.
+ //
+ int status = PSBB_RANGE_IS_SET;
+ if (sscanf(context, "%d %d %d %d", &llx, &lly, &urx, &ury) != 4) {
+ //
+ // Unfortunately some broken applications get this wrong;
+ // try to parse them as doubles instead...
+ //
+ double x1, x2, x3, x4;
+ if (sscanf(context, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
+ llx = (int)x1;
+ lly = (int)x2;
+ urx = (int)x3;
+ ury = (int)x4;
+ }
+ else {
+ // ...but if we can't parse four numbers, skip over any
+ // initial white space...
+ //
+ while (*context == '\x20' || *context == '\t')
+ context++;
+
+ // ...before checking for "(atend)", and setting the
+ // appropriate exit status accordingly.
+ //
+ status = (context_args("(atend)", context) == NULL)
+ ? llx = lly = urx = ury = PSBB_RANGE_IS_BAD
+ : PSBB_RANGE_AT_END;
+ }
+ }
+ return status;
+}
+
+// ps_locator::get_line()
+//
+// Collect an input record from a PostScript or PDF file.
+//
+// Inputs:
+// buf pointer to caller's input buffer.
+// fp FILE stream pointer, whence input is read.
+// filename name of input file, (for diagnostic use only).
+// dscopt DSC_LINE_MAX_ENFORCE or DSC_LINE_MAX_IGNORED.
+//
+// Returns the number of input characters stored into caller's
+// buffer, or zero at end of input stream.
+//
+// FIXME: Currently, get_line() always scans an entire line of
+// input, but returns only as much as will fit in caller's buffer;
+// the return value is always a positive integer, or zero, with no
+// way of indicating to caller, that there was more data than the
+// buffer could accommodate. A future enhancement could mitigate
+// this, returning a negative value in the event of truncation, or
+// even allowing for piecewise retrieval of excessively long lines
+// in successive reads; (this may be necessary to properly support
+// DSC_LINE_MAX_IGNORED, which is currently unimplemented).
+//
+int psbb_locator::get_line(int dscopt)
+{
+ int c, count = 0;
+ do {
+ // Collect input characters into caller's buffer, until we
+ // encounter a line terminator, or end of file...
+ //
+ while (((c = getc(fp)) != '\n') && (c != '\r') && (c != EOF)) {
+ if ((((lastc = c) < 0x1b) && !white_space(c)) || (c == 0x7f))
+ //
+ // ...rejecting any which may be designated as invalid.
+ //
+ error("invalid input character code %1 in '%2'", int(c), filename);
+
+ // On reading a valid input character, and when there is
+ // room in caller's buffer...
+ //
+ else if (count < DSC_LINE_MAX)
+ //
+ // ...store it.
+ //
+ buf[count++] = c;
+
+ // We have a valid input character, but it will not fit
+ // into caller's buffer; if enforcing DSC conformity...
+ //
+ else if (dscopt == DSC_LINE_MAX_ENFORCE) {
+ //
+ // ...diagnose and truncate.
+ //
+ dscopt = DSC_LINE_MAX_CHECKED;
+ error("PostScript file '%1' is non-conforming "
+ "because length of line exceeds 255", filename);
+ }
+ }
+ // Reading LF may be a special case: when it immediately
+ // follows a CR which terminated the preceding input line,
+ // we deem it to complete a CRLF terminator for the already
+ // collected preceding line; discard it, and restart input
+ // collection for the current line.
+ //
+ } while ((lastc == '\r') && ((lastc = c) == '\n'));
+
+ // For each collected input line, record its actual terminator,
+ // substitute our preferred LF terminator...
+ //
+ if (((lastc = c) != EOF) || (count > 0))
+ buf[count++] = '\n';
+
+ // ...and append the required C-string (NUL) terminator, before
+ // returning the actual count of input characters stored.
+ //
+ buf[count] = '\0';
+ return count;
+}
+
+// psbb_locator::context_args()
+//
+// Inputs:
+// tag literal text to be matched at start of input line
+//
+// Returns a pointer to the trailing substring of the current
+// input line, following an initial substring matching the "tag"
+// argument, or NULL if "tag" is not matched.
+//
+inline const char *psbb_locator::context_args(const char *tag)
+{
+ return context_args(tag, buf);
+}
+
+// psbb_locator::context_args()
+//
+// Overloaded variant of the preceding function, operating on
+// an alternative input buffer, (which may represent a terminal
+// substring of the psbb_locator's primary input line buffer).
+//
+// Inputs:
+// tag literal text to be matched at start of buffer
+// p pointer to text to be checked for "tag" match
+//
+// Returns a pointer to the trailing substring of the specified
+// text buffer, following an initial substring matching the "tag"
+// argument, or NULL if "tag" is not matched.
+//
+inline const char *psbb_locator::context_args(const char *tag, const char *p)
+{
+ size_t len = strlen(tag);
+ return (strncmp(tag, p, len) == 0) ? p + len : NULL;
+}
+
+// psbb_locator::bounding_box_args()
+//
+// Returns a pointer to the arguments string, within the current
+// input line, when this represents a PostScript "%%BoundingBox:"
+// comment, or NULL otherwise.
+//
+inline const char *psbb_locator::bounding_box_args(void)
+{
+ return context_args("%%BoundingBox:");
+}
+
+// psbb_locator::assign_registers()
+//
+// Copies the bounding box properties established within the
+// class object, to the associated gtroff registers.
+//
+inline void psbb_locator::assign_registers(void)
+{
+ llx_reg_contents = llx;
+ lly_reg_contents = lly;
+ urx_reg_contents = urx;
+ ury_reg_contents = ury;
+}
+
+// psbb_locator::get_header_comment()
+//
+// Fetch a line of PostScript input; return true if it complies with
+// the formatting requirements for header comments, and it is not an
+// "%%EndComments" line; otherwise return false.
+//
+inline bool psbb_locator::get_header_comment(void)
+{
+ return
+ // The first necessary requirement, for returning true,
+ // is that the input line is not empty, (i.e. not EOF).
+ //
+ get_line(DSC_LINE_MAX_ENFORCE) != 0
+
+ // In header comments, '%X' ('X' any printable character
+ // except whitespace) is also acceptable.
+ //
+ && (buf[0] == '%') && !white_space(buf[1])
+
+ // Finally, the input line must not say "%%EndComments".
+ //
+ && context_args("%%EndComments") == NULL;
+}
+
+// psbb_locator::skip_to_trailer()
+//
+// Reposition the PostScript input stream, such that the next get_line()
+// will retrieve the first line, if any, following a "%%Trailer" comment;
+// returns a positive integer value if the "%%Trailer" comment is found,
+// or zero if it is not.
+//
+inline int psbb_locator::skip_to_trailer(void)
+{
+ // Begin by considering a chunk of the input file starting 512 bytes
+ // before its end, and search it for a "%%Trailer" comment; if none is
+ // found, incrementally double the chunk size while it remains within
+ // a 32768L byte range, and search again...
+ //
+ for (ssize_t offset = 512L; offset > 0L; offset <<= 1) {
+ int status, failed;
+ if ((offset > 32768L) || ((failed = fseek(fp, -offset, SEEK_END)) != 0))
+ //
+ // ...ultimately resetting the offset to zero, and simply seeking
+ // to the start of the file, to terminate the cycle and do a "last
+ // ditch" search of the entire file, if any backward seek fails, or
+ // if we reach the arbitrary 32768L byte range limit.
+ //
+ failed = fseek(fp, offset = 0L, SEEK_SET);
+
+ // Following each successful seek...
+ //
+ if (!failed) {
+ //
+ // ...perform a search by reading lines from the input stream...
+ //
+ do { status = get_line(DSC_LINE_MAX_ENFORCE);
+ //
+ // ...until we either exhaust the available stream data, or
+ // we have located a "%%Trailer" comment line.
+ //
+ } while ((status != 0) && (context_args("%%Trailer") == NULL));
+ if (status > 0)
+ //
+ // We found the "%%Trailer" comment, so we may immediately
+ // return, with the stream positioned appropriately...
+ //
+ return status;
+ }
+ }
+ // ...otherwise, we report that no "%%Trailer" comment was found.
+ //
+ return 0;
+}
+
+// ps_bbox_request()
+//
+// Handle the .psbb request.
+//
+void ps_bbox_request()
+{
+ // Parse input line, to extract file name.
+ //
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ //
+ // No file name specified: ignore the entire request.
+ //
+ skip_line();
+ else {
+ // File name acquired: swallow the rest of the line.
+ //
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ errno = 0;
+
+ // Update {llx,lly,urx,ury}_reg_contents:
+ // declaring this class instance achieves this, as an
+ // intentional side effect of object construction.
+ //
+ psbb_locator do_ps_file(nm.contents());
+
+ // All done for .psbb; move on, to continue
+ // input stream processing.
+ //
+ tok.next();
+ }
+}
+
+const char *asciify(int c)
+{
+ static char buf[3];
+ buf[0] = escape_char == '\0' ? '\\' : escape_char;
+ buf[1] = buf[2] = '\0';
+ switch (c) {
+ case ESCAPE_QUESTION:
+ buf[1] = '?';
+ break;
+ case ESCAPE_AMPERSAND:
+ buf[1] = '&';
+ break;
+ case ESCAPE_RIGHT_PARENTHESIS:
+ buf[1] = ')';
+ break;
+ case ESCAPE_UNDERSCORE:
+ buf[1] = '_';
+ break;
+ case ESCAPE_BAR:
+ buf[1] = '|';
+ break;
+ case ESCAPE_CIRCUMFLEX:
+ buf[1] = '^';
+ break;
+ case ESCAPE_LEFT_BRACE:
+ buf[1] = '{';
+ break;
+ case ESCAPE_RIGHT_BRACE:
+ buf[1] = '}';
+ break;
+ case ESCAPE_LEFT_QUOTE:
+ buf[1] = '`';
+ break;
+ case ESCAPE_RIGHT_QUOTE:
+ buf[1] = '\'';
+ break;
+ case ESCAPE_HYPHEN:
+ buf[1] = '-';
+ break;
+ case ESCAPE_BANG:
+ buf[1] = '!';
+ break;
+ case ESCAPE_c:
+ buf[1] = 'c';
+ break;
+ case ESCAPE_e:
+ buf[1] = 'e';
+ break;
+ case ESCAPE_E:
+ buf[1] = 'E';
+ break;
+ case ESCAPE_PERCENT:
+ buf[1] = '%';
+ break;
+ case ESCAPE_SPACE:
+ buf[1] = ' ';
+ break;
+ case ESCAPE_TILDE:
+ buf[1] = '~';
+ break;
+ case ESCAPE_COLON:
+ buf[1] = ':';
+ break;
+ case PUSH_GROFF_MODE:
+ case PUSH_COMP_MODE:
+ case POP_GROFFCOMP_MODE:
+ buf[0] = '\0';
+ break;
+ default:
+ if (is_invalid_input_char(c))
+ buf[0] = '\0';
+ else
+ buf[0] = c;
+ break;
+ }
+ return buf;
+}
+
+const char *input_char_description(int c)
+{
+ switch (c) {
+ case '\n':
+ return "a newline character";
+ case '\b':
+ return "a backspace character";
+ case '\001':
+ return "a leader character";
+ case '\t':
+ return "a tab character";
+ case ' ':
+ return "a space character";
+ case '\0':
+ return "a node";
+ }
+ size_t bufsz = sizeof "magic character code " + INT_DIGITS + 1;
+ // repeat expression; no VLAs in ISO C++
+ static char buf[sizeof "magic character code " + INT_DIGITS + 1];
+ (void) memset(buf, 0, bufsz);
+ if (is_invalid_input_char(c)) {
+ const char *s = asciify(c);
+ if (*s) {
+ buf[0] = '\'';
+ strcpy(buf + 1, s);
+ strcat(buf, "'");
+ return buf;
+ }
+ sprintf(buf, "magic character code %d", c);
+ return buf;
+ }
+ if (csprint(c)) {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ return buf;
+ }
+ sprintf(buf, "character code %d", c);
+ return buf;
+}
+
+void tag()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ string s;
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ s = "x X ";
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ s += (char)c;
+ s += '\n';
+ curenv->add_node(new tag_node(s, 0));
+ }
+ tok.next();
+}
+
+void taga()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ string s;
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ s = "x X ";
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ s += (char)c;
+ s += '\n';
+ curenv->add_node(new tag_node(s, 1));
+ }
+ tok.next();
+}
+
+// .tm, .tm1, and .tmc
+
+void do_terminal(int newline, int string_like)
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (string_like && c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), stderr);
+ }
+ if (newline)
+ fputc('\n', stderr);
+ fflush(stderr);
+ tok.next();
+}
+
+void terminal()
+{
+ do_terminal(1, 0);
+}
+
+void terminal1()
+{
+ do_terminal(1, 1);
+}
+
+void terminal_continue()
+{
+ do_terminal(0, 1);
+}
+
+dictionary stream_dictionary(20);
+
+void do_open(int append)
+{
+ symbol stream = get_name(true /* required */);
+ if (!stream.is_null()) {
+ symbol filename = get_long_name(true /* required */);
+ if (!filename.is_null()) {
+ errno = 0;
+ FILE *fp = fopen(filename.contents(), append ? "a" : "w");
+ if (!fp) {
+ error("can't open '%1' for %2: %3",
+ filename.contents(),
+ append ? "appending" : "writing",
+ strerror(errno));
+ fp = (FILE *)stream_dictionary.remove(stream);
+ }
+ else
+ fp = (FILE *)stream_dictionary.lookup(stream, fp);
+ if (fp)
+ fclose(fp);
+ }
+ }
+ skip_line();
+}
+
+void open_request()
+{
+ if (!unsafe_flag) {
+ error("'open' request is not allowed in safer mode");
+ skip_line();
+ }
+ else
+ do_open(0);
+}
+
+void opena_request()
+{
+ if (!unsafe_flag) {
+ error("'opena' request is not allowed in safer mode");
+ skip_line();
+ }
+ else
+ do_open(1);
+}
+
+void close_request()
+{
+ symbol stream = get_name(true /* required */);
+ if (!stream.is_null()) {
+ FILE *fp = (FILE *)stream_dictionary.remove(stream);
+ if (!fp)
+ error("no stream named '%1'", stream.contents());
+ else
+ fclose(fp);
+ }
+ skip_line();
+}
+
+// .write and .writec
+
+void do_write_request(int newline)
+{
+ symbol stream = get_name(true /* required */);
+ if (stream.is_null()) {
+ skip_line();
+ return;
+ }
+ FILE *fp = (FILE *)stream_dictionary.lookup(stream);
+ if (!fp) {
+ error("no stream named '%1'", stream.contents());
+ skip_line();
+ return;
+ }
+ int c;
+ while ((c = get_copy(0)) == ' ')
+ ;
+ if (c == '"')
+ c = get_copy(0);
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), fp);
+ if (newline)
+ fputc('\n', fp);
+ fflush(fp);
+ tok.next();
+}
+
+void write_request()
+{
+ do_write_request(1);
+}
+
+void write_request_continue()
+{
+ do_write_request(0);
+}
+
+void write_macro_request()
+{
+ symbol stream = get_name(true /* required */);
+ if (stream.is_null()) {
+ skip_line();
+ return;
+ }
+ FILE *fp = (FILE *)stream_dictionary.lookup(stream);
+ if (!fp) {
+ error("no stream named '%1'", stream.contents());
+ skip_line();
+ return;
+ }
+ symbol s = get_name(true /* required */);
+ if (s.is_null()) {
+ skip_line();
+ return;
+ }
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot write request");
+ else {
+ string_iterator iter(*m);
+ for (;;) {
+ int c = iter.get(0);
+ if (c == EOF)
+ break;
+ fputs(asciify(c), fp);
+ }
+ fflush(fp);
+ }
+ skip_line();
+}
+
+void warnscale_request()
+{
+ if (has_arg()) {
+ char c = tok.ch();
+ if (c == 'u')
+ warn_scale = 1.0;
+ else if (c == 'i')
+ warn_scale = (double)units_per_inch;
+ else if (c == 'c')
+ warn_scale = (double)units_per_inch / 2.54;
+ else if (c == 'p')
+ warn_scale = (double)units_per_inch / 72.0;
+ else if (c == 'P')
+ warn_scale = (double)units_per_inch / 6.0;
+ else {
+ warning(WARN_SCALE,
+ "scaling unit '%1' invalid; using 'i' instead", c);
+ c = 'i';
+ }
+ warn_scaling_indicator = c;
+ }
+ skip_line();
+}
+
+void spreadwarn_request()
+{
+ hunits n;
+ if (has_arg() && get_hunits(&n, 'm')) {
+ if (n < 0)
+ n = 0;
+ hunits em = curenv->get_size();
+ spread_limit = (double)n.to_units()
+ / (em.is_zero() ? hresolution : em.to_units());
+ }
+ else
+ spread_limit = -spread_limit - 1; // no arg toggles on/off without
+ // changing value; we mirror at
+ // -0.5 to make zero a valid value
+ skip_line();
+}
+
+static void init_charset_table()
+{
+ char buf[16];
+ strcpy(buf, "char");
+ for (int i = 0; i < 256; i++) {
+ strcpy(buf + 4, i_to_a(i));
+ charset_table[i] = get_charinfo(symbol(buf));
+ charset_table[i]->set_ascii_code(i);
+ if (csalpha(i))
+ charset_table[i]->set_hyphenation_code(cmlower(i));
+ }
+ charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['-']->set_flags(charinfo::BREAK_AFTER);
+ charset_table['"']->set_flags(charinfo::TRANSPARENT);
+ charset_table['\'']->set_flags(charinfo::TRANSPARENT);
+ charset_table[')']->set_flags(charinfo::TRANSPARENT);
+ charset_table[']']->set_flags(charinfo::TRANSPARENT);
+ charset_table['*']->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("dd"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("cq"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
+ get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
+ get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
+ page_character = charset_table['%'];
+}
+
+static void init_hpf_code_table()
+{
+ for (int i = 0; i < 256; i++)
+ hpf_code_table[i] = cmlower(i);
+}
+
+static void do_translate(int translate_transparent, int translate_input)
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (tok.is_space()) {
+ // This is a really bizarre troff feature.
+ tok.next();
+ translate_space_to_dummy = tok.is_dummy();
+ if (tok.is_newline() || tok.is_eof())
+ break;
+ error("cannot translate space character; ignoring");
+ tok.next();
+ continue;
+ }
+ charinfo *ci1 = tok.get_char(true /* required */);
+ if (ci1 == 0)
+ break;
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
+ translate_transparent);
+ break;
+ }
+ if (tok.is_space())
+ ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
+ translate_transparent);
+ else if (tok.is_stretchable_space())
+ ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
+ translate_transparent);
+ else if (tok.is_dummy())
+ ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
+ translate_transparent);
+ else if (tok.is_hyphen_indicator())
+ ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
+ translate_transparent);
+ else {
+ charinfo *ci2 = tok.get_char(true /* required */);
+ if (ci2 == 0)
+ break;
+ if (ci1 == ci2)
+ ci1->set_translation(0, translate_transparent, translate_input);
+ else
+ ci1->set_translation(ci2, translate_transparent, translate_input);
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+void translate()
+{
+ do_translate(1, 0);
+}
+
+void translate_no_transparent()
+{
+ do_translate(0, 0);
+}
+
+void translate_input()
+{
+ do_translate(1, 1);
+}
+
+void char_flags()
+{
+ int flags;
+ if (get_integer(&flags))
+ while (has_arg()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ ci->set_flags(flags);
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+void hyphenation_code()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0)
+ break;
+ tok.next();
+ tok.skip();
+ unsigned char c = tok.ch();
+ if (c == 0) {
+ error("hyphenation code must be ordinary character");
+ break;
+ }
+ if (csdigit(c)) {
+ error("hyphenation code cannot be digit");
+ break;
+ }
+ ci->set_hyphenation_code(c);
+ if (ci->get_translation()
+ && ci->get_translation()->get_translation_input())
+ ci->get_translation()->set_hyphenation_code(c);
+ tok.next();
+ tok.skip();
+ }
+ skip_line();
+}
+
+void hyphenation_patterns_file_code()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ int n1, n2;
+ if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
+ if (!has_arg()) {
+ error("missing output hyphenation code");
+ break;
+ }
+ if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
+ hpf_code_table[n1] = n2;
+ tok.skip();
+ }
+ else {
+ error("output hyphenation code must be integer in the range 0..255");
+ break;
+ }
+ }
+ else {
+ error("input hyphenation code must be integer in the range 0..255");
+ break;
+ }
+ }
+ skip_line();
+}
+
+dictionary char_class_dictionary(501);
+
+void define_class()
+{
+ tok.skip();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ charinfo *ci = get_charinfo(nm);
+ charinfo *child1 = 0, *child2 = 0;
+ while (!tok.is_newline() && !tok.is_eof()) {
+ tok.skip();
+ if (child1 != 0 && tok.ch() == '-') {
+ tok.next();
+ child2 = tok.get_char(true /* required */);
+ if (!child2) {
+ warning(WARN_MISSING,
+ "missing end of character range in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ if (child1->is_class() || child2->is_class()) {
+ warning(WARN_SYNTAX,
+ "a nested character class is not allowed in a range"
+ " definition");
+ skip_line();
+ return;
+ }
+ int u1 = child1->get_unicode_code();
+ int u2 = child2->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid start value in character range");
+ skip_line();
+ return;
+ }
+ if (u2 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid end value in character range");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1, u2);
+ child1 = child2 = 0;
+ }
+ else if (child1 != 0) {
+ if (child1->is_class()) {
+ if (ci == child1) {
+ warning(WARN_SYNTAX, "invalid cyclic class nesting");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(child1);
+ }
+ else {
+ int u1 = child1->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid character value in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1);
+ }
+ child1 = 0;
+ }
+ child1 = tok.get_char(true /* required */);
+ tok.next();
+ if (!child1) {
+ if (!tok.is_newline())
+ skip_line();
+ break;
+ }
+ }
+ if (child1 != 0) {
+ if (child1->is_class()) {
+ if (ci == child1) {
+ warning(WARN_SYNTAX, "invalid cyclic class nesting");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(child1);
+ }
+ else {
+ int u1 = child1->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid character value in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1);
+ }
+ child1 = 0;
+ }
+ if (!ci->is_class()) {
+ warning(WARN_SYNTAX,
+ "empty class definition for '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ (void)char_class_dictionary.lookup(nm, ci);
+ skip_line();
+}
+
+charinfo *token::get_char(bool required)
+{
+ if (type == TOKEN_CHAR)
+ return charset_table[c];
+ if (type == TOKEN_SPECIAL)
+ return get_charinfo(nm);
+ if (type == TOKEN_NUMBERED_CHAR)
+ return get_charinfo_by_number(val);
+ if (type == TOKEN_ESCAPE) {
+ if (escape_char != 0)
+ return charset_table[escape_char];
+ else {
+ // XXX: Is this possible? token::add_to_zero_width_node_list()
+ // and token::process() don't add this token type if the escape
+ // character is null. If not, this should be an assert(). Also
+ // see escape_off().
+ error("escaped 'e' used while escape sequences disabled");
+ return 0;
+ }
+ }
+ if (required) {
+ if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
+ warning(WARN_MISSING, "missing ordinary or special character");
+ else
+ error("expected ordinary or special character, got %1",
+ description());
+ }
+ return 0;
+}
+
+charinfo *get_optional_char()
+{
+ while (tok.is_space())
+ tok.next();
+ charinfo *ci = tok.get_char();
+ if (!ci)
+ check_missing_character();
+ else
+ tok.next();
+ return ci;
+}
+
+void check_missing_character()
+{
+ if (!tok.is_newline() && !tok.is_eof() && !tok.is_right_brace()
+ && !tok.is_tab())
+ error("expected ordinary or special character, got %1; treated as"
+ " missing", tok.description());
+}
+
+// this is for \Z
+
+int token::add_to_zero_width_node_list(node **pp)
+{
+ hunits w;
+ int s;
+ node *n = 0;
+ switch (type) {
+ case TOKEN_CHAR:
+ *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
+ break;
+ case TOKEN_DUMMY:
+ n = new dummy_node;
+ break;
+ case TOKEN_ESCAPE:
+ if (escape_char != 0)
+ *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
+ break;
+ case TOKEN_HYPHEN_INDICATOR:
+ *pp = (*pp)->add_discretionary_hyphen();
+ break;
+ case TOKEN_ITALIC_CORRECTION:
+ *pp = (*pp)->add_italic_correction(&w);
+ break;
+ case TOKEN_LEFT_BRACE:
+ break;
+ case TOKEN_MARK_INPUT:
+ set_number_reg(nm, curenv->get_input_line_position().to_units());
+ break;
+ case TOKEN_NODE:
+ case TOKEN_HORIZONTAL_SPACE:
+ n = nd;
+ nd = 0;
+ break;
+ case TOKEN_NUMBERED_CHAR:
+ *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
+ break;
+ case TOKEN_RIGHT_BRACE:
+ break;
+ case TOKEN_SPACE:
+ n = new hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_SPECIAL:
+ *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
+ break;
+ case TOKEN_STRETCHABLE_SPACE:
+ n = new unbreakable_space_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ n = new space_char_hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_TRANSPARENT_DUMMY:
+ n = new transparent_dummy_node;
+ break;
+ case TOKEN_ZERO_WIDTH_BREAK:
+ n = new space_node(H0, curenv->get_fill_color());
+ n->freeze_space();
+ n->is_escape_colon();
+ break;
+ default:
+ return 0;
+ }
+ if (n) {
+ n->next = *pp;
+ *pp = n;
+ }
+ return 1;
+}
+
+void token::process()
+{
+ if (possibly_handle_first_page_transition())
+ return;
+ switch (type) {
+ case TOKEN_BACKSPACE:
+ curenv->add_node(new hmotion_node(-curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_CHAR:
+ curenv->add_char(charset_table[c]);
+ break;
+ case TOKEN_DUMMY:
+ curenv->add_node(new dummy_node);
+ break;
+ case TOKEN_EMPTY:
+ assert(0);
+ break;
+ case TOKEN_EOF:
+ assert(0);
+ break;
+ case TOKEN_ESCAPE:
+ if (escape_char != 0)
+ curenv->add_char(charset_table[escape_char]);
+ break;
+ case TOKEN_BEGIN_TRAP:
+ case TOKEN_END_TRAP:
+ case TOKEN_PAGE_EJECTOR:
+ // these are all handled in process_input_stack()
+ break;
+ case TOKEN_HYPHEN_INDICATOR:
+ curenv->add_hyphen_indicator();
+ break;
+ case TOKEN_INTERRUPT:
+ curenv->interrupt();
+ break;
+ case TOKEN_ITALIC_CORRECTION:
+ curenv->add_italic_correction();
+ break;
+ case TOKEN_LEADER:
+ curenv->handle_tab(1);
+ break;
+ case TOKEN_LEFT_BRACE:
+ break;
+ case TOKEN_MARK_INPUT:
+ set_number_reg(nm, curenv->get_input_line_position().to_units());
+ break;
+ case TOKEN_NEWLINE:
+ curenv->newline();
+ break;
+ case TOKEN_NODE:
+ case TOKEN_HORIZONTAL_SPACE:
+ curenv->add_node(nd);
+ nd = 0;
+ break;
+ case TOKEN_NUMBERED_CHAR:
+ curenv->add_char(get_charinfo_by_number(val));
+ break;
+ case TOKEN_REQUEST:
+ // handled in process_input_stack()
+ break;
+ case TOKEN_RIGHT_BRACE:
+ break;
+ case TOKEN_SPACE:
+ curenv->space();
+ break;
+ case TOKEN_SPECIAL:
+ curenv->add_char(get_charinfo(nm));
+ break;
+ case TOKEN_SPREAD:
+ curenv->spread();
+ break;
+ case TOKEN_STRETCHABLE_SPACE:
+ curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_TAB:
+ curenv->handle_tab(0);
+ break;
+ case TOKEN_TRANSPARENT:
+ break;
+ case TOKEN_TRANSPARENT_DUMMY:
+ curenv->add_node(new transparent_dummy_node);
+ break;
+ case TOKEN_ZERO_WIDTH_BREAK:
+ {
+ node *tmp = new space_node(H0, curenv->get_fill_color());
+ tmp->freeze_space();
+ tmp->is_escape_colon();
+ curenv->add_node(tmp);
+ break;
+ }
+ default:
+ assert(0);
+ }
+}
+
+class nargs_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *nargs_reg::get_string()
+{
+ return i_to_a(input_stack::nargs());
+}
+
+class lineno_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *lineno_reg::get_string()
+{
+ int line;
+ const char *file;
+ if (!input_stack::get_location(0, &file, &line))
+ line = 0;
+ return i_to_a(line);
+}
+
+class writable_lineno_reg : public general_reg {
+public:
+ writable_lineno_reg();
+ void set_value(units);
+ bool get_value(units *);
+};
+
+writable_lineno_reg::writable_lineno_reg()
+{
+}
+
+bool writable_lineno_reg::get_value(units *res)
+{
+ int line;
+ const char *file;
+ if (!input_stack::get_location(0, &file, &line))
+ return false;
+ *res = line;
+ return true;
+}
+
+void writable_lineno_reg::set_value(units n)
+{
+ input_stack::set_location(0, n);
+}
+
+class filename_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *filename_reg::get_string()
+{
+ int line;
+ const char *file;
+ if (input_stack::get_location(0, &file, &line))
+ return file;
+ else
+ return 0;
+}
+
+class break_flag_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *break_flag_reg::get_string()
+{
+ return i_to_a(input_stack::get_break_flag());
+}
+
+class readonly_text_register : public reg {
+ const char *s;
+public:
+ readonly_text_register(const char *);
+ const char *get_string();
+};
+
+readonly_text_register::readonly_text_register(const char *p) : s(p)
+{
+}
+
+const char *readonly_text_register::get_string()
+{
+ return s;
+}
+
+readonly_register::readonly_register(int *q) : p(q)
+{
+}
+
+const char *readonly_register::get_string()
+{
+ return i_to_a(*p);
+}
+
+void abort_request()
+{
+ int c;
+ if (tok.is_eof())
+ c = EOF;
+ else if (tok.is_newline())
+ c = '\n';
+ else {
+ while ((c = get_copy(0)) == ' ')
+ ;
+ }
+ if (!(c == EOF || c == '\n')) {
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), stderr);
+ fputc('\n', stderr);
+ }
+ fflush(stderr);
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+char *read_string()
+{
+ int len = 256;
+ char *s = new char[len];
+ int c;
+ while ((c = get_copy(0)) == ' ')
+ ;
+ int i = 0;
+ while (c != '\n' && c != EOF) {
+ if (!is_invalid_input_char(c)) {
+ if (i + 2 > len) {
+ char *tem = s;
+ s = new char[len*2];
+ memcpy(s, tem, len);
+ len *= 2;
+ delete[] tem;
+ }
+ s[i++] = c;
+ }
+ c = get_copy(0);
+ }
+ s[i] = '\0';
+ tok.next();
+ if (i == 0) {
+ delete[] s;
+ return 0;
+ }
+ return s;
+}
+
+void pipe_output()
+{
+ if (!unsafe_flag) {
+ error("'pi' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+#ifdef POPEN_MISSING
+ error("pipes not available on this system");
+ skip_line();
+#else /* not POPEN_MISSING */
+ if (the_output) {
+ error("can't pipe: output already started");
+ skip_line();
+ }
+ else {
+ char *pc;
+ if ((pc = read_string()) == 0)
+ error("can't pipe to empty command");
+ if (pipe_command) {
+ char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
+ strcpy(s, pipe_command);
+ strcat(s, "|");
+ strcat(s, pc);
+ delete[] pipe_command;
+ delete[] pc;
+ pipe_command = s;
+ }
+ else
+ pipe_command = pc;
+ }
+#endif /* not POPEN_MISSING */
+ }
+}
+
+static int system_status;
+
+void system_request()
+{
+ if (!unsafe_flag) {
+ error("'sy' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+ char *command = read_string();
+ if (!command)
+ error("empty command");
+ else {
+ system_status = system(command);
+ delete[] command;
+ }
+ }
+}
+
+void copy_file()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(COPY_FILE_REQUEST);
+ return;
+ }
+ symbol filename = get_long_name(true /* required */);
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (!filename.is_null())
+ curdiv->copy_file(filename.contents());
+ tok.next();
+}
+
+#ifdef COLUMN
+
+void vjustify()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(VJUSTIFY_REQUEST);
+ return;
+ }
+ symbol type = get_long_name(true /* required */);
+ if (!type.is_null())
+ curdiv->vjustify(type);
+ skip_line();
+}
+
+#endif /* COLUMN */
+
+void transparent_file()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(TRANSPARENT_FILE_REQUEST);
+ return;
+ }
+ symbol filename = get_long_name(true /* required */);
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (!filename.is_null()) {
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(filename.contents());
+ if (!fp)
+ error("can't open '%1': %2", filename.contents(), strerror(errno));
+ else {
+ int bol = 1;
+ for (;;) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ curdiv->transparent_output(c);
+ bol = c == '\n';
+ }
+ }
+ if (!bol)
+ curdiv->transparent_output('\n');
+ fclose(fp);
+ }
+ }
+ tok.next();
+}
+
+class page_range {
+ int first;
+ int last;
+public:
+ page_range *next;
+ page_range(int, int, page_range *);
+ int contains(int n);
+};
+
+page_range::page_range(int i, int j, page_range *p)
+: first(i), last(j), next(p)
+{
+}
+
+int page_range::contains(int n)
+{
+ return n >= first && (last <= 0 || n <= last);
+}
+
+page_range *output_page_list = 0;
+
+int in_output_page_list(int n)
+{
+ if (!output_page_list)
+ return 1;
+ for (page_range *p = output_page_list; p; p = p->next)
+ if (p->contains(n))
+ return 1;
+ return 0;
+}
+
+static void parse_output_page_list(char *p)
+{
+ for (;;) {
+ int i;
+ if (*p == '-')
+ i = 1;
+ else if (csdigit(*p)) {
+ i = 0;
+ do
+ i = i*10 + *p++ - '0';
+ while (csdigit(*p));
+ }
+ else
+ break;
+ int j;
+ if (*p == '-') {
+ p++;
+ j = 0;
+ if (csdigit(*p)) {
+ do
+ j = j*10 + *p++ - '0';
+ while (csdigit(*p));
+ }
+ }
+ else
+ j = i;
+ if (j == 0)
+ last_page_number = -1;
+ else if (last_page_number >= 0 && j > last_page_number)
+ last_page_number = j;
+ output_page_list = new page_range(i, j, output_page_list);
+ if (*p != ',')
+ break;
+ ++p;
+ }
+ if (*p != '\0') {
+ error("bad output page list");
+ output_page_list = 0;
+ }
+}
+
+static FILE *open_mac_file(const char *mac, char **path)
+{
+ // Try `mac`.tmac first, then tmac.`mac`. Expect ENOENT errors.
+ char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
+ strcpy(s1, mac);
+ strcat(s1, MACRO_POSTFIX);
+ FILE *fp = mac_path->open_file(s1, path);
+ if (!fp && ENOENT != errno)
+ error("can't open macro file '%1': %2", s1, strerror(errno));
+ delete[] s1;
+ if (!fp) {
+ char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
+ strcpy(s2, MACRO_PREFIX);
+ strcat(s2, mac);
+ fp = mac_path->open_file(s2, path);
+ if (!fp && ENOENT != errno)
+ error("can't open macro file '%1': %2", s2, strerror(errno));
+ delete[] s2;
+ }
+ return fp;
+}
+
+static void process_macro_file(const char *mac)
+{
+ char *path;
+ FILE *fp = open_mac_file(mac, &path);
+ if (!fp)
+ fatal("unable to open macro file for -m argument '%1'", mac);
+ const char *s = symbol(path).contents();
+ free(path);
+ input_stack::push(new file_iterator(fp, s));
+ tok.next();
+ process_input_stack();
+}
+
+static void process_startup_file(const char *filename)
+{
+ char *path;
+ search_path *orig_mac_path = mac_path;
+ mac_path = &config_macro_path;
+ FILE *fp = mac_path->open_file(filename, &path);
+ if (fp) {
+ input_stack::push(new file_iterator(fp, symbol(path).contents()));
+ free(path);
+ tok.next();
+ process_input_stack();
+ }
+ mac_path = orig_mac_path;
+}
+
+void do_macro_source(bool quietly)
+{
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ skip_line();
+ else {
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ char *path;
+ FILE *fp = mac_path->open_file(nm.contents(), &path);
+ // .mso cannot go through open_mac_file, which handles the -m option
+ // and expects only an identifier like "s" or "an", not a file name.
+ // We need to do it here manually: If we have tmac.FOOBAR, try
+ // FOOBAR.tmac and vice versa.
+ if (!fp) {
+ const char *fn = nm.contents();
+ size_t fnlen = strlen(fn);
+ if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
+ char *s = new char[fnlen + sizeof(MACRO_POSTFIX)];
+ strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
+ strcat(s, MACRO_POSTFIX);
+ fp = mac_path->open_file(s, &path);
+ delete[] s;
+ }
+ if (!fp) {
+ if (strncasecmp(fn + fnlen - sizeof(MACRO_POSTFIX) + 1,
+ MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
+ char *s = new char[fnlen + sizeof(MACRO_PREFIX)];
+ strcpy(s, MACRO_PREFIX);
+ strncat(s, fn, fnlen - sizeof(MACRO_POSTFIX) + 1);
+ fp = mac_path->open_file(s, &path);
+ delete[] s;
+ }
+ }
+ }
+ if (fp) {
+ input_stack::push(new file_iterator(fp, symbol(path).contents()));
+ free(path);
+ }
+ else
+ // Suppress diagnostic only if we're operating quietly and it's an
+ // expected problem.
+ if (!quietly && (ENOENT == errno))
+ warning(WARN_FILE, "can't open macro file '%1': %2",
+ nm.contents(), strerror(errno));
+ tok.next();
+ }
+}
+
+// .mso
+
+void macro_source()
+{
+ do_macro_source(false /* not quietly (if WARN_FILE enabled) */ );
+}
+
+// .msoquiet: like .mso, but silently ignore files that can't be opened
+// due to their nonexistence
+
+void macro_source_quietly()
+{
+ do_macro_source(true /* quietly */ );
+}
+
+static void process_input_file(const char *name)
+{
+ FILE *fp;
+ if (strcmp(name, "-") == 0) {
+ clearerr(stdin);
+ fp = stdin;
+ }
+ else {
+ errno = 0;
+ fp = include_search_path.open_file_cautious(name);
+ if (!fp)
+ fatal("can't open '%1': %2", name, strerror(errno));
+ }
+ input_stack::push(new file_iterator(fp, name));
+ tok.next();
+ process_input_stack();
+}
+
+// make sure the_input is empty before calling this
+
+static int evaluate_expression(const char *expr, units *res)
+{
+ input_stack::push(make_temp_iterator(expr));
+ tok.next();
+ int success = get_number(res, 'u');
+ while (input_stack::get(0) != EOF)
+ ;
+ return success;
+}
+
+static void do_register_assignment(const char *s)
+{
+ const char *p = strchr(s, '=');
+ if (!p) {
+ char buf[2];
+ buf[0] = s[0];
+ buf[1] = 0;
+ units n;
+ if (evaluate_expression(s + 1, &n))
+ set_number_reg(buf, n);
+ }
+ else {
+ char *buf = new char[p - s + 1];
+ memcpy(buf, s, p - s);
+ buf[p - s] = 0;
+ units n;
+ if (evaluate_expression(p + 1, &n))
+ set_number_reg(buf, n);
+ delete[] buf;
+ }
+}
+
+static void set_string(const char *name, const char *value)
+{
+ macro *m = new macro;
+ for (const char *p = value; *p; p++)
+ if (!is_invalid_input_char((unsigned char)*p))
+ m->append(*p);
+ request_dictionary.define(name, m);
+}
+
+static void do_string_assignment(const char *s)
+{
+ const char *p = strchr(s, '=');
+ if (!p) {
+ char buf[2];
+ buf[0] = s[0];
+ buf[1] = 0;
+ set_string(buf, s + 1);
+ }
+ else {
+ char *buf = new char[p - s + 1];
+ memcpy(buf, s, p - s);
+ buf[p - s] = 0;
+ set_string(buf, p + 1);
+ delete[] buf;
+ }
+}
+
+struct string_list {
+ const char *s;
+ string_list *next;
+ string_list(const char *ss) : s(ss), next(0) {}
+};
+
+#if 0
+static void prepend_string(const char *s, string_list **p)
+{
+ string_list *l = new string_list(s);
+ l->next = *p;
+ *p = l;
+}
+#endif
+
+static void add_string(const char *s, string_list **p)
+{
+ while (*p)
+ p = &((*p)->next);
+ *p = new string_list(s);
+}
+
+void usage(FILE *stream, const char *prog)
+{
+ fprintf(stream,
+"usage: %s [-abcCEiRUz] [-d ct] [-d string=text] [-f font-family]"
+" [-F font-directory] [-I inclusion-directory] [-m macro-package]"
+" [-M macro-directory] [-n page-number] [-o page-list]"
+" [-r cnumeric-expression] [-r register=numeric-expression]"
+" [-T output-device] [-w warning-category] [-W warning-category]"
+" [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ prog, prog, prog);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ string_list *macros = 0;
+ string_list *register_assignments = 0;
+ string_list *string_assignments = 0;
+ int iflag = 0;
+ int tflag = 0;
+ int fflag = 0;
+ int nflag = 0;
+ int no_rc = 0; // don't process troffrc and troffrc-end
+ int next_page_number = 0; // pacify compiler
+ opterr = 0;
+ hresolution = vresolution = 1;
+ // restore $PATH if called from groff
+ char* groff_path = getenv("GROFF_PATH__");
+ if (groff_path) {
+ string e = "PATH";
+ e += '=';
+ if (*groff_path)
+ e += groff_path;
+ e += '\0';
+ if (putenv(strsave(e.contents())))
+ fatal("putenv failed");
+ }
+ setlocale(LC_CTYPE, "");
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { 0, 0, 0, 0 }
+ };
+#if defined(DEBUGGING)
+#define DEBUG_OPTION "D"
+#else
+#define DEBUG_OPTION ""
+#endif
+ while ((c = getopt_long(argc, argv,
+ "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
+ DEBUG_OPTION, long_options, 0))
+ != EOF)
+ switch(c) {
+ case 'v':
+ {
+ printf("GNU troff (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'I':
+ // Search path for .psbb files
+ // and most other non-system input files.
+ include_search_path.command_line_dir(optarg);
+ break;
+ case 'T':
+ device = optarg;
+ tflag = 1;
+ is_html = (strcmp(device, "html") == 0);
+ break;
+ case 'C':
+ compatible_flag = 1;
+ // fall through
+ case 'c':
+ color_flag = 0;
+ break;
+ case 'M':
+ macro_path.command_line_dir(optarg);
+ safer_macro_path.command_line_dir(optarg);
+ config_macro_path.command_line_dir(optarg);
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'm':
+ add_string(optarg, &macros);
+ break;
+ case 'E':
+ inhibit_errors = 1;
+ break;
+ case 'R':
+ no_rc = 1;
+ break;
+ case 'w':
+ enable_warning(optarg);
+ break;
+ case 'W':
+ disable_warning(optarg);
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'b':
+ backtrace_flag = 1;
+ break;
+ case 'a':
+ ascii_output_flag = 1;
+ break;
+ case 'z':
+ suppress_output_flag = 1;
+ break;
+ case 'n':
+ if (sscanf(optarg, "%d", &next_page_number) == 1)
+ nflag++;
+ else
+ error("bad page number");
+ break;
+ case 'o':
+ parse_output_page_list(optarg);
+ break;
+ case 'd':
+ if (*optarg == '\0')
+ error("'-d' requires non-empty argument");
+ else if (*optarg == '=')
+ error("malformed argument to '-d'; string name cannot be empty"
+ " or contain an equals sign");
+ else
+ add_string(optarg, &string_assignments);
+ break;
+ case 'r':
+ if (*optarg == '\0')
+ error("'-r' requires non-empty argument");
+ else if (*optarg == '=')
+ error("malformed argument to '-r'; register name cannot be"
+ " empty or contain an equals sign");
+ else
+ add_string(optarg, &register_assignments);
+ break;
+ case 'f':
+ default_family = symbol(optarg);
+ fflag = 1;
+ break;
+ case 'q':
+ case 's':
+ case 't':
+ // silently ignore these
+ break;
+ case 'U':
+ unsafe_flag = 1; // unsafe behaviour
+ break;
+#if defined(DEBUGGING)
+ case 'D':
+ debug_state = 1;
+ break;
+#endif
+ case CHAR_MAX + 1: // --help
+ usage(stdout, argv[0]);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr, argv[0]);
+ exit(1);
+ break; // never reached
+ default:
+ assert(0);
+ }
+ if (unsafe_flag)
+ mac_path = &macro_path;
+ set_string(".T", device);
+ init_charset_table();
+ init_hpf_code_table();
+ if (0 /* nullptr */ == font::load_desc())
+ fatal("cannot load 'DESC' description file for device '%1'",
+ device);
+ units_per_inch = font::res;
+ hresolution = font::hor;
+ vresolution = font::vert;
+ sizescale = font::sizescale;
+ device_has_tcommand = font::has_tcommand;
+ warn_scale = (double)units_per_inch;
+ warn_scaling_indicator = 'i';
+ if (!fflag && font::family != 0 && *font::family != '\0')
+ default_family = symbol(font::family);
+ font_size::init_size_table(font::sizes);
+ int i;
+ int j = 1;
+ if (font::style_table)
+ for (i = 0; font::style_table[i]; i++)
+ // Mounting a style can't actually fail due to a bad style name;
+ // that's not determined until the full font name is resolved.
+ // The DESC file also can't provoke a problem by requesting over a
+ // thousand slots in the style table.
+ if (!mount_style(j++, symbol(font::style_table[i])))
+ warning(WARN_FONT, "cannot mount style '%1' directed by 'DESC'"
+ " file for device '%2'", font::style_table[i], device);
+ for (i = 0; font::font_name_table[i]; i++, j++)
+ // In the DESC file, a font name of 0 (zero) means "leave this
+ // position empty".
+ if (strcmp(font::font_name_table[i], "0") != 0)
+ if (!mount_font(j, symbol(font::font_name_table[i])))
+ warning(WARN_FONT, "cannot mount font '%1' directed by 'DESC'"
+ " file for device '%2'", font::font_name_table[i],
+ device);
+ curdiv = topdiv = new top_level_diversion;
+ if (nflag)
+ topdiv->set_next_page_number(next_page_number);
+ init_input_requests();
+ init_env_requests();
+ init_div_requests();
+#ifdef COLUMN
+ init_column_requests();
+#endif /* COLUMN */
+ init_node_requests();
+ register_dictionary.define(".T", new readonly_text_register(tflag ? "1" : "0"));
+ init_registers();
+ init_reg_requests();
+ init_hyphen_requests();
+ init_environments();
+ while (string_assignments) {
+ do_string_assignment(string_assignments->s);
+ string_list *tem = string_assignments;
+ string_assignments = string_assignments->next;
+ delete tem;
+ }
+ while (register_assignments) {
+ do_register_assignment(register_assignments->s);
+ string_list *tem = register_assignments;
+ register_assignments = register_assignments->next;
+ delete tem;
+ }
+ if (!no_rc)
+ process_startup_file(INITIAL_STARTUP_FILE);
+ while (macros) {
+ process_macro_file(macros->s);
+ string_list *tem = macros;
+ macros = macros->next;
+ delete tem;
+ }
+ if (!no_rc)
+ process_startup_file(FINAL_STARTUP_FILE);
+ for (i = optind; i < argc; i++)
+ process_input_file(argv[i]);
+ if (optind >= argc || iflag)
+ process_input_file("-");
+ exit_troff();
+ return 0; // not reached
+}
+
+void warn_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n & ~WARN_TOTAL) {
+ warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
+ n &= WARN_TOTAL;
+ }
+ warning_mask = n;
+ }
+ else
+ warning_mask = WARN_TOTAL;
+ skip_line();
+}
+
+static void init_registers()
+{
+#ifdef LONG_FOR_TIME_T
+ long
+#else /* not LONG_FOR_TIME_T */
+ time_t
+#endif /* not LONG_FOR_TIME_T */
+ t = current_time();
+ struct tm *tt = localtime(&t);
+ set_number_reg("seconds", int(tt->tm_sec));
+ set_number_reg("minutes", int(tt->tm_min));
+ set_number_reg("hours", int(tt->tm_hour));
+ set_number_reg("dw", int(tt->tm_wday + 1));
+ set_number_reg("dy", int(tt->tm_mday));
+ set_number_reg("mo", int(tt->tm_mon + 1));
+ set_number_reg("year", int(1900 + tt->tm_year));
+ set_number_reg("yr", int(tt->tm_year));
+ set_number_reg("$$", getpid());
+ register_dictionary.define(".A",
+ new readonly_text_register(ascii_output_flag
+ ? "1"
+ : "0"));
+}
+
+/*
+ * registers associated with \O
+ */
+
+static int output_reg_minx_contents = -1;
+static int output_reg_miny_contents = -1;
+static int output_reg_maxx_contents = -1;
+static int output_reg_maxy_contents = -1;
+
+void check_output_limits(int x, int y)
+{
+ if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
+ output_reg_minx_contents = x;
+ if (x > output_reg_maxx_contents)
+ output_reg_maxx_contents = x;
+ if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
+ output_reg_miny_contents = y;
+ if (y > output_reg_maxy_contents)
+ output_reg_maxy_contents = y;
+}
+
+void reset_output_registers()
+{
+ output_reg_minx_contents = -1;
+ output_reg_miny_contents = -1;
+ output_reg_maxx_contents = -1;
+ output_reg_maxy_contents = -1;
+}
+
+void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
+{
+ *minx = output_reg_minx_contents;
+ *miny = output_reg_miny_contents;
+ *maxx = output_reg_maxx_contents;
+ *maxy = output_reg_maxy_contents;
+}
+
+void init_input_requests()
+{
+ init_request("ab", abort_request);
+ init_request("als", alias_macro);
+ init_request("am", append_macro);
+ init_request("am1", append_nocomp_macro);
+ init_request("ami", append_indirect_macro);
+ init_request("ami1", append_indirect_nocomp_macro);
+ init_request("as", append_string);
+ init_request("as1", append_nocomp_string);
+ init_request("asciify", asciify_macro);
+ init_request("backtrace", backtrace_request);
+ init_request("blm", blank_line_macro);
+ init_request("break", while_break_request);
+ init_request("cf", copy_file);
+ init_request("cflags", char_flags);
+ init_request("char", define_character);
+ init_request("chop", chop_macro);
+ init_request("class", define_class);
+ init_request("close", close_request);
+ init_request("color", activate_color);
+ init_request("composite", composite_request);
+ init_request("continue", while_continue_request);
+ init_request("cp", compatible);
+ init_request("de", define_macro);
+ init_request("de1", define_nocomp_macro);
+ init_request("defcolor", define_color);
+ init_request("dei", define_indirect_macro);
+ init_request("dei1", define_indirect_nocomp_macro);
+ init_request("device", device_request);
+ init_request("devicem", device_macro_request);
+ init_request("do", do_request);
+ init_request("ds", define_string);
+ init_request("ds1", define_nocomp_string);
+ init_request("ec", set_escape_char);
+ init_request("ecr", restore_escape_char);
+ init_request("ecs", save_escape_char);
+ init_request("el", else_request);
+ init_request("em", eoi_macro);
+ init_request("eo", escape_off);
+ init_request("ex", exit_request);
+ init_request("fchar", define_fallback_character);
+#ifdef WIDOW_CONTROL
+ init_request("fpl", flush_pending_lines);
+#endif /* WIDOW_CONTROL */
+ init_request("hcode", hyphenation_code);
+ init_request("hpfcode", hyphenation_patterns_file_code);
+ init_request("ie", if_else_request);
+ init_request("if", if_request);
+ init_request("ig", ignore);
+ init_request("length", length_request);
+ init_request("lf", line_file);
+ init_request("lsm", leading_spaces_macro);
+ init_request("mso", macro_source);
+ init_request("msoquiet", macro_source_quietly);
+ init_request("nop", nop_request);
+ init_request("nroff", nroff_request);
+ init_request("nx", next_file);
+ init_request("open", open_request);
+ init_request("opena", opena_request);
+ init_request("output", output_request);
+ init_request("pc", set_page_character);
+ init_request("pi", pipe_output);
+ init_request("pm", print_macros);
+ init_request("psbb", ps_bbox_request);
+#ifndef POPEN_MISSING
+ init_request("pso", pipe_source);
+#endif /* not POPEN_MISSING */
+ init_request("rchar", remove_character);
+ init_request("rd", read_request);
+ init_request("return", return_macro_request);
+ init_request("rm", remove_macro);
+ init_request("rn", rename_macro);
+ init_request("schar", define_special_character);
+ init_request("shift", shift);
+ init_request("so", source);
+ init_request("soquiet", source_quietly);
+ init_request("spreadwarn", spreadwarn_request);
+ init_request("stringdown", stringdown_request);
+ init_request("stringup", stringup_request);
+ init_request("substring", substring_request);
+ init_request("sy", system_request);
+ init_request("tag", tag);
+ init_request("taga", taga);
+ init_request("tm", terminal);
+ init_request("tm1", terminal1);
+ init_request("tmc", terminal_continue);
+ init_request("tr", translate);
+ init_request("trf", transparent_file);
+ init_request("trin", translate_input);
+ init_request("trnt", translate_no_transparent);
+ init_request("troff", troff_request);
+ init_request("unformat", unformat_macro);
+#ifdef COLUMN
+ init_request("vj", vjustify);
+#endif /* COLUMN */
+ init_request("warn", warn_request);
+ init_request("warnscale", warnscale_request);
+ init_request("while", while_request);
+ init_request("write", write_request);
+ init_request("writec", write_request_continue);
+ init_request("writem", write_macro_request);
+ register_dictionary.define(".$", new nargs_reg);
+ register_dictionary.define(".br", new break_flag_reg);
+ register_dictionary.define(".C", new readonly_register(&compatible_flag));
+ register_dictionary.define(".cp", new readonly_register(&do_old_compatible_flag));
+ register_dictionary.define(".O", new variable_reg(&begin_level));
+ register_dictionary.define(".c", new lineno_reg);
+ register_dictionary.define(".color", new readonly_register(&color_flag));
+ register_dictionary.define(".F", new filename_reg);
+ register_dictionary.define(".g", new readonly_text_register("1"));
+ register_dictionary.define(".H", new readonly_register(&hresolution));
+ register_dictionary.define(".R", new readonly_text_register("10000"));
+ register_dictionary.define(".U", new readonly_register(&unsafe_flag));
+ register_dictionary.define(".V", new readonly_register(&vresolution));
+ register_dictionary.define(".warn", new readonly_register(&warning_mask));
+ extern const char *major_version;
+ register_dictionary.define(".x", new readonly_text_register(major_version));
+ extern const char *revision;
+ register_dictionary.define(".Y", new readonly_text_register(revision));
+ extern const char *minor_version;
+ register_dictionary.define(".y", new readonly_text_register(minor_version));
+ register_dictionary.define("c.", new writable_lineno_reg);
+ register_dictionary.define("llx", new variable_reg(&llx_reg_contents));
+ register_dictionary.define("lly", new variable_reg(&lly_reg_contents));
+ register_dictionary.define("lsn", new variable_reg(&leading_spaces_number));
+ register_dictionary.define("lss", new variable_reg(&leading_spaces_space));
+ register_dictionary.define("opmaxx",
+ new variable_reg(&output_reg_maxx_contents));
+ register_dictionary.define("opmaxy",
+ new variable_reg(&output_reg_maxy_contents));
+ register_dictionary.define("opminx",
+ new variable_reg(&output_reg_minx_contents));
+ register_dictionary.define("opminy",
+ new variable_reg(&output_reg_miny_contents));
+ register_dictionary.define("slimit",
+ new variable_reg(&input_stack::limit));
+ register_dictionary.define("systat", new variable_reg(&system_status));
+ register_dictionary.define("urx", new variable_reg(&urx_reg_contents));
+ register_dictionary.define("ury", new variable_reg(&ury_reg_contents));
+}
+
+object_dictionary request_dictionary(501);
+
+void init_request(const char *s, REQUEST_FUNCP f)
+{
+ request_dictionary.define(s, new request(f));
+}
+
+static request_or_macro *lookup_request(symbol nm)
+{
+ assert(!nm.is_null());
+ request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
+ if (p == 0) {
+ warning(WARN_MAC, "macro '%1' not defined", nm.contents());
+ p = new macro;
+ request_dictionary.define(nm, p);
+ }
+ return p;
+}
+
+node *charinfo_to_node_list(charinfo *ci, const environment *envp)
+{
+ // Don't interpret character definitions in compatible mode.
+ int old_compatible_flag = compatible_flag;
+ compatible_flag = 0;
+ int old_escape_char = escape_char;
+ escape_char = '\\';
+ macro *mac = ci->set_macro(0);
+ assert(mac != 0);
+ environment *oldenv = curenv;
+ environment env(envp);
+ curenv = &env;
+ curenv->set_composite();
+ token old_tok = tok;
+ input_stack::add_boundary();
+ string_iterator *si =
+ new string_iterator(*mac, "composite character", ci->nm);
+ input_stack::push(si);
+ // we don't use process_input_stack, because we don't want to recognise
+ // requests
+ for (;;) {
+ tok.next();
+ if (tok.is_eof())
+ break;
+ if (tok.is_newline()) {
+ error("composite character mustn't contain newline");
+ while (!tok.is_eof())
+ tok.next();
+ break;
+ }
+ else
+ tok.process();
+ }
+ node *n = curenv->extract_output_line();
+ input_stack::remove_boundary();
+ ci->set_macro(mac);
+ tok = old_tok;
+ curenv = oldenv;
+ compatible_flag = old_compatible_flag;
+ escape_char = old_escape_char;
+ have_input = 0;
+ return n;
+}
+
+static node *read_draw_node()
+{
+ token start;
+ start.next();
+ if (!start.usable_as_delimiter(true /* report error */)){
+ do {
+ tok.next();
+ } while (tok != start && !tok.is_newline() && !tok.is_eof());
+ }
+ else {
+ tok.next();
+ if (tok == start)
+ error("missing argument");
+ else {
+ unsigned char type = tok.ch();
+ if (type == 'F') {
+ read_color_draw_node(start);
+ return 0;
+ }
+ tok.next();
+ int maxpoints = 10;
+ hvpair *point = new hvpair[maxpoints];
+ int npoints = 0;
+ int no_last_v = 0;
+ bool err = false;
+ int i;
+ for (i = 0; tok != start; i++) {
+ if (i == maxpoints) {
+ hvpair *oldpoint = point;
+ point = new hvpair[maxpoints*2];
+ for (int j = 0; j < maxpoints; j++)
+ point[j] = oldpoint[j];
+ maxpoints *= 2;
+ delete[] oldpoint;
+ }
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in drawing"
+ " escape sequence (got %1)", tok.description());
+ err = true;
+ break;
+ }
+ if (!get_hunits(&point[i].h,
+ type == 'f' || type == 't' ? 'u' : 'm')) {
+ err = true;
+ break;
+ }
+ ++npoints;
+ tok.skip();
+ point[i].v = V0;
+ if (tok == start) {
+ no_last_v = 1;
+ break;
+ }
+ if (!get_vunits(&point[i].v, 'v')) {
+ err = false;
+ break;
+ }
+ tok.skip();
+ }
+ while (tok != start && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (!err) {
+ switch (type) {
+ case 'l':
+ if (npoints != 1 || no_last_v) {
+ error("two arguments needed for line");
+ npoints = 1;
+ }
+ break;
+ case 'c':
+ if (npoints != 1 || !no_last_v) {
+ error("one argument needed for circle");
+ npoints = 1;
+ point[0].v = V0;
+ }
+ break;
+ case 'e':
+ if (npoints != 1 || no_last_v) {
+ error("two arguments needed for ellipse");
+ npoints = 1;
+ }
+ break;
+ case 'a':
+ if (npoints != 2 || no_last_v) {
+ error("four arguments needed for arc");
+ npoints = 2;
+ }
+ break;
+ case '~':
+ if (no_last_v)
+ error("even number of arguments needed for spline");
+ break;
+ case 'f':
+ if (npoints != 1 || !no_last_v) {
+ error("one argument needed for gray shade");
+ npoints = 1;
+ point[0].v = V0;
+ }
+ default:
+ // silently pass it through
+ break;
+ }
+ draw_node *dn = new draw_node(type, point, npoints,
+ curenv->get_font_size(),
+ curenv->get_glyph_color(),
+ curenv->get_fill_color());
+ delete[] point;
+ return dn;
+ }
+ else {
+ delete[] point;
+ }
+ }
+ }
+ return 0;
+}
+
+static void read_color_draw_node(token &start)
+{
+ tok.next();
+ if (tok == start) {
+ error("missing color scheme");
+ return;
+ }
+ unsigned char scheme = tok.ch();
+ tok.next();
+ color *col = 0;
+ char end = start.ch();
+ switch (scheme) {
+ case 'c':
+ col = read_cmy(end);
+ break;
+ case 'd':
+ col = &default_color;
+ break;
+ case 'g':
+ col = read_gray(end);
+ break;
+ case 'k':
+ col = read_cmyk(end);
+ break;
+ case 'r':
+ col = read_rgb(end);
+ break;
+ }
+ if (col)
+ curenv->set_fill_color(col);
+ while (tok != start) {
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in color space"
+ " drawing escape sequence (got %1)", tok.description());
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ tok.next();
+ }
+ have_input = 1;
+}
+
+static struct {
+ const char *name;
+ int mask;
+} warning_table[] = {
+ { "char", WARN_CHAR },
+ { "range", WARN_RANGE },
+ { "break", WARN_BREAK },
+ { "delim", WARN_DELIM },
+ { "el", WARN_EL },
+ { "scale", WARN_SCALE },
+ { "number", WARN_NUMBER },
+ { "syntax", WARN_SYNTAX },
+ { "tab", WARN_TAB },
+ { "right-brace", WARN_RIGHT_BRACE },
+ { "missing", WARN_MISSING },
+ { "input", WARN_INPUT },
+ { "escape", WARN_ESCAPE },
+ { "space", WARN_SPACE },
+ { "font", WARN_FONT },
+ { "di", WARN_DI },
+ { "mac", WARN_MAC },
+ { "reg", WARN_REG },
+ { "ig", WARN_IG },
+ { "color", WARN_COLOR },
+ { "file", WARN_FILE },
+ { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
+ { "w", WARN_TOTAL },
+ { "default", DEFAULT_WARNING_MASK },
+};
+
+static int lookup_warning(const char *name)
+{
+ for (unsigned int i = 0;
+ i < sizeof(warning_table)/sizeof(warning_table[0]);
+ i++)
+ if (strcmp(name, warning_table[i].name) == 0)
+ return warning_table[i].mask;
+ return 0;
+}
+
+static void enable_warning(const char *name)
+{
+ int mask = lookup_warning(name);
+ if (mask)
+ warning_mask |= mask;
+ else
+ error("unrecognized warning category '%1'", name);
+}
+
+static void disable_warning(const char *name)
+{
+ int mask = lookup_warning(name);
+ if (mask)
+ warning_mask &= ~mask;
+ else
+ error("unrecognized warning category '%1'", name);
+}
+
+static void copy_mode_error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (ignoring) {
+ static const char prefix[] = "(in ignored input) ";
+ char *s = new char[sizeof(prefix) + strlen(format)];
+ strcpy(s, prefix);
+ strcat(s, format);
+ warning(WARN_IG, s, arg1, arg2, arg3);
+ delete[] s;
+ }
+ else
+ error(format, arg1, arg2, arg3);
+}
+
+enum error_type { DEBUG, WARNING, OUTPUT_WARNING, ERROR, FATAL };
+
+static void do_error(error_type type,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ const char *filename;
+ int lineno;
+ if (inhibit_errors && type < FATAL)
+ return;
+ if (backtrace_flag)
+ input_stack::backtrace();
+ if (!get_file_line(&filename, &lineno))
+ filename = 0;
+ if (filename) {
+ if (program_name)
+ errprint("%1:", program_name);
+ errprint("%1:%2: ", filename, lineno);
+ }
+ else if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ switch (type) {
+ case FATAL:
+ fputs("fatal error: ", stderr);
+ break;
+ case ERROR:
+ fputs("error: ", stderr);
+ break;
+ case WARNING:
+ fputs("warning: ", stderr);
+ break;
+ case DEBUG:
+ fputs("debug: ", stderr);
+ break;
+ case OUTPUT_WARNING:
+ double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
+ fprintf(stderr, "warning [p %d, %.1f%c",
+ topdiv->get_page_number(), fromtop, warn_scaling_indicator);
+ if (topdiv != curdiv) {
+ double fromtop1 = curdiv->get_vertical_position().to_units()
+ / warn_scale;
+ fprintf(stderr, ", div '%s', %.1f%c",
+ curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
+ }
+ fprintf(stderr, "]: ");
+ break;
+ }
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ if (type == FATAL)
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+void debug(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(DEBUG, format, arg1, arg2, arg3);
+}
+
+int warning(warning_type t,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if ((t & warning_mask) != 0) {
+ do_error(WARNING, format, arg1, arg2, arg3);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int output_warning(warning_type t,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if ((t & warning_mask) != 0) {
+ do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(ERROR, format, arg1, arg2, arg3);
+}
+
+void fatal(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(FATAL, format, arg1, arg2, arg3);
+}
+
+void fatal_with_file_and_line(const char *filename, int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" fatal error: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+void error_with_file_and_line(const char *filename, int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" error: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+void debug_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" debug: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+dictionary charinfo_dictionary(501);
+
+charinfo *get_charinfo(symbol nm)
+{
+ void *p = charinfo_dictionary.lookup(nm);
+ if (p != 0)
+ return (charinfo *)p;
+ charinfo *cp = new charinfo(nm);
+ (void)charinfo_dictionary.lookup(nm, cp);
+ return cp;
+}
+
+int charinfo::next_index = 0;
+
+charinfo::charinfo(symbol s)
+: translation(0), mac(0), special_translation(TRANSLATE_NONE),
+ hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
+ not_found(0), transparent_translate(1), translate_input(0),
+ mode(CHAR_NORMAL), nm(s)
+{
+ index = next_index++;
+ number = -1;
+ get_flags();
+}
+
+int charinfo::get_unicode_code()
+{
+ if (ascii_code != '\0')
+ return ascii_code;
+ return glyph_to_unicode(this);
+}
+
+void charinfo::set_hyphenation_code(unsigned char c)
+{
+ hyphenation_code = c;
+}
+
+void charinfo::set_translation(charinfo *ci, int tt, int ti)
+{
+ translation = ci;
+ if (ci && ti) {
+ if (hyphenation_code != 0)
+ ci->set_hyphenation_code(hyphenation_code);
+ if (asciify_code != 0)
+ ci->set_asciify_code(asciify_code);
+ else if (ascii_code != 0)
+ ci->set_asciify_code(ascii_code);
+ ci->set_translation_input();
+ }
+ special_translation = TRANSLATE_NONE;
+ transparent_translate = tt;
+}
+
+// Recompute flags for all entries in the charinfo dictionary.
+void get_flags()
+{
+ dictionary_iterator iter(charinfo_dictionary);
+ charinfo *ci;
+ symbol s;
+ while (iter.get(&s, (void **)&ci)) {
+ assert(!s.is_null());
+ ci->get_flags();
+ }
+ class_flag = 0;
+}
+
+// Get the union of all flags affecting this charinfo.
+void charinfo::get_flags()
+{
+ dictionary_iterator iter(char_class_dictionary);
+ charinfo *ci;
+ symbol s;
+ while (iter.get(&s, (void **)&ci)) {
+ assert(!s.is_null());
+ if (ci->contains(get_unicode_code())) {
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "charinfo::get_flags %p %s %d\n",
+ (void *)ci, ci->nm.contents(), ci->flags);
+#endif
+ flags |= ci->flags;
+ }
+ }
+}
+
+void charinfo::set_special_translation(int c, int tt)
+{
+ special_translation = c;
+ translation = 0;
+ transparent_translate = tt;
+}
+
+void charinfo::set_ascii_code(unsigned char c)
+{
+ ascii_code = c;
+}
+
+void charinfo::set_asciify_code(unsigned char c)
+{
+ asciify_code = c;
+}
+
+macro *charinfo::set_macro(macro *m)
+{
+ macro *tem = mac;
+ mac = m;
+ return tem;
+}
+
+macro *charinfo::setx_macro(macro *m, char_mode cm)
+{
+ macro *tem = mac;
+ mac = m;
+ mode = cm;
+ return tem;
+}
+
+void charinfo::set_number(int n)
+{
+ assert(n >= 0);
+ number = n;
+}
+
+int charinfo::get_number()
+{
+ assert(number >= 0);
+ return number;
+}
+
+bool charinfo::contains(int c, bool already_called)
+{
+ if (already_called) {
+ warning(WARN_SYNTAX,
+ "cyclic nested class detected while processing character code %1",
+ c);
+ return false;
+ }
+ std::vector<std::pair<int, int> >::const_iterator ranges_iter;
+ ranges_iter = ranges.begin();
+ while (ranges_iter != ranges.end()) {
+ if (c >= ranges_iter->first && c <= ranges_iter->second) {
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "charinfo::contains(%d)\n", c);
+#endif
+ return true;
+ }
+ ++ranges_iter;
+ }
+
+ std::vector<charinfo *>::const_iterator nested_iter;
+ nested_iter = nested_classes.begin();
+ while (nested_iter != nested_classes.end()) {
+ if ((*nested_iter)->contains(c, true))
+ return true;
+ ++nested_iter;
+ }
+
+ return false;
+}
+
+bool charinfo::contains(symbol s, bool already_called)
+{
+ if (already_called) {
+ warning(WARN_SYNTAX,
+ "cyclic nested class detected while processing symbol %1",
+ s.contents());
+ return false;
+ }
+ const char *unicode = glyph_name_to_unicode(s.contents());
+ if (unicode != 0 && strchr(unicode, '_') == 0) {
+ char *ignore;
+ int c = (int)strtol(unicode, &ignore, 16);
+ return contains(c, true);
+ }
+ else
+ return false;
+}
+
+bool charinfo::contains(charinfo *, bool)
+{
+ // TODO
+ return false;
+}
+
+symbol UNNAMED_SYMBOL("---");
+
+// For numbered characters not between 0 and 255, we make a symbol out
+// of the number and store them in this dictionary.
+
+dictionary numbered_charinfo_dictionary(11);
+
+charinfo *get_charinfo_by_number(int n)
+{
+ static charinfo *number_table[256];
+
+ if (n >= 0 && n < 256) {
+ charinfo *ci = number_table[n];
+ if (!ci) {
+ ci = new charinfo(UNNAMED_SYMBOL);
+ ci->set_number(n);
+ number_table[n] = ci;
+ }
+ return ci;
+ }
+ else {
+ symbol ns(i_to_a(n));
+ charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
+ if (!ci) {
+ ci = new charinfo(UNNAMED_SYMBOL);
+ ci->set_number(n);
+ (void)numbered_charinfo_dictionary.lookup(ns, ci);
+ }
+ return ci;
+ }
+}
+
+// This overrides the same function from libgroff; while reading font
+// definition files it puts single-letter glyph names into
+// 'charset_table' and converts glyph names of the form '\x' ('x' a
+// single letter) into 'x'. Consequently, symbol("x") refers to glyph
+// name '\x', not 'x'.
+
+glyph *name_to_glyph(const char *nm)
+{
+ charinfo *ci;
+ if (nm[1] == 0)
+ ci = charset_table[nm[0] & 0xff];
+ else if (nm[0] == '\\' && nm[2] == 0)
+ ci = get_charinfo(symbol(nm + 1));
+ else
+ ci = get_charinfo(symbol(nm));
+ return ci->as_glyph();
+}
+
+glyph *number_to_glyph(int n)
+{
+ return get_charinfo_by_number(n)->as_glyph();
+}
+
+const char *glyph_to_name(glyph *g)
+{
+ charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
+ return (ci->nm != UNNAMED_SYMBOL ? ci->nm.contents() : 0);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: