/* $NetBSD: input.cpp,v 1.2 2016/01/13 19:01:58 christos Exp $ */
// -*- C++ -*-
// <groff_src_dir>/src/libs/libdriver/input.cpp
/* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005
Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.com)
Major rewrite 2001 by Bernd Warken (bwarken@mayn.de)
Last update: 15 Jun 2005
This file is part of groff, the GNU roff text processing system.
groff is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
groff is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with groff; see the file COPYING. If not, write to the Free
Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA
02110-1301, USA.
*/
/* Description
This file implements the parser for the intermediate groff output,
see groff_out(5), and does the printout for the given device.
All parsed information is processed within the function do_file().
A device postprocessor just needs to fill in the methods for the class
`printer' (or rather a derived class) without having to worry about
the syntax of the intermediate output format. Consequently, the
programming of groff postprocessors is similar to the development of
device drivers.
The prototyping for this file is done in driver.h (and error.h).
*/
/* Changes of the 2001 rewrite of this file.
The interface to the outside and the handling of the global
variables was not changed, but internally many necessary changes
were performed.
The main aim for this rewrite is to provide a first step towards
making groff fully compatible with classical troff without pain.
Bugs fixed
- Unknown subcommands of `D' and `x' are now ignored like in the
classical case, but a warning is issued. This was also
implemented for the other commands.
- A warning is emitted if `x stop' is missing.
- `DC' and `DE' commands didn't position to the right end after
drawing (now they do), see discussion below.
- So far, `x stop' was ignored. Now it terminates the processing
of the current intermediate output file like the classical troff.
- The command `c' didn't check correctly on white-space.
- The environment stack wasn't suitable for the color extensions
(replaced by a class).
- The old groff parser could only handle a prologue with the first
3 lines having a fixed structure, while classical troff specified
the sequence of the first 3 commands without further
restrictions. Now the parser is smart about additional
white space, comments, and empty lines in the prologue.
- The old parser allowed space characters only as syntactical
separators, while classical troff had tab characters as well.
Now any sequence of tabs and/or spaces is a syntactical
separator between commands and/or arguments.
- Range checks for numbers implemented.
New and improved features
- The color commands `m' and `DF' are added.
- The old color command `Df' is now converted and delegated to `DFg'.
- The command `F' is implemented as `use intended file name'. It
checks whether its argument agrees with the file name used so far,
otherwise a warning is issued. Then the new name is remembered
and used for the following error messages.
- For the positioning after drawing commands, an alternative, easier
scheme is provided, but not yet activated; it can be chosen by
undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
It extends the rule of the classical troff output language in a
logical way instead of the rather strange actual positioning.
For details, see the discussion below.
- For the `D' commands that only set the environment, the calling of
pr->send_draw() was removed because this doesn't make sense for
the `DF' commands; the (changed) environment is sent with the
next command anyway.
- Error handling was clearly separated into warnings and fatal.
- The error behavior on additional arguments for `D' and `x'
commands with a fixed number of arguments was changed from being
ignored (former groff) to issue a warning and ignore (now), see
skip_line_x(). No fatal was chosen because both string and
integer arguments can occur.
- The gtroff program issues a trailing dummy integer argument for
some drawing commands with an odd number of arguments to make the
number of arguments even, e.g. the DC and Dt commands; this is
honored now.
- All D commands with a variable number of args expect an even
number of trailing integer arguments, so fatal on error was
implemented.
- Disable environment stack and the commands `{' and `}' by making
them conditional on macro USE_ENV_STACK; actually, this is
undefined by default. There isn't any known application for these
features.
Cosmetics
- Nested `switch' commands are avoided by using more functions.
Dangerous 'fall-through's avoided.
- Commands and functions are sorted alphabetically (where possible).
- Dynamic arrays/buffers are now implemented as container classes.
- Some functions had an ugly return structure; this has been
streamlined by using classes.
- Use standard C math functions for number handling, so getting rid
of differences to '0'.
- The macro `IntArg' has been created for an easier transition
to guaranteed 32 bits integers (`int' is enough for GNU, while
ANSI only guarantees `long int' to have a length of 32 bits).
- The many usages of type `int' are differentiated by using `Char',
`bool', and `IntArg' where appropriate.
- To ease the calls of the local utility functions, the parser
variables `current_file', `npages', and `current_env'
(formerly env) were made global to the file (formerly they were
local to the do_file() function)
- Various comments were added.
TODO
- Get rid of the stupid drawing positioning.
- Can the `Dt' command be completely handled by setting environment
within do_file() instead of sending to pr?
- Integer arguments must be >= 32 bits, use conditional #define.
- Add scaling facility for classical device independence and
non-groff devices. Classical troff output had a quasi device
independence by scaling the intermediate output to the resolution
of the postprocessor device if different from the one specified
with `x T', groff have not. So implement full quasi device
indepedence, including the mapping of the strange classical
devices to the postprocessor device (seems to be reasonably
easy).
- The external, global pointer variables are not optimally handled.
- The global variables `current_filename',
`current_source_filename', and `current_lineno' are only used for
error reporting. So implement a static class `Error'
(`::' calls).
- The global `device' is the name used during the formatting
process; there should be a new variable for the device name used
during the postprocessing.
- Implement the B-spline drawing `D~' for all graphical devices.
- Make `environment' a class with an overflow check for its members
and a delete method to get rid of delete_current_env().
- Implement the `EnvStack' to use `new' instead of `malloc'.
- The class definitions of this document could go into a new file.
- The comments in this section should go to a `Changelog' or some
`README' file in this directory.
*/
/*
Discussion of the positioning by drawing commands
There was some confusion about the positioning of the graphical
pointer at the printout after having executed a `D' command.
The classical troff manual of Osanna & Kernighan specified,
`The position after a graphical object has been drawn is
at its end; for circles and ellipses, the "end" is at the
right side.'
From this, it follows that
- all open figures (args, splines, and lines) should position at their
final point.
- all circles and ellipses should position at their right-most point
(as if 2 halves had been drawn).
- all closed figures apart from circles and ellipses shouldn't change
the position because they return to their origin.
- all setting commands should not change position because they do not
draw any graphical object.
In the case of the open figures, this means that the horizontal
displacement is the sum of all odd arguments and the vertical offset
the sum of all even arguments, called the alternate arguments sum
displacement in the following.
Unfortunately, groff did not implement this simple rule. The former
documentation in groff_out(5) differed from the source code, and
neither of them is compatible with the classical rule.
The former groff_out(5) specified to use the alternative arguments
sum displacement for calculating the drawing positioning of
non-classical commands, including the `Dt' command (setting-only)
and closed polygons. Applying this to the new groff color commands
will lead to disaster. For their arguments can take large values (>
65000). On low resolution devices, the displacement of such large
values will corrupt the display or kill the printer. So the
nonsense specification has come to a natural end anyway.
The groff source code, however, had no positioning for the
setting-only commands (esp. `Dt'), the right-end positioning for
outlined circles and ellipses, and the alternative argument sum
displacement for all other commands (including filled circles and
ellipses).
The reason why no one seems to have suffered from this mayhem so
far is that the graphical objects are usually generated by
preprocessors like pic that do not depend on the automatic
positioning. When using the low level `\D' escape sequences or `D'
output commands, the strange positionings can be circumvented by
absolute positionings or by tricks like `\Z'.
So doing an exorcism on the strange, incompatible displacements might
not harm any existing documents, but will make the usage of the
graphical escape sequences and commands natural.
That's why the rewrite of this file returned to the reasonable,
classical specification with its clear end-of-drawing rule that is
suitable for all cases. But a macro STUPID_DRAWING_POSITIONING is
provided for testing the funny former behavior.
The new rule implies the following behavior.
- Setting commands (`Dt', `Df', `DF') and polygons (`Dp' and `DP')
do not change position now.
- Filled circles and ellipses (`DC' and `DE') position at their
most right point (outlined ones `Dc' and `De' did this anyway).
- As before, all open graphical objects position to their final
drawing point (alternate sum of the command arguments).
*/
#ifndef STUPID_DRAWING_POSITIONING
// uncomment next line if all non-classical D commands shall position
// to the strange alternate sum of args displacement
#define STUPID_DRAWING_POSITIONING
#endif
// Decide whether the commands `{' and `}' for different environments
// should be used.
#undef USE_ENV_STACK
#include "driver.h"
#include "device.h"
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <math.h>
/**********************************************************************
local types
**********************************************************************/
// integer type used in the fields of struct environment (see printer.h)
typedef int EnvInt;
// integer arguments of groff_out commands, must be >= 32 bits
typedef int IntArg;
// color components of groff_out color commands, must be >= 32 bits
typedef unsigned int ColorArg;
// Array for IntArg values.
class IntArray {
size_t num_allocated;
size_t num_stored;
IntArg *data;
public:
IntArray(void);
IntArray(const size_t);
~IntArray(void);
IntArg operator[](const size_t i) const
{
if (i >= num_stored)
fatal("index out of range");
return (IntArg) data[i];
}
void append(IntArg);
IntArg *get_data(void) const { return (IntArg *)data; }
size_t len(void) const { return num_stored; }
};
// Characters read from the input queue.
class Char {
int data;
public:
Char(void) : data('\0') {}
Char(const int c) : data(c) {}
bool operator==(char c) const { return (data == c) ? true : false; }
bool operator==(int c) const { return (data == c) ? true : false; }
bool operator==(const Char c) const
{ return (data == c.data) ? true : false; }
bool operator!=(char c) const { return !(*this == c); }
bool operator!=(int c) const { return !(*this == c); }
bool operator!=(const Char c) const { return !(*this == c); }
operator int() const { return (int) data; }
operator unsigned char() const { return (unsigned char) data; }
operator char() const { return (char) data; }
};
// Buffer for string arguments (Char, not char).
class StringBuf {
size_t num_allocated;
size_t num_stored;
Char *data; // not terminated by '\0'
public:
StringBuf(void); // allocate without storing
~StringBuf(void);
void append(const Char); // append character to `data'
char *make_string(void); // return new copy of `data' with '\0'
bool is_empty(void) { // true if none stored
return (num_stored > 0) ? false : true;
}
void reset(void); // set `num_stored' to 0
};
#ifdef USE_ENV_STACK
class EnvStack {
environment **data;
size_t num_allocated;
size_t num_stored;
public:
EnvStack(void);
~EnvStack(void);
environment *pop(void);
void push(environment *e);
};
#endif // USE_ENV_STACK
/**********************************************************************
external variables
**********************************************************************/
// exported as extern by error.h (called from driver.h)
// needed for error messages (see ../libgroff/error.cpp)
const char *current_filename = 0; // printable name of the current file
// printable name of current source file
const char *current_source_filename = 0;
int current_lineno = 0; // current line number of printout
// exported as extern by device.h;
const char *device = 0; // cancel former init with literal
printer *pr;
// Note:
//
// We rely on an implementation of the `new' operator which aborts
// gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
/**********************************************************************
static local variables
**********************************************************************/
FILE *current_file = 0; // current input stream for parser
// npages: number of pages processed so far (including current page),
// _not_ the page number in the printout (can be set with `p').
int npages = 0;
const ColorArg
COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
const IntArg
INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
// parser environment, created and deleted by each run of do_file()
environment *current_env = 0;
#ifdef USE_ENV_STACK
const size_t
envp_size = sizeof(environment *);
#endif // USE_ENV_STACK
/**********************************************************************
function declarations
**********************************************************************/
// utility functions
ColorArg color_from_Df_command(IntArg);
// transform old color into new
void delete_current_env(void); // delete global var current_env
void fatal_command(char); // abort for invalid command
inline Char get_char(void); // read next character from input stream
ColorArg get_color_arg(void); // read in argument for new color cmds
IntArray *get_D_fixed_args(const size_t);
// read in fixed number of integer
// arguments
IntArray *get_D_fixed_args_odd_dummy(const size_t);
// read in a fixed number of integer
// arguments plus optional dummy
IntArray *get_D_variable_args(void);
// variable, even number of int args
char *get_extended_arg(void); // argument for `x X' (several lines)
IntArg get_integer_arg(void); // read in next integer argument
IntArray *get_possibly_integer_args();
// 0 or more integer arguments
char *get_string_arg(void); // read in next string arg, ended by WS
inline bool is_space_or_tab(const Char);
// test on space/tab char
Char next_arg_begin(void); // skip white space on current line
Char next_command(void); // go to next command, evt. diff. line
inline bool odd(const int); // test if integer is odd
void position_to_end_of_args(const IntArray * const);
// positioning after drawing
void remember_filename(const char *);
// set global current_filename
void remember_source_filename(const char *);
// set global current_source_filename
void send_draw(const Char, const IntArray * const);
// call pr->draw
void skip_line(void); // unconditionally skip to next line
bool skip_line_checked(void); // skip line, false if args are left
void skip_line_fatal(void); // skip line, fatal if args are left
void skip_line_warn(void); // skip line, warn if args are left
void skip_line_D(void); // skip line in D commands
void skip_line_x(void); // skip line in x commands
void skip_to_end_of_line(void); // skip to the end of the current line
inline void unget_char(const Char);
// restore character onto input
// parser subcommands
void parse_color_command(color *);
// color sub(sub)commands m and DF
void parse_D_command(void); // graphical subcommands
bool parse_x_command(void); // device controller subcommands
/**********************************************************************
class methods
**********************************************************************/
#ifdef USE_ENV_STACK
EnvStack::EnvStack(void)
{
num_allocated = 4;
// allocate pointer to array of num_allocated pointers to environment
data = (environment **)malloc(envp_size * num_allocated);
if (data == 0)
fatal("could not allocate environment data");
num_stored = 0;
}
EnvStack::~EnvStack(void)
{
for (size_t i = 0; i < num_stored; i++)
delete data[i];
free(data);
}
// return top element from stack and decrease stack pointer
//
// the calling function must take care of properly deleting the result
environment *
EnvStack::pop(void)
{
num_stored--;
environment *result = data[num_stored];
data[num_stored] = 0;
return result;
}
// copy argument and push this onto the stack
void
EnvStack::push(environment *e)
{
environment *e_copy = new environment;
if (num_stored >= num_allocated) {
environment **old_data = data;
num_allocated *= 2;
data = (environment **)malloc(envp_size * num_allocated);
if (data == 0)
fatal("could not allocate data");
for (size_t i = 0; i < num_stored; i++)
data[i] = old_data[i];
free(old_data);
}
e_copy->col = new color;
e_copy->fill = new color;
*e_copy->col = *e->col;
*e_copy->fill = *e->fill;
e_copy->fontno = e->fontno;
e_copy->height = e->height;
e_copy->hpos = e->hpos;
e_copy->size = e->size;
e_copy->slant = e->slant;
e_copy->vpos = e->vpos;
data[num_stored] = e_copy;
num_stored++;
}
#endif // USE_ENV_STACK
IntArray::IntArray(void)
{
num_allocated = 4;
data = new IntArg[num_allocated];
num_stored = 0;
}
IntArray::IntArray(const size_t n)
{
if (n <= 0)
fatal("number of integers to be allocated must be > 0");
num_allocated = n;
data = new IntArg[num_allocated];
num_stored = 0;
}
IntArray::~IntArray(void)
{
a_delete data;
}
void
IntArray::append(IntArg x)
{
if (num_stored >= num_allocated) {
IntArg *old_data = data;
num_allocated *= 2;
data = new IntArg[num_allocated];
for (size_t i = 0; i < num_stored; i++)
data[i] = old_data[i];
a_delete old_data;
}
data[num_stored] = x;
num_stored++;
}
StringBuf::StringBuf(void)
{
num_stored = 0;
num_allocated = 128;
data = new Char[num_allocated];
}
StringBuf::~StringBuf(void)
{
a_delete data;
}
void
StringBuf::append(const Char c)
{
if (num_stored >= num_allocated) {
Char *old_data = data;
num_allocated *= 2;
data = new Char[num_allocated];
for (size_t i = 0; i < num_stored; i++)
data[i] = old_data[i];
a_delete old_data;
}
data[num_stored] = c;
num_stored++;
}
char *
StringBuf::make_string(void)
{
char *result = new char[num_stored + 1];
for (size_t i = 0; i < num_stored; i++)
result[i] = (char) data[i];
result[num_stored] = '\0';
return result;
}
void
StringBuf::reset(void)
{
num_stored = 0;
}
/**********************************************************************
utility functions
**********************************************************************/
//////////////////////////////////////////////////////////////////////
/* color_from_Df_command:
Process the gray shade setting command Df.
Transform Df style color into DF style color.
Df color: 0-1000, 0 is white
DF color: 0-65536, 0 is black
The Df command is obsoleted by command DFg, but kept for
compatibility.
*/
ColorArg
color_from_Df_command(IntArg Df_gray)
{
return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
}
//////////////////////////////////////////////////////////////////////
/* delete_current_env():
Delete global variable current_env and its pointer members.
This should be a class method of environment.
*/
void delete_current_env(void)
{
delete current_env->col;
delete current_env->fill;
delete current_env;
current_env = 0;
}
//////////////////////////////////////////////////////////////////////
/* fatal_command():
Emit error message about invalid command and abort.
*/
void
fatal_command(char command)
{
fatal("`%1' command invalid before first `p' command", command);
}
//////////////////////////////////////////////////////////////////////
/* get_char():
Retrieve the next character from the input queue.
Return: The retrieved character (incl. EOF), converted to Char.
*/
inline Char
get_char(void)
{
return (Char) getc(current_file);
}
//////////////////////////////////////////////////////////////////////
/* get_color_arg():
Retrieve an argument suitable for the color commands m and DF.
Return: The retrieved color argument.
*/
ColorArg
get_color_arg(void)
{
IntArg x = get_integer_arg();
if (x < 0 || x > (IntArg)COLORARG_MAX) {
error("color component argument out of range");
x = 0;
}
return (ColorArg) x;
}
//////////////////////////////////////////////////////////////////////
/* get_D_fixed_args():
Get a fixed number of integer arguments for D commands.
Fatal if wrong number of arguments.
Too many arguments on the line raise a warning.
A line skip is done.
number: In-parameter, the number of arguments to be retrieved.
ignore: In-parameter, ignore next argument -- GNU troff always emits
pairs of parameters for `D' extensions added by groff.
Default is `false'.
Return: New IntArray containing the arguments.
*/
IntArray *
get_D_fixed_args(const size_t number)
{
if (number <= 0)
fatal("requested number of arguments must be > 0");
IntArray *args = new IntArray(number);
for (size_t i = 0; i < number; i++)
args->append(get_integer_arg());
skip_line_D();
return args;
}
//////////////////////////////////////////////////////////////////////
/* get_D_fixed_args_odd_dummy():
Get a fixed number of integer arguments for D commands and optionally
ignore a dummy integer argument if the requested number is odd.
The gtroff program adds a dummy argument to some commands to get
an even number of arguments.
Error if the number of arguments differs from the scheme above.
A line skip is done.
number: In-parameter, the number of arguments to be retrieved.
Return: New IntArray containing the arguments.
*/
IntArray *
get_D_fixed_args_odd_dummy(const size_t number)
{
if (number <= 0)
fatal("requested number of arguments must be > 0");
IntArray *args = new IntArray(number);
for (size_t i = 0; i < number; i++)
args->append(get_integer_arg());
if (odd(number)) {
IntArray *a = get_possibly_integer_args();
if (a->len() > 1)
error("too many arguments");
delete a;
}
skip_line_D();
return args;
}
//////////////////////////////////////////////////////////////////////
/* get_D_variable_args():
Get a variable even number of integer arguments for D commands.
Get as many integer arguments as possible from the rest of the
current line.
- The arguments are separated by an arbitrary sequence of space or
tab characters.
- A comment, a newline, or EOF indicates the end of processing.
- Error on non-digit characters different from these.
- A final line skip is performed (except for EOF).
Return: New IntArray of the retrieved arguments.
*/
IntArray *
get_D_variable_args()
{
IntArray *args = get_possibly_integer_args();
size_t n = args->len();
if (n <= 0)
error("no arguments found");
if (odd(n))
error("even number of arguments expected");
skip_line_D();
return args;
}
//////////////////////////////////////////////////////////////////////
/* get_extended_arg():
Retrieve extended arg for `x X' command.
- Skip leading spaces and tabs, error on EOL or newline.
- Return everything before the next NL or EOF ('#' is not a comment);
as long as the following line starts with '+' this is returned
as well, with the '+' replaced by a newline.
- Final line skip is always performed.
Return: Allocated (new) string of retrieved text argument.
*/
char *
get_extended_arg(void)
{
StringBuf buf = StringBuf();
Char c = next_arg_begin();
while ((int) c != EOF) {
if ((int) c == '\n') {
current_lineno++;
c = get_char();
if ((int) c == '+')
buf.append((Char) '\n');
else {
unget_char(c); // first character of next line
break;
}
}
else
buf.append(c);
c = get_char();
}
return buf.make_string();
}
//////////////////////////////////////////////////////////////////////
/* get_integer_arg(): Retrieve integer argument.
Skip leading spaces and tabs, collect an optional '-' and all
following decimal digits (at least one) up to the next non-digit,
which is restored onto the input queue.
Fatal error on all other situations.
Return: Retrieved integer.
*/
IntArg
get_integer_arg(void)
{
StringBuf buf = StringBuf();
Char c = next_arg_begin();
if ((int) c == '-') {
buf.append(c);
c = get_char();
}
if (!isdigit((int) c))
error("integer argument expected");
while (isdigit((int) c)) {
buf.append(c);
c = get_char();
}
// c is not a digit
unget_char(c);
char *s = buf.make_string();
errno = 0;
long int number = strtol(s, 0, 10);
if (errno != 0
|| number > INTARG_MAX || number < -INTARG_MAX) {
error("integer argument too large");
number = 0;
}
a_delete s;
return (IntArg) number;
}
//////////////////////////////////////////////////////////////////////
/* get_possibly_integer_args():
Parse the rest of the input line as a list of integer arguments.
Get as many integer arguments as possible from the rest of the
current line, even none.
- The arguments are separated by an arbitrary sequence of space or
tab characters.
- A comment, a newline, or EOF indicates the end of processing.
- Error on non-digit characters different from these.
- No line skip is performed.
Return: New IntArray of the retrieved arguments.
*/
IntArray *
get_possibly_integer_args()
{
bool done = false;
StringBuf buf = StringBuf();
Char c = get_char();
IntArray *args = new IntArray();
while (!done) {
buf.reset();
while (is_space_or_tab(c))
c = get_char();
if (c == '-') {
Char c1 = get_char();
if (isdigit((int) c1)) {
buf.append(c);
c = c1;
}
else
unget_char(c1);
}
while (isdigit((int) c)) {
buf.append(c);
c = get_char();
}
if (!buf.is_empty()) {
char *s = buf.make_string();
errno = 0;
long int x = strtol(s, 0, 10);
if (errno
|| x > INTARG_MAX || x < -INTARG_MAX) {
error("invalid integer argument, set to 0");
x = 0;
}
args->append((IntArg) x);
a_delete s;
}
// Here, c is not a digit.
// Terminate on comment, end of line, or end of file, while
// space or tab indicate continuation; otherwise error.
switch((int) c) {
case '#':
skip_to_end_of_line();
done = true;
break;
case '\n':
done = true;
unget_char(c);
break;
case EOF:
done = true;
break;
case ' ':
case '\t':
break;
default:
error("integer argument expected");
break;
}
}
return args;
}
//////////////////////////////////////////////////////////////////////
/* get_string_arg():
Retrieve string arg.
- Skip leading spaces and tabs; error on EOL or newline.
- Return all following characters before the next space, tab,
newline, or EOF character (in-word '#' is not a comment character).
- The terminating space, tab, newline, or EOF character is restored
onto the input queue, so no line skip.
Return: Retrieved string as char *, allocated by 'new'.
*/
char *
get_string_arg(void)
{
StringBuf buf = StringBuf();
Char c = next_arg_begin();
while (!is_space_or_tab(c)
&& c != Char('\n') && c != Char(EOF)) {
buf.append(c);
c = get_char();
}
unget_char(c); // restore white space
return buf.make_string();
}
//////////////////////////////////////////////////////////////////////
/* is_space_or_tab():
Test a character if it is a space or tab.
c: In-parameter, character to be tested.
Return: True, if c is a space or tab character, false otherwise.
*/
inline bool
is_space_or_tab(const Char c)
{
return (c == Char(' ') || c == Char('\t')) ? true : false;
}
//////////////////////////////////////////////////////////////////////
/* next_arg_begin():
Return first character of next argument.
Skip space and tab characters; error on newline or EOF.
Return: The first character different from these (including '#').
*/
Char
next_arg_begin(void)
{
Char c;
while (1) {
c = get_char();
switch ((int) c) {
case ' ':
case '\t':
break;
case '\n':
case EOF:
error("missing argument");
break;
default: // first essential character
return c;
}
}
}
//////////////////////////////////////////////////////////////////////
/* next_command():
Find the first character of the next command.
Skip spaces, tabs, comments (introduced by #), and newlines.
Return: The first character different from these (including EOF).
*/
Char
next_command(void)
{
Char c;
while (1) {
c = get_char();
switch ((int) c) {
case ' ':
case '\t':
break;
case '\n':
current_lineno++;
break;
case '#': // comment
skip_line();
break;
default: // EOF or first essential character
return c;
}
}
}
//////////////////////////////////////////////////////////////////////
/* odd():
Test whether argument is an odd number.
n: In-parameter, the integer to be tested.
Return: True if odd, false otherwise.
*/
inline bool
odd(const int n)
{
return (n & 1) ? true : false;
}
//////////////////////////////////////////////////////////////////////
/* position_to_end_of_args():
Move graphical pointer to end of drawn figure.
This is used by the D commands that draw open geometrical figures.
The algorithm simply sums up all horizontal displacements (arguments
with even number) for the horizontal component. Similarly, the
vertical component is the sum of the odd arguments.
args: In-parameter, the arguments of a former drawing command.
*/
void
position_to_end_of_args(const IntArray * const args)
{
size_t i;
const size_t n = args->len();
for (i = 0; i < n; i += 2)
current_env->hpos += (*args)[i];
for (i = 1; i < n; i += 2)
current_env->vpos += (*args)[i];
}
//////////////////////////////////////////////////////////////////////
/* remember_filename():
Set global variable current_filename.
The actual filename is stored in current_filename. This is used by
the postprocessors, expecting the name "<standard input>" for stdin.
filename: In-out-parameter; is changed to the new value also.
*/
void
remember_filename(const char *filename)
{
char *fname;
if (strcmp(filename, "-") == 0)
fname = (char *)"<standard input>";
else
fname = (char *)filename;
size_t len = strlen(fname) + 1;
if (current_filename != 0)
free((char *)current_filename);
current_filename = (const char *)malloc(len);
if (current_filename == 0)
fatal("can't malloc space for filename");
strncpy((char *)current_filename, (char *)fname, len);
}
//////////////////////////////////////////////////////////////////////
/* remember_source_filename():
Set global variable current_source_filename.
The actual filename is stored in current_filename. This is used by
the postprocessors, expecting the name "<standard input>" for stdin.
filename: In-out-parameter; is changed to the new value also.
*/
void
remember_source_filename(const char *filename)
{
char *fname;
if (strcmp(filename, "-") == 0)
fname = (char *)"<standard input>";
else
fname = (char *)filename;
size_t len = strlen(fname) + 1;
if (current_source_filename != 0)
free((char *)current_source_filename);
current_source_filename = (const char *)malloc(len);
if (current_source_filename == 0)
fatal("can't malloc space for filename");
strncpy((char *)current_source_filename, (char *)fname, len);
}
//////////////////////////////////////////////////////////////////////
/* send_draw():
Call draw method of printer class.
subcmd: Letter of actual D subcommand.
args: Array of integer arguments of actual D subcommand.
*/
void
send_draw(const Char subcmd, const IntArray * const args)
{
EnvInt n = (EnvInt) args->len();
pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
}
//////////////////////////////////////////////////////////////////////
/* skip_line():
Go to next line within the input queue.
Skip the rest of the current line, including the newline character.
The global variable current_lineno is adjusted.
No errors are raised.
*/
void
skip_line(void)
{
Char c = get_char();
while (1) {
if (c == '\n') {
current_lineno++;
break;
}
if (c == EOF)
break;
c = get_char();
}
}
//////////////////////////////////////////////////////////////////////
/* skip_line_checked ():
Check that there aren't any arguments left on the rest of the line,
then skip line.
Spaces, tabs, and a comment are allowed before newline or EOF.
All other characters raise an error.
*/
bool
skip_line_checked(void)
{
bool ok = true;
Char c = get_char();
while (is_space_or_tab(c))
c = get_char();
switch((int) c) {
case '#': // comment
skip_line();
break;
case '\n':
current_lineno++;
break;
case EOF:
break;
default:
ok = false;
skip_line();
break;
}
return ok;
}
//////////////////////////////////////////////////////////////////////
/* skip_line_fatal ():
Fatal error if arguments left, otherwise skip line.
Spaces, tabs, and a comment are allowed before newline or EOF.
All other characters trigger the error.
*/
void
skip_line_fatal(void)
{
bool ok = skip_line_checked();
if (!ok) {
current_lineno--;
error("too many arguments");
current_lineno++;
}
}
//////////////////////////////////////////////////////////////////////
/* skip_line_warn ():
Skip line, but warn if arguments are left on actual line.
Spaces, tabs, and a comment are allowed before newline or EOF.
All other characters raise a warning
*/
void
skip_line_warn(void)
{
bool ok = skip_line_checked();
if (!ok) {
current_lineno--;
warning("too many arguments on current line");
current_lineno++;
}
}
//////////////////////////////////////////////////////////////////////
/* skip_line_D ():
Skip line in `D' commands.
Decide whether in case of an additional argument a fatal error is
raised (the documented classical behavior), only a warning is
issued, or the line is just skipped (former groff behavior).
Actually decided for the warning.
*/
void
skip_line_D(void)
{
skip_line_warn();
// or: skip_line_fatal();
// or: skip_line();
}
//////////////////////////////////////////////////////////////////////
/* skip_line_x ():
Skip line in `x' commands.
Decide whether in case of an additional argument a fatal error is
raised (the documented classical behavior), only a warning is
issued, or the line is just skipped (former groff behavior).
Actually decided for the warning.
*/
void
skip_line_x(void)
{
skip_line_warn();
// or: skip_line_fatal();
// or: skip_line();
}
//////////////////////////////////////////////////////////////////////
/* skip_to_end_of_line():
Go to the end of the current line.
Skip the rest of the current line, excluding the newline character.
The global variable current_lineno is not changed.
No errors are raised.
*/
void
skip_to_end_of_line(void)
{
Char c = get_char();
while (1) {
if (c == '\n') {
unget_char(c);
return;
}
if (c == EOF)
return;
c = get_char();
}
}
//////////////////////////////////////////////////////////////////////
/* unget_char(c):
Restore character c onto input queue.
Write a character back onto the input stream.
EOF is gracefully handled.
c: In-parameter; character to be pushed onto the input queue.
*/
inline void
unget_char(const Char c)
{
if (c != EOF) {
int ch = (int) c;
if (ungetc(ch, current_file) == EOF)
fatal("could not unget character");
}
}
/**********************************************************************
parser subcommands
**********************************************************************/
//////////////////////////////////////////////////////////////////////
/* parse_color_command:
Process the commands m and DF, but not Df.
col: In-out-parameter; the color object to be set, must have
been initialized before.
*/
void
parse_color_command(color *col)
{
ColorArg gray = 0;
ColorArg red = 0, green = 0, blue = 0;
ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
Char subcmd = next_arg_begin();
switch((int) subcmd) {
case 'c': // DFc or mc: CMY
cyan = get_color_arg();
magenta = get_color_arg();
yellow = get_color_arg();
col->set_cmy(cyan, magenta, yellow);
break;
case 'd': // DFd or md: set default color
col->set_default();
break;
case 'g': // DFg or mg: gray
gray = get_color_arg();
col->set_gray(gray);
break;
case 'k': // DFk or mk: CMYK
cyan = get_color_arg();
magenta = get_color_arg();
yellow = get_color_arg();
black = get_color_arg();
col->set_cmyk(cyan, magenta, yellow, black);
break;
case 'r': // DFr or mr: RGB
red = get_color_arg();
green = get_color_arg();
blue = get_color_arg();
col->set_rgb(red, green, blue);
break;
default:
error("invalid color scheme `%1'", (int) subcmd);
break;
} // end of color subcommands
}
//////////////////////////////////////////////////////////////////////
/* parse_D_command():
Parse the subcommands of graphical command D.
This is the part of the do_file() parser that scans the graphical
subcommands.
- Error on lacking or wrong arguments.
- Warning on too many arguments.
- Line is always skipped.
*/
void
parse_D_command()
{
Char subcmd = next_arg_begin();
switch((int) subcmd) {
case '~': // D~: draw B-spline
// actually, this isn't available for some postprocessors
// fall through
default: // unknown options are passed to device
{
IntArray *args = get_D_variable_args();
send_draw(subcmd, args);
position_to_end_of_args(args);
delete args;
break;
}
case 'a': // Da: draw arc
{
IntArray *args = get_D_fixed_args(4);
send_draw(subcmd, args);
position_to_end_of_args(args);
delete args;
break;
}
case 'c': // Dc: draw circle line
{
IntArray *args = get_D_fixed_args(1);
send_draw(subcmd, args);
// move to right end
current_env->hpos += (*args)[0];
delete args;
break;
}
case 'C': // DC: draw solid circle
{
IntArray *args = get_D_fixed_args_odd_dummy(1);
send_draw(subcmd, args);
// move to right end
current_env->hpos += (*args)[0];
delete args;
break;
}
case 'e': // De: draw ellipse line
case 'E': // DE: draw solid ellipse
{
IntArray *args = get_D_fixed_args(2);
send_draw(subcmd, args);
// move to right end
current_env->hpos += (*args)[0];
delete args;
break;
}
case 'f': // Df: set fill gray; obsoleted by DFg
{
IntArg arg = get_integer_arg();
if ((arg >= 0) && (arg <= 1000)) {
// convert arg and treat it like DFg
ColorArg gray = color_from_Df_command(arg);
current_env->fill->set_gray(gray);
}
else {
// set fill color to the same value as the current outline color
delete current_env->fill;
current_env->fill = new color(current_env->col);
}
pr->change_fill_color(current_env);
// skip unused `vertical' component (\D'...' always emits pairs)
(void) get_integer_arg();
# ifdef STUPID_DRAWING_POSITIONING
current_env->hpos += arg;
# endif
skip_line_x();
break;
}
case 'F': // DF: set fill color, several formats
parse_color_command(current_env->fill);
pr->change_fill_color(current_env);
// no positioning (setting-only command)
skip_line_x();
break;
case 'l': // Dl: draw line
{
IntArray *args = get_D_fixed_args(2);
send_draw(subcmd, args);
position_to_end_of_args(args);
delete args;
break;
}
case 'p': // Dp: draw closed polygon line
case 'P': // DP: draw solid closed polygon
{
IntArray *args = get_D_variable_args();
send_draw(subcmd, args);
# ifdef STUPID_DRAWING_POSITIONING
// final args positioning
position_to_end_of_args(args);
# endif
delete args;
break;
}
case 't': // Dt: set line thickness
{
IntArray *args = get_D_fixed_args_odd_dummy(1);
send_draw(subcmd, args);
# ifdef STUPID_DRAWING_POSITIONING
// final args positioning
position_to_end_of_args(args);
# endif
delete args;
break;
}
} // end of D subcommands
}
//////////////////////////////////////////////////////////////////////
/* parse_x_command():
Parse subcommands of the device control command x.
This is the part of the do_file() parser that scans the device
controlling commands.
- Error on duplicate prologue commands.
- Error on wrong or lacking arguments.
- Warning on too many arguments.
- Line is always skipped.
Globals:
- current_env: is set by many subcommands.
- npages: page counting variable
Return: boolean in the meaning of `stopped'
- true if parsing should be stopped (`x stop').
- false if parsing should continue.
*/
bool
parse_x_command(void)
{
bool stopped = false;
char *subcmd_str = get_string_arg();
char subcmd = subcmd_str[0];
switch (subcmd) {
case 'f': // x font: mount font
{
IntArg n = get_integer_arg();
char *name = get_string_arg();
pr->load_font(n, name);
a_delete name;
skip_line_x();
break;
}
case 'F': // x Filename: set filename for errors
{
char *str_arg = get_string_arg();
if (str_arg == 0)
warning("empty argument for `x F' command");
else {
remember_source_filename(str_arg);
a_delete str_arg;
}
break;
}
case 'H': // x Height: set character height
current_env->height = get_integer_arg();
if (current_env->height == current_env->size)
current_env->height = 0;
skip_line_x();
break;
case 'i': // x init: initialize device
error("duplicate `x init' command");
skip_line_x();
break;
case 'p': // x pause: pause device
skip_line_x();
break;
case 'r': // x res: set resolution
error("duplicate `x res' command");
skip_line_x();
break;
case 's': // x stop: stop device
stopped = true;
skip_line_x();
break;
case 'S': // x Slant: set slant
current_env->slant = get_integer_arg();
skip_line_x();
break;
case 't': // x trailer: generate trailer info
skip_line_x();
break;
case 'T': // x Typesetter: set typesetter
error("duplicate `x T' command");
skip_line();
break;
case 'u': // x underline: from .cu
{
char *str_arg = get_string_arg();
pr->special(str_arg, current_env, 'u');
a_delete str_arg;
skip_line_x();
break;
}
case 'X': // x X: send uninterpretedly to device
{
char *str_arg = get_extended_arg(); // includes line skip
if (npages <= 0)
error("`x X' command invalid before first `p' command");
else if (str_arg && (strncmp(str_arg, "devtag:",
strlen("devtag:")) == 0))
pr->devtag(str_arg, current_env);
else
pr->special(str_arg, current_env);
a_delete str_arg;
break;
}
default: // ignore unknown x commands, but warn
warning("unknown command `x %1'", subcmd);
skip_line();
}
a_delete subcmd_str;
return stopped;
}
/**********************************************************************
exported part (by driver.h)
**********************************************************************/
////////////////////////////////////////////////////////////////////////
/* do_file():
Parse and postprocess groff intermediate output.
filename: "-" for standard input, normal file name otherwise
*/
void
do_file(const char *filename)
{
Char command;
bool stopped = false; // terminating condition
#ifdef USE_ENV_STACK
EnvStack env_stack = EnvStack();
#endif // USE_ENV_STACK
// setup of global variables
npages = 0;
current_lineno = 1;
// `pr' is initialized after the prologue.
// `device' is set by the 1st prologue command.
if (filename[0] == '-' && filename[1] == '\0')
current_file = stdin;
else {
errno = 0;
current_file = fopen(filename, "r");
if (errno != 0 || current_file == 0) {
error("can't open file `%1'", filename);
return;
}
}
remember_filename(filename);
if (current_env != 0)
delete_current_env();
current_env = new environment;
current_env->col = new color;
current_env->fill = new color;
current_env->fontno = -1;
current_env->height = 0;
current_env->hpos = -1;
current_env->slant = 0;
current_env->size = 0;
current_env->vpos = -1;
// parsing of prologue (first 3 commands)
{
char *str_arg;
IntArg int_arg;
// 1st command `x T'
command = next_command();
if ((int) command == EOF)
return;
if ((int) command != 'x')
fatal("the first command must be `x T'");
str_arg = get_string_arg();
if (str_arg[0] != 'T')
fatal("the first command must be `x T'");
a_delete str_arg;
char *tmp_dev = get_string_arg();
if (pr == 0) { // note: `pr' initialized after prologue
device = tmp_dev;
if (!font::load_desc())
fatal("couldn't load DESC file, can't continue");
}
else {
if (device == 0 || strcmp(device, tmp_dev) != 0)
fatal("all files must use the same device");
a_delete tmp_dev;
}
skip_line_x(); // ignore further arguments
current_env->size = 10 * font::sizescale;
// 2nd command `x res'
command = next_command();
if ((int) command != 'x')
fatal("the second command must be `x res'");
str_arg = get_string_arg();
if (str_arg[0] != 'r')
fatal("the second command must be `x res'");
a_delete str_arg;
int_arg = get_integer_arg();
EnvInt font_res = font::res;
if (int_arg != font_res)
fatal("resolution does not match");
int_arg = get_integer_arg();
if (int_arg != font::hor)
fatal("minimum horizontal motion does not match");
int_arg = get_integer_arg();
if (int_arg != font::vert)
fatal("minimum vertical motion does not match");
skip_line_x(); // ignore further arguments
// 3rd command `x init'
command = next_command();
if (command != 'x')
fatal("the third command must be `x init'");
str_arg = get_string_arg();
if (str_arg[0] != 'i')
fatal("the third command must be `x init'");
a_delete str_arg;
skip_line_x();
}
// parsing of body
if (pr == 0)
pr = make_printer();
while (!stopped) {
command = next_command();
if (command == EOF)
break;
// spaces, tabs, comments, and newlines are skipped here
switch ((int) command) {
case '#': // #: comment, ignore up to end of line
skip_line();
break;
#ifdef USE_ENV_STACK
case '{': // {: start a new environment (a copy)
env_stack.push(current_env);
break;
case '}': // }: pop previous env from stack
delete_current_env();
current_env = env_stack.pop();
break;
#endif // USE_ENV_STACK
case '0': // ddc: obsolete jump and print command
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{ // expect 2 digits and a character
char s[3];
Char c = next_arg_begin();
if (npages <= 0)
fatal_command(command);
if (!isdigit((int) c)) {
error("digit expected");
c = 0;
}
s[0] = (char) command;
s[1] = (char) c;
s[2] = '\0';
errno = 0;
long int x = strtol(s, 0, 10);
if (errno != 0)
error("couldn't convert 2 digits");
EnvInt hor_pos = (EnvInt) x;
current_env->hpos += hor_pos;
c = next_arg_begin();
if ((int) c == '\n' || (int) c == EOF)
error("character argument expected");
else
pr->set_ascii_char((unsigned char) c, current_env);
break;
}
case 'c': // c: print ascii char without moving
{
if (npages <= 0)
fatal_command(command);
Char c = next_arg_begin();
if (c == '\n' || c == EOF)
error("missing argument to `c' command");
else
pr->set_ascii_char((unsigned char) c, current_env);
break;
}
case 'C': // C: print named special character
{
if (npages <= 0)
fatal_command(command);
char *str_arg = get_string_arg();
pr->set_special_char(str_arg, current_env);
a_delete str_arg;
break;
}
case 'D': // drawing commands
if (npages <= 0)
fatal_command(command);
parse_D_command();
break;
case 'f': // f: set font to number
current_env->fontno = get_integer_arg();
break;
case 'F': // F: obsolete, replaced by `x F'
{
char *str_arg = get_string_arg();
remember_source_filename(str_arg);
a_delete str_arg;
break;
}
case 'h': // h: relative horizontal move
current_env->hpos += (EnvInt) get_integer_arg();
break;
case 'H': // H: absolute horizontal positioning
current_env->hpos = (EnvInt) get_integer_arg();
break;
case 'm': // m: glyph color
parse_color_command(current_env->col);
pr->change_color(current_env);
break;
case 'n': // n: print end of line
// ignore two arguments (historically)
if (npages <= 0)
fatal_command(command);
pr->end_of_line();
(void) get_integer_arg();
(void) get_integer_arg();
break;
case 'N': // N: print char with given int code
if (npages <= 0)
fatal_command(command);
pr->set_numbered_char(get_integer_arg(), current_env);
break;
case 'p': // p: start new page with given number
if (npages > 0)
pr->end_page(current_env->vpos);
npages++; // increment # of processed pages
pr->begin_page(get_integer_arg());
current_env->vpos = 0;
break;
case 's': // s: set point size
current_env->size = get_integer_arg();
if (current_env->height == current_env->size)
current_env->height = 0;
break;
case 't': // t: print a text word
{
char c;
if (npages <= 0)
fatal_command(command);
char *str_arg = get_string_arg();
size_t i = 0;
while ((c = str_arg[i++]) != '\0') {
EnvInt w;
pr->set_ascii_char((unsigned char) c, current_env, &w);
current_env->hpos += w;
}
a_delete str_arg;
break;
}
case 'u': // u: print spaced word
{
char c;
if (npages <= 0)
fatal_command(command);
EnvInt kern = (EnvInt) get_integer_arg();
char *str_arg = get_string_arg();
size_t i = 0;
while ((c = str_arg[i++]) != '\0') {
EnvInt w;
pr->set_ascii_char((unsigned char) c, current_env, &w);
current_env->hpos += w + kern;
}
a_delete str_arg;
break;
}
case 'v': // v: relative vertical move
current_env->vpos += (EnvInt) get_integer_arg();
break;
case 'V': // V: absolute vertical positioning
current_env->vpos = (EnvInt) get_integer_arg();
break;
case 'w': // w: inform about paddable space
break;
case 'x': // device controlling commands
stopped = parse_x_command();
break;
default:
warning("unrecognized command `%1'", (unsigned char) command);
skip_line();
break;
} // end of switch
} // end of while
// end of file reached
if (npages > 0)
pr->end_page(current_env->vpos);
delete pr;
pr = 0;
fclose(current_file);
// If `stopped' is not `true' here then there wasn't any `x stop'.
if (!stopped)
warning("no final `x stop' command");
delete_current_env();
}