// -*- C++ -*- /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005 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 2, 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 groff; see the file COPYING. If not, write to the Free Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ #define DEBUGGING #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 "charinfo.h" #include "macropath.h" #include "input.h" #include "defs.h" #include "font.h" #include "unicode.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) #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 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_macro_name; static symbol blank_line_macro_name; static int compatible_flag = 0; 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, // \R, \s, or \S has been processed in // token::next() int old_have_input = 0; // value of have_input right before \n int tcommand_flag = 0; int safer_flag = 1; // safer by default int have_string_arg = 0; // whether we have \*[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**, int = 0); 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_name(read_mode mode = NO_ARGS); static symbol read_long_escape_name(read_mode mode = NO_ARGS); static void interpolate_string(symbol); static void interpolate_string_with_args(symbol); static void interpolate_macro(symbol); 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(int, char); static int get_line_arg(units *res, unsigned char si, charinfo **cp); static int 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("bad escape character"); 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(); } 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 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); } } 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 (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 (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 = ""; else *filenamep = filename; return 1; } void file_iterator::backtrace() { errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno, popened ? "process" : "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); } 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 int nargs(); static int get_location(int, const char **, int *); static int set_location(const char *, int); static void backtrace(); static void backtrace_all(); 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 = NULL; 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--; 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 (diversion_state == NULL) return NULL; 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; } 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() { const char *f; int n; // only backtrace down to (not including) the topmost file for (input_iterator *p = top; p && !p->get_location(0, &f, &n); p = p->next) p->backtrace(); } void input_stack::backtrace_all() { 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_all(); fflush(stderr); skip_line(); } void next_file() { symbol nm = get_long_name(); while (!tok.newline() && !tok.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_name(int allow_space = 0) { int c = get_copy(0); switch (c) { case EOF: copy_mode_error("end of input in escape name"); return '\0'; default: if (!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 name", input_char_description(c)); return '\0'; } return c; } static symbol read_two_char_escape_name() { char buf[3]; buf[0] = get_char_for_escape_name(); if (buf[0] != '\0') { buf[1] = get_char_for_escape_name(); if (buf[1] == '\0') buf[0] = 0; else buf[2] = 0; } return symbol(buf); } static symbol read_long_escape_name(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_name(have_char && mode == WITH_ARGS); if (c == 0) { if (buf != abuf) a_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; a_delete old_buf; } } if (c == ']' && input_stack::get_level() == start_level) break; buf[i++] = c; } buf[i] = 0; if (c == ' ') have_string_arg = 1; 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); a_delete buf; return s; } } static symbol read_escape_name(read_mode mode) { char c = get_char_for_escape_name(); if (c == 0) return NULL_SYMBOL; if (c == '(') return read_two_char_escape_name(); if (c == '[' && !compatible_flag) return read_long_escape_name(mode); char buf[2]; buf[0] = c; buf[1] = '\0'; return symbol(buf); } static symbol read_increment_and_escape_name(int *incp) { char c = get_char_for_escape_name(); switch (c) { case 0: *incp = 0; return NULL_SYMBOL; case '(': *incp = 0; return read_two_char_escape_name(); case '+': *incp = 1; return read_escape_name(); case '-': *incp = -1; return read_escape_name(); case '[': if (!compatible_flag) { *incp = 0; return read_long_escape_name(); } break; } *incp = 0; char buf[2]; buf[0] = c; buf[1] = '\0'; return symbol(buf); } static int get_copy(node **nd, int defining) { 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 == ESCAPE_NEWLINE) { if (defining) return c; do { c = input_stack::get(nd); } while (c == ESCAPE_NEWLINE); } if (c != escape_char || escape_char <= 0) return c; 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_name(); if (!(s.is_null() || s.is_empty())) interpolate_arg(s); break; } case '*': { (void)input_stack::get(0); symbol s = read_escape_name(WITH_ARGS); if (!(s.is_null() || s.is_empty())) { if (have_string_arg) { have_string_arg = 0; 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); return ESCAPE_E; case 'n': { (void)input_stack::get(0); int inc; symbol s = read_increment_and_escape_name(&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_name(); 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_name(); if (!(s.is_null() || s.is_empty())) interpolate_environment_variable(s); break; } case '\n': (void)input_stack::get(0); if (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 a 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 values"); return 0; } const char *s = component.contents(); color *col = new color; if (*s == '#') { if (!col->read_gray(s)) { warning(WARN_COLOR, "`expecting a 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(1); 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(1); 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(); } static node *do_overstrike() { token start; overstrike_node *on = new overstrike_node; int start_level = input_stack::get_level(); start.next(); for (;;) { tok.next(); if (tok.newline() || tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); 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(1); if (ci) { node *n = curenv->make_char_node(ci); if (n) on->overstrike(n); } } return on; } static node *do_bracket() { token start; bracket_node *bn = new bracket_node; start.next(); int start_level = input_stack::get_level(); for (;;) { tok.next(); if (tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); break; } if (tok.newline()) { warning(WARN_DELIM, "missing closing delimiter"); 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(1); if (ci) { node *n = curenv->make_char_node(ci); if (n) bn->bracket(n); } } return bn; } static int do_name_test() { token start; start.next(); int start_level = input_stack::get_level(); int bad_char = 0; int some_char = 0; for (;;) { tok.next(); if (tok.newline() || tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); input_stack::push(make_temp_iterator("\n")); break; } if (tok == start && (compatible_flag || input_stack::get_level() == start_level)) break; if (!tok.ch()) bad_char = 1; some_char = 1; } return some_char && !bad_char; } static int do_expr_test() { token start; start.next(); int start_level = input_stack::get_level(); if (!start.delimiter(1)) 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.newline() || tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); 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.newline() || tok.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; token start; start.next(); int start_level = input_stack::get_level(); for (;;) { tok.next(); if (tok.newline() || tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); input_stack::push(make_temp_iterator("\n")); break; } if (tok == start && (compatible_flag || input_stack::get_level() == start_level)) break; if (!tok.add_to_node_list(&rev)) error("invalid token in argument to \\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 (space()) next(); } int has_arg() { while (tok.space()) tok.next(); return !tok.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_normal_char: switch(cc) { 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 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_NODE; nd = new hmotion_node(curenv->get_narrow_space_width(), curenv->get_fill_color()); return; case ESCAPE_CIRCUMFLEX: ESCAPE_CIRCUMFLEX: type = TOKEN_NODE; 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_name(); 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_NODE; 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_name(); if (!(s.is_null() || s.is_empty())) interpolate_arg(s); break; } case '*': { symbol s = read_escape_name(WITH_ARGS); if (!(s.is_null() || s.is_empty())) { if (have_string_arg) { have_string_arg = 0; 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_name(ALLOW_EMPTY); if (s.is_null()) break; const char *p; for (p = s.contents(); *p != '\0'; p++) if (!csdigit(*p)) break; if (*p || s.is_empty()) curenv->set_font(s); else curenv->set_font(atoi(s.contents())); if (!compatible_flag) have_input = 1; break; } case 'F': { symbol s = read_escape_name(ALLOW_EMPTY); if (s.is_null()) break; curenv->set_family(s); have_input = 1; break; } case 'g': { symbol s = read_escape_name(); if (!(s.is_null() || s.is_empty())) interpolate_number_format(s); break; } case 'h': if (!get_delim_number(&x, 'm')) break; type = TOKEN_NODE; 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_name(); 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_name(ALLOW_EMPTY)); if (!compatible_flag) have_input = 1; break; case 'M': do_fill_color(read_escape_name(ALLOW_EMPTY)); if (!compatible_flag) have_input = 1; break; case 'n': { int inc; symbol s = read_increment_and_escape_name(&inc); if (!(s.is_null() || s.is_empty())) interpolate_number_reg(s, inc); break; } case 'N': if (!get_delim_number(&val, 0)) break; type = TOKEN_NUMBERED_CHAR; return; case 'o': nd = do_overstrike(); type = TOKEN_NODE; return; case 'O': nd = do_suppress(read_escape_name()); 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_name(); 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_name(); 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) nd = new zero_width_node(nd); else { charinfo *ci = get_char(1); 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_name(WITH_ARGS); if (s.is_null() || s.is_empty()) break; if (have_string_arg) { have_string_arg = 0; 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); a_delete buf; } } else nm = symbol(s.contents()); } type = TOKEN_SPECIAL; return; } goto handle_normal_char; default: if (cc != escape_char && cc != '.') warning(WARN_ESCAPE, "escape character ignored before %1", input_char_description(cc)); goto handle_normal_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 ')? int token::delimiter(int err) { 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 (err) error("cannot use character `%1' as a starting delimiter", char(c)); return 0; default: return 1; } case TOKEN_NODE: case TOKEN_SPACE: case TOKEN_STRETCHABLE_SPACE: case TOKEN_UNSTRETCHABLE_SPACE: case TOKEN_TAB: case TOKEN_NEWLINE: if (err) error("cannot use %1 as a starting delimiter", description()); return 0; default: return 1; } } const char *token::description() { static char buf[4]; switch (type) { case TOKEN_BACKSPACE: return "a backspace character"; case TOKEN_CHAR: buf[0] = '`'; buf[1] = c; buf[2] = '\''; buf[3] = '\0'; return buf; case TOKEN_DUMMY: return "`\\&'"; case TOKEN_ESCAPE: return "`\\e'"; case TOKEN_HYPHEN_INDICATOR: return "`\\%'"; case TOKEN_INTERRUPT: return "`\\c'"; case TOKEN_ITALIC_CORRECTION: return "`\\/'"; case TOKEN_LEADER: return "a leader character"; case TOKEN_LEFT_BRACE: return "`\\{'"; case TOKEN_MARK_INPUT: return "`\\k'"; case TOKEN_NEWLINE: return "newline"; case TOKEN_NODE: return "a node"; case TOKEN_NUMBERED_CHAR: return "`\\N'"; case TOKEN_RIGHT_BRACE: return "`\\}'"; case TOKEN_SPACE: return "a space"; case TOKEN_SPECIAL: return "a special character"; case TOKEN_SPREAD: return "`\\p'"; case TOKEN_STRETCHABLE_SPACE: return "`\\~'"; case TOKEN_UNSTRETCHABLE_SPACE: return "`\\ '"; case TOKEN_TAB: return "a tab character"; case TOKEN_TRANSPARENT: return "`\\!'"; case TOKEN_TRANSPARENT_DUMMY: return "`\\)'"; case TOKEN_ZERO_WIDTH_BREAK: return "`\\:'"; case TOKEN_EOF: return "end of input"; default: break; } return "a magic token"; } void skip_line() { while (!tok.newline()) if (tok.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 empty_name_warning(int required) { if (tok.newline() || tok.eof()) { if (required) warning(WARN_MISSING, "missing name"); } else if (tok.right_brace() || tok.tab()) { const char *start = tok.description(); do { tok.next(); } while (tok.space() || tok.right_brace() || tok.tab()); if (!tok.newline() && !tok.eof()) error("%1 is not allowed before an argument", start); else if (required) warning(WARN_MISSING, "missing name"); } else if (required) error("name expected (got %1)", tok.description()); else error("name expected (got %1): treated as missing", tok.description()); } static void non_empty_name_warning() { if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab() && !tok.right_brace() // We don't want to give a warning for .el\{ && !tok.left_brace()) error("%1 is not allowed in a name", tok.description()); } symbol get_name(int 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 non_empty_name_warning(); return symbol(buf); } else { empty_name_warning(required); return NULL_SYMBOL; } } else return get_long_name(required); } symbol get_long_name(int required) { return do_get_long_name(required, 0); } static symbol do_get_long_name(int required, char end) { while (tok.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; a_delete old_buf; } } if ((buf[i] = tok.ch()) == 0 || buf[i] == end) break; i++; tok.next(); } if (i == 0) { empty_name_warning(required); return NULL_SYMBOL; } if (end && buf[i] == end) buf[i+1] = '\0'; else non_empty_name_warning(); if (buf == abuf) return symbol(buf); else { symbol s(buf); a_delete buf; return s; } } void exit_troff() { exit_started = 1; topdiv->set_last_page(); if (!end_macro_name.is_null()) { spring_trap(end_macro_name); tok.next(); process_input_stack(); } curenv->final_break(); tok.next(); process_input_stack(); end_diversions(); if (topdiv->get_page_length() > 0) { done_end_macro = 1; 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(); } // This will only happen if a trap-invoked macro starts a diversion, // or if vertical position traps have been disabled. cleanup_and_exit(0); } // This implements .ex. The input stack must be cleared before calling // exit_troff(). void exit_request() { input_stack::clear(); if (exit_started) 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 end_macro() { end_macro_name = get_name(); skip_line(); } void blank_line_macro() { blank_line_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() { int old_compatible_flag = compatible_flag; compatible_flag = 0; symbol nm = get_name(); if (nm.is_null()) skip_line(); else interpolate_macro(nm); compatible_flag = old_compatible_flag; } 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 (!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; 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; } 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.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.space()); if (tok.newline()) trapping_blank_line(); else { push_token(tok); 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: { 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.newline() && !tok.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) { (*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) { 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), 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; p = 0; } int macro::is_diversion() { return is_a_diversion; } 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; 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; protected: symbol nm; string_iterator(); public: string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL); int fill(node **); int peek(); int get_location(int, const char **, int *); void backtrace(); 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; } } string_iterator::string_iterator() { bp = 0; nd = 0; ptr = eptr = 0; newline_flag = 0; how_invoked = 0; lineno = 1; count = 0; } 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) { errprint("%1:%2: backtrace", 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]; memcpy(base, s, len); ptr = base; eptr = base + len; } temp_iterator::~temp_iterator() { a_delete base; } class small_temp_iterator : public input_iterator { private: small_temp_iterator(const char *, int); ~small_temp_iterator(); enum { BLOCK = 16 }; static small_temp_iterator *free_list; void *operator new(size_t); void operator delete(void *); enum { SIZE = 12 }; unsigned char buf[SIZE]; friend input_iterator *make_temp_iterator(const char *); }; small_temp_iterator *small_temp_iterator::free_list = 0; void *small_temp_iterator::operator new(size_t n) { assert(n == sizeof(small_temp_iterator)); if (!free_list) { free_list = (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK]; for (int i = 0; i < BLOCK - 1; i++) free_list[i].next = free_list + i + 1; free_list[BLOCK-1].next = 0; } small_temp_iterator *p = free_list; free_list = (small_temp_iterator *)(free_list->next); p->next = 0; return p; } #ifdef __GNUG__ inline #endif void small_temp_iterator::operator delete(void *p) { if (p) { ((small_temp_iterator *)p)->next = free_list; free_list = (small_temp_iterator *)p; } } small_temp_iterator::~small_temp_iterator() { } #ifdef __GNUG__ inline #endif small_temp_iterator::small_temp_iterator(const char *s, int len) { for (int i = 0; i < len; i++) buf[i] = s[i]; ptr = buf; eptr = buf + len; } input_iterator *make_temp_iterator(const char *s) { if (s == 0) return new small_temp_iterator(s, 0); else { int n = strlen(s); if (n <= small_temp_iterator::SIZE) return new small_temp_iterator(s, n); else return new temp_iterator(s, n); } } // this is used when macros with arguments are interpolated struct arg_list { macro mac; arg_list *next; arg_list(const macro &); ~arg_list(); }; arg_list::arg_list(const macro &m) : mac(m), next(0) { } arg_list::~arg_list() { } class macro_iterator : public string_iterator { arg_list *args; int argc; public: macro_iterator(symbol, macro &, const char *how_invoked = "macro"); macro_iterator(); ~macro_iterator(); int has_args() { return 1; } input_iterator *get_arg(int i); int nargs() { return argc; } void add_arg(const macro &m); void shift(int n); 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; } void macro_iterator::add_arg(const macro &m) { arg_list **p; for (p = &args; *p; p = &((*p)->next)) ; *p = new arg_list(m); ++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 eg .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) { 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 " "(probably 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); else { skip_line(); return; } } static void decode_args(macro_iterator *mi) { if (!tok.newline() && !tok.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; if (c == '"') { quote_input_level = input_stack::get_level(); c = get_copy(&n); } arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE); 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)) { 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); } } } 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); } } void macro::invoke(symbol nm) { macro_iterator *mi = new macro_iterator(nm, *this); decode_args(mi); input_stack::push(mi); 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) : string_iterator(m, how_called, s), args(0), argc(0) { } macro_iterator::macro_iterator() : args(0), argc(0) { } 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(1); 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(1); 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) 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); macro *m = p->to_macro(); if (m) input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro")); else error("you can't invoke a request with a trap"); 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.newline() && !tok.eof()) { int c = get_copy(0); while (c == ' ') c = get_copy(0); while (c != EOF && c != '\n' && c != ' ') { if (!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 (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(1); if (nm.is_null()) { skip_line(); return; } if (tok.newline()) c = '\n'; else if (tok.tab()) c = '\t'; else if (!tok.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 (!mm) { mm = new macro; request_dictionary.define(nm, mm); } if (comp == COMP_DISABLE || comp == COMP_ENABLE) mac.append(POP_GROFFCOMP_MODE); *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(1); 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.newline()) c = '\n'; else if (tok.tab()) c = '\t'; else if (!tok.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.newline() && !tok.eof()) { if (!tok.space() && !tok.tab()) { charinfo *ci = tok.get_char(1); 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("you can only invoke a string or macro using \\*"); else { string_iterator *si = new string_iterator(*m, "string", nm); input_stack::push(si); } } static void interpolate_string_with_args(symbol s) { request_or_macro *p = lookup_request(s); macro *m = p->to_macro(); if (!m) error("you can only invoke a string or macro using \\*"); else { macro_iterator *mi = new macro_iterator(s, *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 argument name"); 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) args += c; if (i != limit) args += ' '; } 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 += BEGIN_QUOTE; input_iterator *p = input_stack::get_arg(i); int c; while ((c = p->get(0)) != EOF) args += c; args += END_QUOTE; args += '"'; if (i != limit) args += ' '; } 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("bad argument name `%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); } // this should be local to define_macro, but cfront 1.2 doesn't support that static symbol dot_symbol("."); void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp) { symbol nm, term; if (calling == CALLING_INDIRECT) { symbol temp1 = get_name(1); 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(1); 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.newline() && !tok.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, 1); 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 (;;) { while (c == ESCAPE_NEWLINE) { if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) mac.append(c); c = get_copy(&n, 1); } 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, 1); } } 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(1); if (!s1.is_null()) { symbol s2 = get_name(1); if (!s2.is_null()) request_dictionary.rename(s1, s2); } skip_line(); } void alias_macro() { symbol s1 = get_name(1); if (!s1.is_null()) { symbol s2 = get_name(1); 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(1); 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(); } void substring_request() { int start; // 0, 1, ..., n-1 or -1, -2, ... symbol s = get_name(1); 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(1); if (ret.is_null()) { skip_line(); return; } int c; node *n; if (tok.newline()) c = '\n'; else if (tok.tab()) c = '\t'; else if (!tok.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*)number_reg_dictionary.lookup(ret); if (r) r->set_value(len); else set_number_reg(ret, len); tok.next(); } void asciify_macro() { symbol s = get_name(1); 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(1); 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 *)number_reg_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.delimiter(1)) { 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.delimiter(1)) { 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.delimiter(1)) return 0; tok.next(); if (get_number(n, si)) { if (tok.dummy() || tok.transparent_dummy()) tok.next(); if (!(start == tok && input_stack::get_level() == start_level)) { *cp = tok.get_char(1); tok.next(); } if (!(start == tok && input_stack::get_level() == start_level)) warning(WARN_DELIM, "closing delimiter does not match"); return 1; } return 0; } static int 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 int bad = 0; 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)) bad = 1; else { val = c - '0'; tok.next(); c = tok.ch(); if (!csdigit(c)) bad = 1; else { val = val*10 + (c - '0'); val *= sizescale; } } } else if (csdigit(c)) { val = c - '0'; if (!inc && c != '0' && c < '4') { tok.next(); c = tok.ch(); if (!csdigit(c)) bad = 1; else val = val*10 + (c - '0'); } val *= sizescale; } else if (!tok.delimiter(1)) return 0; else { token start(tok); tok.next(); if (!(inc ? get_number(&val, 'z') : get_number(&val, 'z', curenv->get_requested_point_size()))) return 0; if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) { if (start.ch() == '[') error("missing `]'"); else error("missing closing delimiter"); return 0; } } if (!bad) { switch (inc) { case 0: if (val == 0) { // special case -- \s[0] and \s0 means to revert to previous size *x = 0; return 1; } *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, "\\s request results in non-positive point size; set to 1"); *x = 1; } return 1; } else { error("bad digit in point size"); return 0; } } static symbol get_delim_name() { token start; start.next(); if (start.eof()) { error("end of input at start of delimited name"); return NULL_SYMBOL; } if (start.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; a_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) a_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); a_delete buf; return s; } } // Implement \R static void do_register() { token start; start.next(); if (!start.delimiter(1)) return; tok.next(); symbol nm = get_long_name(1); if (nm.is_null()) return; while (tok.space()) tok.next(); reg *r = (reg *)number_reg_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() { token start; start.next(); int start_level = input_stack::get_level(); environment env(curenv); environment *oldenv = curenv; curenv = &env; for (;;) { tok.next(); if (tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); break; } if (tok.newline()) { warning(WARN_DELIM, "missing closing delimiter"); 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.newline() || tok.eof()) return; token start(tok); int start_level = input_stack::get_level(); tok.next(); for (int i = 0; i < 3; i++) { while (!tok.newline() && !tok.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.newline() && !tok.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("missing \\?"); return 0; } return new non_interpreted_node(mac); } static void encode_char(macro *mac, char c) { if (c == '\0') { if ((font::use_charnames_in_special) && tok.special()) { charinfo *ci = tok.get_char(1); const char *s = ci->get_symbol()->contents(); if (s[0] != (char)0) { mac->append('\\'); mac->append('('); int i = 0; while (s[i] != (char)0) { mac->append(s[i]); i++; } mac->append('\\'); mac->append(')'); } } else if (tok.stretchable_space() || tok.unstretchable_space()) mac->append(' '); else if (!(tok.hyphen_indicator() || tok.dummy() || tok.transparent_dummy() || tok.zero_width_break())) error("%1 is invalid within \\X", tok.description()); } else { if ((font::use_charnames_in_special) && (c == '\\')) { /* * add escape escape sequence */ mac->append(c); } mac->append(c); } } node *do_special() { token start; start.next(); int start_level = input_stack::get_level(); macro mac; for (tok.next(); tok != start || input_stack::get_level() != start_level; tok.next()) { if (tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); return 0; } if (tok.newline()) { input_stack::push(make_temp_iterator("\n")); warning(WARN_DELIM, "missing closing delimiter"); break; } unsigned char c; if (tok.space()) c = ' '; else if (tok.tab()) c = '\t'; else if (tok.leader()) c = '\001'; else if (tok.backspace()) c = '\b'; else c = tok.ch(); encode_char(&mac, c); } return new special_node(mac); } void output_request() { if (!tok.newline() && !tok.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("expecting an argument to escape \\O"); 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': begin_level++; break; case '4': begin_level--; break; case '5': { s++; // move over '5' char position = *s; if (*s == (char)0) { error("missing position and filename in \\O"); return 0; } if (!(position == 'l' || position == 'r' || position == 'c' || position == 'i')) { error("l, r, c, or i position expected (got %1 in \\O)", position); return 0; } s++; // onto image name if (s == (char *)0) { error("missing image name for \\O"); return 0; } image_no++; if (begin_level == 0) return new suppress_node(symbol(s), position, image_no); } break; default: error("`%1' is an invalid argument to \\O", *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.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, eg .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.space() || tok.left_brace()) tok.next(); } void nop_request() { while (tok.space()) tok.next(); } static int_stack if_else_stack; int do_if_request() { int invert = 0; while (tok.space()) tok.next(); while (tok.ch() == '!') { tok.next(); invert = !invert; } int result; unsigned char c = tok.ch(); 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(1); if (nm.is_null()) { skip_alternative(); return 0; } result = (c == 'd' ? request_dictionary.lookup(nm) != 0 : number_reg_dictionary.lookup(nm) != 0); } else if (c == 'm') { tok.next(); symbol nm = get_long_name(1); 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(1); 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(1); 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(1); if (nm.is_null()) { skip_alternative(); return 0; } result = check_style(nm); } else if (tok.space()) result = 0; else if (tok.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.newline() || tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); 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 \\{ \\}"); 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(); } } // .so void source() { symbol nm = get_long_name(1); if (nm.is_null()) skip_line(); else { while (!tok.newline() && !tok.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 error("can't open `%1': %2", nm.contents(), strerror(errno)); tok.next(); } } // like .so but use popen() void pipe_source() { if (safer_flag) { error(".pso request 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.newline() || tok.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); a_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)); a_delete buf; } tok.next(); #endif /* not POPEN_MISSING */ } } // .psbb static int llx_reg_contents = 0; static int lly_reg_contents = 0; static int urx_reg_contents = 0; static int ury_reg_contents = 0; struct bounding_box { int llx, lly, urx, ury; }; /* Parse the argument to a %%BoundingBox comment. Return 1 if it contains 4 numbers, 2 if it contains (atend), 0 otherwise. */ int parse_bounding_box(char *p, bounding_box *bb) { if (sscanf(p, "%d %d %d %d", &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4) return 1; else { /* The Document Structuring Conventions say that the numbers should be integers. Unfortunately some broken applications get this wrong. */ double x1, x2, x3, x4; if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) { bb->llx = (int)x1; bb->lly = (int)x2; bb->urx = (int)x3; bb->ury = (int)x4; return 1; } else { for (; *p == ' ' || *p == '\t'; p++) ; if (strncmp(p, "(atend)", 7) == 0) { return 2; } } } bb->llx = bb->lly = bb->urx = bb->ury = 0; return 0; } // This version is taken from psrm.cpp #define PS_LINE_MAX 255 cset white_space("\n\r \t"); int ps_get_line(char *buf, FILE *fp, const char* filename) { int c = getc(fp); if (c == EOF) { buf[0] = '\0'; return 0; } int i = 0; int err = 0; while (c != '\r' && c != '\n' && c != EOF) { if ((c < 0x1b && !white_space(c)) || c == 0x7f) error("invalid input character code %1 in `%2'", int(c), filename); else if (i < PS_LINE_MAX) buf[i++] = c; else if (!err) { err = 1; error("PostScript file `%1' is non-conforming " "because length of line exceeds 255", filename); } c = getc(fp); } buf[i++] = '\n'; buf[i] = '\0'; if (c == '\r') { c = getc(fp); if (c != EOF && c != '\n') ungetc(c, fp); } return 1; } inline void assign_registers(int llx, int lly, int urx, int ury) { llx_reg_contents = llx; lly_reg_contents = lly; urx_reg_contents = urx; ury_reg_contents = ury; } void do_ps_file(FILE *fp, const char* filename) { bounding_box bb; int bb_at_end = 0; char buf[PS_LINE_MAX]; llx_reg_contents = lly_reg_contents = urx_reg_contents = ury_reg_contents = 0; if (!ps_get_line(buf, fp, filename)) { error("`%1' is empty", filename); return; } if (strncmp("%!PS-Adobe-", buf, 11) != 0) { error("`%1' is not conforming to the Document Structuring Conventions", filename); return; } while (ps_get_line(buf, fp, filename) != 0) { if (buf[0] != '%' || buf[1] != '%' || strncmp(buf + 2, "EndComments", 11) == 0) break; if (strncmp(buf + 2, "BoundingBox:", 12) == 0) { int res = parse_bounding_box(buf + 14, &bb); if (res == 1) { assign_registers(bb.llx, bb.lly, bb.urx, bb.ury); return; } else if (res == 2) { bb_at_end = 1; break; } else { error("the arguments to the %%%%BoundingBox comment in `%1' are bad", filename); return; } } } if (bb_at_end) { long offset; int last_try = 0; /* in the trailer, the last BoundingBox comment is significant */ for (offset = 512; !last_try; offset *= 2) { int had_trailer = 0; int got_bb = 0; if (offset > 32768 || fseek(fp, -offset, 2) == -1) { last_try = 1; if (fseek(fp, 0L, 0) == -1) break; } while (ps_get_line(buf, fp, filename) != 0) { if (buf[0] == '%' && buf[1] == '%') { if (!had_trailer) { if (strncmp(buf + 2, "Trailer", 7) == 0) had_trailer = 1; } else { if (strncmp(buf + 2, "BoundingBox:", 12) == 0) { int res = parse_bounding_box(buf + 14, &bb); if (res == 1) got_bb = 1; else if (res == 2) { error("`(atend)' not allowed in trailer of `%1'", filename); return; } else { error("the arguments to the %%%%BoundingBox comment in `%1' are bad", filename); return; } } } } } if (got_bb) { assign_registers(bb.llx, bb.lly, bb.urx, bb.ury); return; } } } error("%%%%BoundingBox comment not found in `%1'", filename); } void ps_bbox_request() { symbol nm = get_long_name(1); if (nm.is_null()) skip_line(); else { while (!tok.newline() && !tok.eof()) tok.next(); errno = 0; // PS files might contain non-printable characters, such as ^Z // and CRs not followed by an LF, so open them in binary mode. FILE *fp = include_search_path.open_file_cautious(nm.contents(), 0, FOPEN_RB); if (fp) { do_ps_file(fp, nm.contents()); fclose(fp); } else error("can't open `%1': %2", nm.contents(), strerror(errno)); 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 (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"; } static char buf[sizeof("magic character code ") + 1 + INT_DIGITS]; if (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.newline() && !tok.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.newline() && !tok.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.newline() && !tok.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(1); if (!stream.is_null()) { symbol filename = get_long_name(1); 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 (safer_flag) { error(".open request not allowed in safer mode"); skip_line(); } else do_open(0); } void opena_request() { if (safer_flag) { error(".opena request not allowed in safer mode"); skip_line(); } else do_open(1); } void close_request() { symbol stream = get_name(1); 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(1); 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(1); 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(1); 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, "invalid scaling indicator `%1', 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("rq"))->set_flags(charinfo::TRANSPARENT); get_charinfo(symbol("em"))->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] = i; } static void do_translate(int translate_transparent, int translate_input) { tok.skip(); while (!tok.newline() && !tok.eof()) { if (tok.space()) { // This is a really bizarre troff feature. tok.next(); translate_space_to_dummy = tok.dummy(); if (tok.newline() || tok.eof()) break; tok.next(); continue; } charinfo *ci1 = tok.get_char(1); if (ci1 == 0) break; tok.next(); if (tok.newline() || tok.eof()) { ci1->set_special_translation(charinfo::TRANSLATE_SPACE, translate_transparent); break; } if (tok.space()) ci1->set_special_translation(charinfo::TRANSLATE_SPACE, translate_transparent); else if (tok.stretchable_space()) ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE, translate_transparent); else if (tok.dummy()) ci1->set_special_translation(charinfo::TRANSLATE_DUMMY, translate_transparent); else if (tok.hyphen_indicator()) ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR, translate_transparent); else { charinfo *ci2 = tok.get_char(1); 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(1); 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.newline() && !tok.eof()) { charinfo *ci = tok.get_char(1); 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.newline() && !tok.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(); } charinfo *token::get_char(int 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 { error("`\\e' used while no current escape character"); return 0; } } if (required) { if (type == TOKEN_EOF || type == TOKEN_NEWLINE) warning(WARN_MISSING, "missing normal or special character"); else error("normal or special character expected (got %1)", description()); } return 0; } charinfo *get_optional_char() { while (tok.space()) tok.next(); charinfo *ci = tok.get_char(); if (!ci) check_missing_character(); else tok.next(); return ci; } void check_missing_character() { if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab()) error("normal or special character expected (got %1): " "treated as missing", tok.description()); } // this is for \Z int token::add_to_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: 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: 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); int get_value(units *); }; writable_lineno_reg::writable_lineno_reg() { } int writable_lineno_reg::get_value(units *res) { int line; const char *file; if (!input_stack::get_location(0, &file, &line)) return 0; *res = line; return 1; } 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 constant_reg : public reg { const char *s; public: constant_reg(const char *); const char *get_string(); }; constant_reg::constant_reg(const char *p) : s(p) { } const char *constant_reg::get_string() { return s; } constant_int_reg::constant_int_reg(int *q) : p(q) { } const char *constant_int_reg::get_string() { return i_to_a(*p); } void abort_request() { int c; if (tok.eof()) c = EOF; else if (tok.newline()) c = '\n'; else { while ((c = get_copy(0)) == ' ') ; } if (c == EOF || c == '\n') fputs("User Abort.", stderr); else { for (; c != '\n' && c != EOF; c = get_copy(0)) fputs(asciify(c), stderr); } fputc('\n', stderr); cleanup_and_exit(1); } 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 (!invalid_input_char(c)) { if (i + 2 > len) { char *tem = s; s = new char[len*2]; memcpy(s, tem, len); len *= 2; a_delete tem; } s[i++] = c; } c = get_copy(0); } s[i] = '\0'; tok.next(); if (i == 0) { a_delete s; return 0; } return s; } void pipe_output() { if (safer_flag) { error(".pi request 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); a_delete pipe_command; a_delete pc; pipe_command = s; } else pipe_command = pc; } #endif /* not POPEN_MISSING */ } } static int system_status; void system_request() { if (safer_flag) { error(".sy request not allowed in safer mode"); skip_line(); } else { char *command = read_string(); if (!command) error("empty command"); else { system_status = system(command); a_delete command; } } } void copy_file() { if (curdiv == topdiv && topdiv->before_first_page) { handle_initial_request(COPY_FILE_REQUEST); return; } symbol filename = get_long_name(1); while (!tok.newline() && !tok.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(1); 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(1); while (!tok.newline() && !tok.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 (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 first FOOBAR.tmac, then tmac.FOOBAR 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); a_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); a_delete s2; } return fp; } static void process_macro_file(const char *mac) { char *path; FILE *fp = open_mac_file(mac, &path); if (!fp) fatal("can't find macro file %1", mac); const char *s = symbol(path).contents(); a_delete 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())); a_delete path; tok.next(); process_input_stack(); } mac_path = orig_mac_path; } void macro_source() { symbol nm = get_long_name(1); if (nm.is_null()) skip_line(); else { while (!tok.newline() && !tok.eof()) tok.next(); char *path; FILE *fp = mac_path->open_file(nm.contents(), &path); // .mso doesn't (and cannot) go through open_mac_file, so 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(); if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) { char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)]; strcpy(s, fn + sizeof(MACRO_PREFIX) - 1); strcat(s, MACRO_POSTFIX); fp = mac_path->open_file(s, &path); a_delete s; } if (!fp) { if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1, MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) { char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)]; strcpy(s, MACRO_PREFIX); strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1); fp = mac_path->open_file(s, &path); a_delete s; } } } if (fp) { input_stack::push(new file_iterator(fp, symbol(path).contents())); a_delete path; } else error("can't find macro file `%1'", nm.contents()); tok.next(); } } 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); a_delete buf; } } static void set_string(const char *name, const char *value) { macro *m = new macro; for (const char *p = value; *p; p++) if (!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); a_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 -abcivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n" " -rcn -Tname -Fdir -Idir -Mdir [files...]\n", 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"); } 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" #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 add_string(optarg, &string_assignments); break; case 'r': if (*optarg == '\0') error("`-r' requires non-empty argument"); 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': safer_flag = 0; // 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 (!safer_flag) mac_path = ¯o_path; set_string(".T", device); init_charset_table(); init_hpf_code_table(); if (!font::load_desc()) fatal("sorry, I can't continue"); units_per_inch = font::res; hresolution = font::hor; vresolution = font::vert; sizescale = font::sizescale; tcommand_flag = font::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++) mount_style(j++, symbol(font::style_table[i])); } 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) mount_font(j, symbol(font::font_name_table[i])); 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(); number_reg_dictionary.define(".T", new constant_reg(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 = time(0); // Use struct here to work around misfeature in old versions of g++. 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()); number_reg_dictionary.define(".A", new constant_reg(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("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("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", end_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("mso", macro_source); 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("spreadwarn", spreadwarn_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); number_reg_dictionary.define(".$", new nargs_reg); number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag)); number_reg_dictionary.define(".c", new lineno_reg); number_reg_dictionary.define(".color", new constant_int_reg(&color_flag)); number_reg_dictionary.define(".F", new filename_reg); number_reg_dictionary.define(".g", new constant_reg("1")); number_reg_dictionary.define(".H", new constant_int_reg(&hresolution)); number_reg_dictionary.define(".R", new constant_reg("10000")); number_reg_dictionary.define(".U", new constant_int_reg(&safer_flag)); number_reg_dictionary.define(".V", new constant_int_reg(&vresolution)); number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask)); extern const char *major_version; number_reg_dictionary.define(".x", new constant_reg(major_version)); extern const char *revision; number_reg_dictionary.define(".Y", new constant_reg(revision)); extern const char *minor_version; number_reg_dictionary.define(".y", new constant_reg(minor_version)); number_reg_dictionary.define("c.", new writable_lineno_reg); number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents)); number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents)); number_reg_dictionary.define("opmaxx", new variable_reg(&output_reg_maxx_contents)); number_reg_dictionary.define("opmaxy", new variable_reg(&output_reg_maxy_contents)); number_reg_dictionary.define("opminx", new variable_reg(&output_reg_minx_contents)); number_reg_dictionary.define("opminy", new variable_reg(&output_reg_miny_contents)); number_reg_dictionary.define("slimit", new variable_reg(&input_stack::limit)); number_reg_dictionary.define("systat", new variable_reg(&system_status)); number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents)); number_reg_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.eof()) break; if (tok.newline()) { error("composite character mustn't contain newline"); while (!tok.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.delimiter(1)){ do { tok.next(); } while (tok != start && !tok.newline() && !tok.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; int err = 0; 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; a_delete oldpoint; } if (!get_hunits(&point[i].h, type == 'f' || type == 't' ? 'u' : 'm')) { err = 1; break; } ++npoints; tok.skip(); point[i].v = V0; if (tok == start) { no_last_v = 1; break; } if (!get_vunits(&point[i].v, 'v')) { err = 1; break; } tok.skip(); } while (tok != start && !tok.newline() && !tok.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()); a_delete point; return dn; } else { a_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.newline() || tok.eof()) { warning(WARN_DELIM, "missing closing delimiter"); 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 }, { "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("unknown warning `%1'", name); } static void disable_warning(const char *name) { int mask = lookup_warning(name); if (mask) warning_mask &= ~mask; else error("unknown warning `%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); a_delete s; } else error(format, arg1, arg2, arg3); } enum error_type { 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) 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: break; case WARNING: fputs("warning: ", 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(1); } 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) { fprintf(stderr, "%s:%d: fatal error: ", filename, lineno); errprint(format, arg1, arg2, arg3); fputc('\n', stderr); fflush(stderr); cleanup_and_exit(1); } void error_with_file_and_line(const char *filename, int lineno, const char *format, const errarg &arg1, const errarg &arg2, const errarg &arg3) { fprintf(stderr, "%s:%d: error: ", filename, lineno); 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++; } 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; } 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) { number = n; flags |= NUMBERED; } int charinfo::get_number() { assert(flags & NUMBERED); return number; } 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; } } int font::name_to_index(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)); if (ci == 0) return -1; else return ci->get_index(); } int font::number_to_index(int n) { return get_charinfo_by_number(n)->get_index(); }