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