diff options
Diffstat (limited to 'src/roff/troff/input.cpp')
-rw-r--r-- | src/roff/troff/input.cpp | 9209 |
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 = ⊤ *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 = ⊤ *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 ¯o::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, ¯os); + 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, ®ister_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 = ¯o_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: |