Home | History | Annotate | Line # | Download | only in libdriver
      1 /*	$NetBSD: input.cpp,v 1.2 2016/01/13 19:01:58 christos Exp $	*/
      2 
      3 // -*- C++ -*-
      4 
      5 // <groff_src_dir>/src/libs/libdriver/input.cpp
      6 
      7 /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005
      8    Free Software Foundation, Inc.
      9 
     10    Written by James Clark (jjc (at) jclark.com)
     11    Major rewrite 2001 by Bernd Warken (bwarken (at) mayn.de)
     12 
     13    Last update: 15 Jun 2005
     14 
     15    This file is part of groff, the GNU roff text processing system.
     16 
     17    groff is free software; you can redistribute it and/or modify it
     18    under the terms of the GNU General Public License as published by
     19    the Free Software Foundation; either version 2, or (at your option)
     20    any later version.
     21 
     22    groff is distributed in the hope that it will be useful, but
     23    WITHOUT ANY WARRANTY; without even the implied warranty of
     24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     25    General Public License for more details.
     26 
     27    You should have received a copy of the GNU General Public License
     28    along with groff; see the file COPYING.  If not, write to the Free
     29    Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA
     30    02110-1301, USA.
     31 */
     32 
     33 /* Description
     34 
     35    This file implements the parser for the intermediate groff output,
     36    see groff_out(5), and does the printout for the given device.
     37 
     38    All parsed information is processed within the function do_file().
     39    A device postprocessor just needs to fill in the methods for the class
     40    `printer' (or rather a derived class) without having to worry about
     41    the syntax of the intermediate output format.  Consequently, the
     42    programming of groff postprocessors is similar to the development of
     43    device drivers.
     44 
     45    The prototyping for this file is done in driver.h (and error.h).
     46 */
     47 
     48 /* Changes of the 2001 rewrite of this file.
     49 
     50    The interface to the outside and the handling of the global
     51    variables was not changed, but internally many necessary changes
     52    were performed.
     53 
     54    The main aim for this rewrite is to provide a first step towards
     55    making groff fully compatible with classical troff without pain.
     56 
     57    Bugs fixed
     58    - Unknown subcommands of `D' and `x' are now ignored like in the
     59      classical case, but a warning is issued.  This was also
     60      implemented for the other commands.
     61    - A warning is emitted if `x stop' is missing.
     62    - `DC' and `DE' commands didn't position to the right end after
     63      drawing (now they do), see discussion below.
     64    - So far, `x stop' was ignored.  Now it terminates the processing
     65      of the current intermediate output file like the classical troff.
     66    - The command `c' didn't check correctly on white-space.
     67    - The environment stack wasn't suitable for the color extensions
     68      (replaced by a class).
     69    - The old groff parser could only handle a prologue with the first
     70      3 lines having a fixed structure, while classical troff specified
     71      the sequence of the first 3 commands without further
     72      restrictions.  Now the parser is smart about additional
     73      white space, comments, and empty lines in the prologue.
     74    - The old parser allowed space characters only as syntactical
     75      separators, while classical troff had tab characters as well.
     76      Now any sequence of tabs and/or spaces is a syntactical
     77      separator between commands and/or arguments.
     78    - Range checks for numbers implemented.
     79 
     80    New and improved features
     81    - The color commands `m' and `DF' are added.
     82    - The old color command `Df' is now converted and delegated to `DFg'.
     83    - The command `F' is implemented as `use intended file name'.  It
     84      checks whether its argument agrees with the file name used so far,
     85      otherwise a warning is issued.  Then the new name is remembered
     86      and used for the following error messages.
     87    - For the positioning after drawing commands, an alternative, easier
     88      scheme is provided, but not yet activated; it can be chosen by
     89      undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
     90      It extends the rule of the classical troff output language in a
     91      logical way instead of the rather strange actual positioning.
     92      For details, see the discussion below.
     93    - For the `D' commands that only set the environment, the calling of
     94      pr->send_draw() was removed because this doesn't make sense for
     95      the `DF' commands; the (changed) environment is sent with the
     96      next command anyway.
     97    - Error handling was clearly separated into warnings and fatal.
     98    - The error behavior on additional arguments for `D' and `x'
     99      commands with a fixed number of arguments was changed from being
    100      ignored (former groff) to issue a warning and ignore (now), see
    101      skip_line_x().  No fatal was chosen because both string and
    102      integer arguments can occur.
    103    - The gtroff program issues a trailing dummy integer argument for
    104      some drawing commands with an odd number of arguments to make the
    105      number of arguments even, e.g. the DC and Dt commands; this is
    106      honored now.
    107    - All D commands with a variable number of args expect an even
    108      number of trailing integer arguments, so fatal on error was
    109      implemented.
    110    - Disable environment stack and the commands `{' and `}' by making
    111      them conditional on macro USE_ENV_STACK; actually, this is
    112      undefined by default.  There isn't any known application for these
    113      features.
    114 
    115    Cosmetics
    116    - Nested `switch' commands are avoided by using more functions.
    117      Dangerous 'fall-through's avoided.
    118    - Commands and functions are sorted alphabetically (where possible).
    119    - Dynamic arrays/buffers are now implemented as container classes.
    120    - Some functions had an ugly return structure; this has been
    121      streamlined by using classes.
    122    - Use standard C math functions for number handling, so getting rid
    123      of differences to '0'.
    124    - The macro `IntArg' has been created for an easier transition
    125      to guaranteed 32 bits integers (`int' is enough for GNU, while
    126      ANSI only guarantees `long int' to have a length of 32 bits).
    127    - The many usages of type `int' are differentiated by using `Char',
    128      `bool', and `IntArg' where appropriate.
    129    - To ease the calls of the local utility functions, the parser
    130      variables `current_file', `npages', and `current_env'
    131      (formerly env) were made global to the file (formerly they were
    132      local to the do_file() function)
    133    - Various comments were added.
    134 
    135    TODO
    136    - Get rid of the stupid drawing positioning.
    137    - Can the `Dt' command be completely handled by setting environment
    138      within do_file() instead of sending to pr?
    139    - Integer arguments must be >= 32 bits, use conditional #define.
    140    - Add scaling facility for classical device independence and
    141      non-groff devices.  Classical troff output had a quasi device
    142      independence by scaling the intermediate output to the resolution
    143      of the postprocessor device if different from the one specified
    144      with `x T', groff have not.  So implement full quasi device
    145      indepedence, including the mapping of the strange classical
    146      devices to the postprocessor device (seems to be reasonably
    147      easy).
    148    - The external, global pointer variables are not optimally handled.
    149      - The global variables `current_filename',
    150        `current_source_filename', and `current_lineno' are only used for
    151        error reporting.  So implement a static class `Error'
    152        (`::' calls).
    153      - The global `device' is the name used during the formatting
    154        process; there should be a new variable for the device name used
    155        during the postprocessing.
    156   - Implement the B-spline drawing `D~' for all graphical devices.
    157   - Make `environment' a class with an overflow check for its members
    158     and a delete method to get rid of delete_current_env().
    159   - Implement the `EnvStack' to use `new' instead of `malloc'.
    160   - The class definitions of this document could go into a new file.
    161   - The comments in this section should go to a `Changelog' or some
    162     `README' file in this directory.
    163 */
    164 
    165 /*
    166   Discussion of the positioning by drawing commands
    167 
    168   There was some confusion about the positioning of the graphical
    169   pointer at the printout after having executed a `D' command.
    170   The classical troff manual of Osanna & Kernighan specified,
    171 
    172     `The position after a graphical object has been drawn is
    173      at its end; for circles and ellipses, the "end" is at the
    174      right side.'
    175 
    176   From this, it follows that
    177   - all open figures (args, splines, and lines) should position at their
    178     final point.
    179   - all circles and ellipses should position at their right-most point
    180     (as if 2 halves had been drawn).
    181   - all closed figures apart from circles and ellipses shouldn't change
    182     the position because they return to their origin.
    183   - all setting commands should not change position because they do not
    184     draw any graphical object.
    185 
    186   In the case of the open figures, this means that the horizontal
    187   displacement is the sum of all odd arguments and the vertical offset
    188   the sum of all even arguments, called the alternate arguments sum
    189   displacement in the following.
    190 
    191   Unfortunately, groff did not implement this simple rule.  The former
    192   documentation in groff_out(5) differed from the source code, and
    193   neither of them is compatible with the classical rule.
    194 
    195   The former groff_out(5) specified to use the alternative arguments
    196   sum displacement for calculating the drawing positioning of
    197   non-classical commands, including the `Dt' command (setting-only)
    198   and closed polygons.  Applying this to the new groff color commands
    199   will lead to disaster.  For their arguments can take large values (>
    200   65000).  On low resolution devices, the displacement of such large
    201   values will corrupt the display or kill the printer.  So the
    202   nonsense specification has come to a natural end anyway.
    203 
    204   The groff source code, however, had no positioning for the
    205   setting-only commands (esp. `Dt'), the right-end positioning for
    206   outlined circles and ellipses, and the alternative argument sum
    207   displacement for all other commands (including filled circles and
    208   ellipses).
    209 
    210   The reason why no one seems to have suffered from this mayhem so
    211   far is that the graphical objects are usually generated by
    212   preprocessors like pic that do not depend on the automatic
    213   positioning.  When using the low level `\D' escape sequences or `D'
    214   output commands, the strange positionings can be circumvented by
    215   absolute positionings or by tricks like `\Z'.
    216 
    217   So doing an exorcism on the strange, incompatible displacements might
    218   not harm any existing documents, but will make the usage of the
    219   graphical escape sequences and commands natural.
    220 
    221   That's why the rewrite of this file returned to the reasonable,
    222   classical specification with its clear end-of-drawing rule that is
    223   suitable for all cases.  But a macro STUPID_DRAWING_POSITIONING is
    224   provided for testing the funny former behavior.
    225 
    226   The new rule implies the following behavior.
    227   - Setting commands (`Dt', `Df', `DF') and polygons (`Dp' and `DP')
    228     do not change position now.
    229   - Filled circles and ellipses (`DC' and `DE') position at their
    230     most right point (outlined ones `Dc' and `De' did this anyway).
    231   - As before, all open graphical objects position to their final
    232     drawing point (alternate sum of the command arguments).
    233 
    234 */
    235 
    236 #ifndef STUPID_DRAWING_POSITIONING
    237 // uncomment next line if all non-classical D commands shall position
    238 // to the strange alternate sum of args displacement
    239 #define STUPID_DRAWING_POSITIONING
    240 #endif
    241 
    242 // Decide whether the commands `{' and `}' for different environments
    243 // should be used.
    244 #undef USE_ENV_STACK
    245 
    246 #include "driver.h"
    247 #include "device.h"
    248 
    249 #include <stdlib.h>
    250 #include <errno.h>
    251 #include <ctype.h>
    252 #include <math.h>
    253 
    254 
    255 /**********************************************************************
    256                            local types
    257  **********************************************************************/
    258 
    259 // integer type used in the fields of struct environment (see printer.h)
    260 typedef int EnvInt;
    261 
    262 // integer arguments of groff_out commands, must be >= 32 bits
    263 typedef int IntArg;
    264 
    265 // color components of groff_out color commands, must be >= 32 bits
    266 typedef unsigned int ColorArg;
    267 
    268 // Array for IntArg values.
    269 class IntArray {
    270   size_t num_allocated;
    271   size_t num_stored;
    272   IntArg *data;
    273 public:
    274   IntArray(void);
    275   IntArray(const size_t);
    276   ~IntArray(void);
    277   IntArg operator[](const size_t i) const
    278   {
    279     if (i >= num_stored)
    280       fatal("index out of range");
    281     return (IntArg) data[i];
    282   }
    283   void append(IntArg);
    284   IntArg *get_data(void) const { return (IntArg *)data; }
    285   size_t len(void) const { return num_stored; }
    286 };
    287 
    288 // Characters read from the input queue.
    289 class Char {
    290   int data;
    291 public:
    292   Char(void) : data('\0') {}
    293   Char(const int c) : data(c) {}
    294   bool operator==(char c) const { return (data == c) ? true : false; }
    295   bool operator==(int c) const { return (data == c) ? true : false; }
    296   bool operator==(const Char c) const
    297 		  { return (data == c.data) ? true : false; }
    298   bool operator!=(char c) const { return !(*this == c); }
    299   bool operator!=(int c) const { return !(*this == c); }
    300   bool operator!=(const Char c) const { return !(*this == c); }
    301   operator int() const { return (int) data; }
    302   operator unsigned char() const { return (unsigned char) data; }
    303   operator char() const { return (char) data; }
    304 };
    305 
    306 // Buffer for string arguments (Char, not char).
    307 class StringBuf {
    308   size_t num_allocated;
    309   size_t num_stored;
    310   Char *data;			// not terminated by '\0'
    311 public:
    312   StringBuf(void);		// allocate without storing
    313   ~StringBuf(void);
    314   void append(const Char);	// append character to `data'
    315   char *make_string(void);	// return new copy of `data' with '\0'
    316   bool is_empty(void) {		// true if none stored
    317     return (num_stored > 0) ? false : true;
    318   }
    319   void reset(void);		// set `num_stored' to 0
    320 };
    321 
    322 #ifdef USE_ENV_STACK
    323 class EnvStack {
    324   environment **data;
    325   size_t num_allocated;
    326   size_t num_stored;
    327 public:
    328   EnvStack(void);
    329   ~EnvStack(void);
    330   environment *pop(void);
    331   void push(environment *e);
    332 };
    333 #endif // USE_ENV_STACK
    334 
    335 
    336 /**********************************************************************
    337                           external variables
    338  **********************************************************************/
    339 
    340 // exported as extern by error.h (called from driver.h)
    341 // needed for error messages (see ../libgroff/error.cpp)
    342 const char *current_filename = 0; // printable name of the current file
    343 				  // printable name of current source file
    344 const char *current_source_filename = 0;
    345 int current_lineno = 0;		  // current line number of printout
    346 
    347 // exported as extern by device.h;
    348 const char *device = 0;		  // cancel former init with literal
    349 
    350 printer *pr;
    351 
    352 // Note:
    353 //
    354 //   We rely on an implementation of the `new' operator which aborts
    355 //   gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
    356 
    357 
    358 /**********************************************************************
    359                         static local variables
    360  **********************************************************************/
    361 
    362 FILE *current_file = 0;		// current input stream for parser
    363 
    364 // npages: number of pages processed so far (including current page),
    365 //         _not_ the page number in the printout (can be set with `p').
    366 int npages = 0;
    367 
    368 const ColorArg
    369 COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
    370 
    371 const IntArg
    372 INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
    373 
    374 // parser environment, created and deleted by each run of do_file()
    375 environment *current_env = 0;
    376 
    377 #ifdef USE_ENV_STACK
    378 const size_t
    379 envp_size = sizeof(environment *);
    380 #endif // USE_ENV_STACK
    381 
    382 
    383 /**********************************************************************
    384                         function declarations
    385  **********************************************************************/
    386 
    387 // utility functions
    388 ColorArg color_from_Df_command(IntArg);
    389 				// transform old color into new
    390 void delete_current_env(void);	// delete global var current_env
    391 void fatal_command(char);	// abort for invalid command
    392 inline Char get_char(void);	// read next character from input stream
    393 ColorArg get_color_arg(void);	// read in argument for new color cmds
    394 IntArray *get_D_fixed_args(const size_t);
    395 				// read in fixed number of integer
    396 				// arguments
    397 IntArray *get_D_fixed_args_odd_dummy(const size_t);
    398 				// read in a fixed number of integer
    399 				// arguments plus optional dummy
    400 IntArray *get_D_variable_args(void);
    401                                 // variable, even number of int args
    402 char *get_extended_arg(void);	// argument for `x X' (several lines)
    403 IntArg get_integer_arg(void);	// read in next integer argument
    404 IntArray *get_possibly_integer_args();
    405 				// 0 or more integer arguments
    406 char *get_string_arg(void);	// read in next string arg, ended by WS
    407 inline bool is_space_or_tab(const Char);
    408 				// test on space/tab char
    409 Char next_arg_begin(void);	// skip white space on current line
    410 Char next_command(void);	// go to next command, evt. diff. line
    411 inline bool odd(const int);	// test if integer is odd
    412 void position_to_end_of_args(const IntArray * const);
    413 				// positioning after drawing
    414 void remember_filename(const char *);
    415 				// set global current_filename
    416 void remember_source_filename(const char *);
    417 				// set global current_source_filename
    418 void send_draw(const Char, const IntArray * const);
    419 				// call pr->draw
    420 void skip_line(void);		// unconditionally skip to next line
    421 bool skip_line_checked(void);	// skip line, false if args are left
    422 void skip_line_fatal(void);	// skip line, fatal if args are left
    423 void skip_line_warn(void);	// skip line, warn if args are left
    424 void skip_line_D(void);		// skip line in D commands
    425 void skip_line_x(void);		// skip line in x commands
    426 void skip_to_end_of_line(void);	// skip to the end of the current line
    427 inline void unget_char(const Char);
    428 				// restore character onto input
    429 
    430 // parser subcommands
    431 void parse_color_command(color *);
    432 				// color sub(sub)commands m and DF
    433 void parse_D_command(void);	// graphical subcommands
    434 bool parse_x_command(void);	// device controller subcommands
    435 
    436 
    437 /**********************************************************************
    438                          class methods
    439  **********************************************************************/
    440 
    441 #ifdef USE_ENV_STACK
    442 EnvStack::EnvStack(void)
    443 {
    444   num_allocated = 4;
    445   // allocate pointer to array of num_allocated pointers to environment
    446   data = (environment **)malloc(envp_size * num_allocated);
    447   if (data == 0)
    448     fatal("could not allocate environment data");
    449   num_stored = 0;
    450 }
    451 
    452 EnvStack::~EnvStack(void)
    453 {
    454   for (size_t i = 0; i < num_stored; i++)
    455     delete data[i];
    456   free(data);
    457 }
    458 
    459 // return top element from stack and decrease stack pointer
    460 //
    461 // the calling function must take care of properly deleting the result
    462 environment *
    463 EnvStack::pop(void)
    464 {
    465   num_stored--;
    466   environment *result = data[num_stored];
    467   data[num_stored] = 0;
    468   return result;
    469 }
    470 
    471 // copy argument and push this onto the stack
    472 void
    473 EnvStack::push(environment *e)
    474 {
    475   environment *e_copy = new environment;
    476   if (num_stored >= num_allocated) {
    477     environment **old_data = data;
    478     num_allocated *= 2;
    479     data = (environment **)malloc(envp_size * num_allocated);
    480     if (data == 0)
    481       fatal("could not allocate data");
    482     for (size_t i = 0; i < num_stored; i++)
    483       data[i] = old_data[i];
    484     free(old_data);
    485   }
    486   e_copy->col = new color;
    487   e_copy->fill = new color;
    488   *e_copy->col = *e->col;
    489   *e_copy->fill = *e->fill;
    490   e_copy->fontno = e->fontno;
    491   e_copy->height = e->height;
    492   e_copy->hpos = e->hpos;
    493   e_copy->size = e->size;
    494   e_copy->slant = e->slant;
    495   e_copy->vpos = e->vpos;
    496   data[num_stored] = e_copy;
    497   num_stored++;
    498 }
    499 #endif // USE_ENV_STACK
    500 
    501 IntArray::IntArray(void)
    502 {
    503   num_allocated = 4;
    504   data = new IntArg[num_allocated];
    505   num_stored = 0;
    506 }
    507 
    508 IntArray::IntArray(const size_t n)
    509 {
    510   if (n <= 0)
    511     fatal("number of integers to be allocated must be > 0");
    512   num_allocated = n;
    513   data = new IntArg[num_allocated];
    514   num_stored = 0;
    515 }
    516 
    517 IntArray::~IntArray(void)
    518 {
    519   a_delete data;
    520 }
    521 
    522 void
    523 IntArray::append(IntArg x)
    524 {
    525   if (num_stored >= num_allocated) {
    526     IntArg *old_data = data;
    527     num_allocated *= 2;
    528     data = new IntArg[num_allocated];
    529     for (size_t i = 0; i < num_stored; i++)
    530       data[i] = old_data[i];
    531     a_delete old_data;
    532   }
    533   data[num_stored] = x;
    534   num_stored++;
    535 }
    536 
    537 StringBuf::StringBuf(void)
    538 {
    539   num_stored = 0;
    540   num_allocated = 128;
    541   data = new Char[num_allocated];
    542 }
    543 
    544 StringBuf::~StringBuf(void)
    545 {
    546   a_delete data;
    547 }
    548 
    549 void
    550 StringBuf::append(const Char c)
    551 {
    552   if (num_stored >= num_allocated) {
    553     Char *old_data = data;
    554     num_allocated *= 2;
    555     data = new Char[num_allocated];
    556     for (size_t i = 0; i < num_stored; i++)
    557       data[i] = old_data[i];
    558     a_delete old_data;
    559   }
    560   data[num_stored] = c;
    561   num_stored++;
    562 }
    563 
    564 char *
    565 StringBuf::make_string(void)
    566 {
    567   char *result = new char[num_stored + 1];
    568   for (size_t i = 0; i < num_stored; i++)
    569     result[i] = (char) data[i];
    570   result[num_stored] = '\0';
    571   return result;
    572 }
    573 
    574 void
    575 StringBuf::reset(void)
    576 {
    577   num_stored = 0;
    578 }
    579 
    580 /**********************************************************************
    581                         utility functions
    582  **********************************************************************/
    583 
    584 //////////////////////////////////////////////////////////////////////
    585 /* color_from_Df_command:
    586    Process the gray shade setting command Df.
    587 
    588    Transform Df style color into DF style color.
    589    Df color: 0-1000, 0 is white
    590    DF color: 0-65536, 0 is black
    591 
    592    The Df command is obsoleted by command DFg, but kept for
    593    compatibility.
    594 */
    595 ColorArg
    596 color_from_Df_command(IntArg Df_gray)
    597 {
    598   return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
    599 }
    600 
    601 //////////////////////////////////////////////////////////////////////
    602 /* delete_current_env():
    603    Delete global variable current_env and its pointer members.
    604 
    605    This should be a class method of environment.
    606 */
    607 void delete_current_env(void)
    608 {
    609   delete current_env->col;
    610   delete current_env->fill;
    611   delete current_env;
    612   current_env = 0;
    613 }
    614 
    615 //////////////////////////////////////////////////////////////////////
    616 /* fatal_command():
    617    Emit error message about invalid command and abort.
    618 */
    619 void
    620 fatal_command(char command)
    621 {
    622   fatal("`%1' command invalid before first `p' command", command);
    623 }
    624 
    625 //////////////////////////////////////////////////////////////////////
    626 /* get_char():
    627    Retrieve the next character from the input queue.
    628 
    629    Return: The retrieved character (incl. EOF), converted to Char.
    630 */
    631 inline Char
    632 get_char(void)
    633 {
    634   return (Char) getc(current_file);
    635 }
    636 
    637 //////////////////////////////////////////////////////////////////////
    638 /* get_color_arg():
    639    Retrieve an argument suitable for the color commands m and DF.
    640 
    641    Return: The retrieved color argument.
    642 */
    643 ColorArg
    644 get_color_arg(void)
    645 {
    646   IntArg x = get_integer_arg();
    647   if (x < 0 || x > (IntArg)COLORARG_MAX) {
    648     error("color component argument out of range");
    649     x = 0;
    650   }
    651   return (ColorArg) x;
    652 }
    653 
    654 //////////////////////////////////////////////////////////////////////
    655 /* get_D_fixed_args():
    656    Get a fixed number of integer arguments for D commands.
    657 
    658    Fatal if wrong number of arguments.
    659    Too many arguments on the line raise a warning.
    660    A line skip is done.
    661 
    662    number: In-parameter, the number of arguments to be retrieved.
    663    ignore: In-parameter, ignore next argument -- GNU troff always emits
    664            pairs of parameters for `D' extensions added by groff.
    665            Default is `false'.
    666 
    667    Return: New IntArray containing the arguments.
    668 */
    669 IntArray *
    670 get_D_fixed_args(const size_t number)
    671 {
    672   if (number <= 0)
    673     fatal("requested number of arguments must be > 0");
    674   IntArray *args = new IntArray(number);
    675   for (size_t i = 0; i < number; i++)
    676     args->append(get_integer_arg());
    677   skip_line_D();
    678   return args;
    679 }
    680 
    681 //////////////////////////////////////////////////////////////////////
    682 /* get_D_fixed_args_odd_dummy():
    683    Get a fixed number of integer arguments for D commands and optionally
    684    ignore a dummy integer argument if the requested number is odd.
    685 
    686    The gtroff program adds a dummy argument to some commands to get
    687    an even number of arguments.
    688    Error if the number of arguments differs from the scheme above.
    689    A line skip is done.
    690 
    691    number: In-parameter, the number of arguments to be retrieved.
    692 
    693    Return: New IntArray containing the arguments.
    694 */
    695 IntArray *
    696 get_D_fixed_args_odd_dummy(const size_t number)
    697 {
    698   if (number <= 0)
    699     fatal("requested number of arguments must be > 0");
    700   IntArray *args = new IntArray(number);
    701   for (size_t i = 0; i < number; i++)
    702     args->append(get_integer_arg());
    703   if (odd(number)) {
    704     IntArray *a = get_possibly_integer_args();
    705     if (a->len() > 1)
    706       error("too many arguments");
    707     delete a;
    708   }
    709   skip_line_D();
    710   return args;
    711 }
    712 
    713 //////////////////////////////////////////////////////////////////////
    714 /* get_D_variable_args():
    715    Get a variable even number of integer arguments for D commands.
    716 
    717    Get as many integer arguments as possible from the rest of the
    718    current line.
    719    - The arguments are separated by an arbitrary sequence of space or
    720      tab characters.
    721    - A comment, a newline, or EOF indicates the end of processing.
    722    - Error on non-digit characters different from these.
    723    - A final line skip is performed (except for EOF).
    724 
    725    Return: New IntArray of the retrieved arguments.
    726 */
    727 IntArray *
    728 get_D_variable_args()
    729 {
    730   IntArray *args = get_possibly_integer_args();
    731   size_t n = args->len();
    732   if (n <= 0)
    733     error("no arguments found");
    734   if (odd(n))
    735     error("even number of arguments expected");
    736   skip_line_D();
    737   return args;
    738 }
    739 
    740 //////////////////////////////////////////////////////////////////////
    741 /* get_extended_arg():
    742    Retrieve extended arg for `x X' command.
    743 
    744    - Skip leading spaces and tabs, error on EOL or newline.
    745    - Return everything before the next NL or EOF ('#' is not a comment);
    746      as long as the following line starts with '+' this is returned
    747      as well, with the '+' replaced by a newline.
    748    - Final line skip is always performed.
    749 
    750    Return: Allocated (new) string of retrieved text argument.
    751 */
    752 char *
    753 get_extended_arg(void)
    754 {
    755   StringBuf buf = StringBuf();
    756   Char c = next_arg_begin();
    757   while ((int) c != EOF) {
    758     if ((int) c == '\n') {
    759       current_lineno++;
    760       c = get_char();
    761       if ((int) c == '+')
    762 	buf.append((Char) '\n');
    763       else {
    764 	unget_char(c);		// first character of next line
    765 	break;
    766       }
    767     }
    768     else
    769       buf.append(c);
    770     c = get_char();
    771   }
    772   return buf.make_string();
    773 }
    774 
    775 //////////////////////////////////////////////////////////////////////
    776 /* get_integer_arg(): Retrieve integer argument.
    777 
    778    Skip leading spaces and tabs, collect an optional '-' and all
    779    following decimal digits (at least one) up to the next non-digit,
    780    which is restored onto the input queue.
    781 
    782    Fatal error on all other situations.
    783 
    784    Return: Retrieved integer.
    785 */
    786 IntArg
    787 get_integer_arg(void)
    788 {
    789   StringBuf buf = StringBuf();
    790   Char c = next_arg_begin();
    791   if ((int) c == '-') {
    792     buf.append(c);
    793     c = get_char();
    794   }
    795   if (!isdigit((int) c))
    796     error("integer argument expected");
    797   while (isdigit((int) c)) {
    798     buf.append(c);
    799     c = get_char();
    800   }
    801   // c is not a digit
    802   unget_char(c);
    803   char *s = buf.make_string();
    804   errno = 0;
    805   long int number = strtol(s, 0, 10);
    806   if (errno != 0
    807       || number > INTARG_MAX || number < -INTARG_MAX) {
    808     error("integer argument too large");
    809     number = 0;
    810   }
    811   a_delete s;
    812   return (IntArg) number;
    813 }
    814 
    815 //////////////////////////////////////////////////////////////////////
    816 /* get_possibly_integer_args():
    817    Parse the rest of the input line as a list of integer arguments.
    818 
    819    Get as many integer arguments as possible from the rest of the
    820    current line, even none.
    821    - The arguments are separated by an arbitrary sequence of space or
    822      tab characters.
    823    - A comment, a newline, or EOF indicates the end of processing.
    824    - Error on non-digit characters different from these.
    825    - No line skip is performed.
    826 
    827    Return: New IntArray of the retrieved arguments.
    828 */
    829 IntArray *
    830 get_possibly_integer_args()
    831 {
    832   bool done = false;
    833   StringBuf buf = StringBuf();
    834   Char c = get_char();
    835   IntArray *args = new IntArray();
    836   while (!done) {
    837     buf.reset();
    838     while (is_space_or_tab(c))
    839       c = get_char();
    840     if (c == '-') {
    841       Char c1 = get_char();
    842       if (isdigit((int) c1)) {
    843 	buf.append(c);
    844 	c = c1;
    845       }
    846       else
    847 	unget_char(c1);
    848     }
    849     while (isdigit((int) c)) {
    850       buf.append(c);
    851       c = get_char();
    852     }
    853     if (!buf.is_empty()) {
    854       char *s = buf.make_string();
    855       errno = 0;
    856       long int x = strtol(s, 0, 10);
    857       if (errno
    858 	  || x > INTARG_MAX || x < -INTARG_MAX) {
    859 	error("invalid integer argument, set to 0");
    860 	x = 0;
    861       }
    862       args->append((IntArg) x);
    863       a_delete s;
    864     }
    865     // Here, c is not a digit.
    866     // Terminate on comment, end of line, or end of file, while
    867     // space or tab indicate continuation; otherwise error.
    868     switch((int) c) {
    869     case '#':
    870       skip_to_end_of_line();
    871       done = true;
    872       break;
    873     case '\n':
    874       done = true;
    875       unget_char(c);
    876       break;
    877     case EOF:
    878       done = true;
    879       break;
    880     case ' ':
    881     case '\t':
    882       break;
    883     default:
    884       error("integer argument expected");
    885       break;
    886     }
    887   }
    888   return args;
    889 }
    890 
    891 //////////////////////////////////////////////////////////////////////
    892 /* get_string_arg():
    893    Retrieve string arg.
    894 
    895    - Skip leading spaces and tabs; error on EOL or newline.
    896    - Return all following characters before the next space, tab,
    897      newline, or EOF character (in-word '#' is not a comment character).
    898    - The terminating space, tab, newline, or EOF character is restored
    899      onto the input queue, so no line skip.
    900 
    901    Return: Retrieved string as char *, allocated by 'new'.
    902 */
    903 char *
    904 get_string_arg(void)
    905 {
    906   StringBuf buf = StringBuf();
    907   Char c = next_arg_begin();
    908   while (!is_space_or_tab(c)
    909 	 && c != Char('\n') && c != Char(EOF)) {
    910     buf.append(c);
    911     c = get_char();
    912   }
    913   unget_char(c);		// restore white space
    914   return buf.make_string();
    915 }
    916 
    917 //////////////////////////////////////////////////////////////////////
    918 /* is_space_or_tab():
    919    Test a character if it is a space or tab.
    920 
    921    c: In-parameter, character to be tested.
    922 
    923    Return: True, if c is a space or tab character, false otherwise.
    924 */
    925 inline bool
    926 is_space_or_tab(const Char c)
    927 {
    928   return (c == Char(' ') || c == Char('\t')) ? true : false;
    929 }
    930 
    931 //////////////////////////////////////////////////////////////////////
    932 /* next_arg_begin():
    933    Return first character of next argument.
    934 
    935    Skip space and tab characters; error on newline or EOF.
    936 
    937    Return: The first character different from these (including '#').
    938 */
    939 Char
    940 next_arg_begin(void)
    941 {
    942   Char c;
    943   while (1) {
    944     c = get_char();
    945     switch ((int) c) {
    946     case ' ':
    947     case '\t':
    948       break;
    949     case '\n':
    950     case EOF:
    951       error("missing argument");
    952       break;
    953     default:			// first essential character
    954       return c;
    955     }
    956   }
    957 }
    958 
    959 //////////////////////////////////////////////////////////////////////
    960 /* next_command():
    961    Find the first character of the next command.
    962 
    963    Skip spaces, tabs, comments (introduced by #), and newlines.
    964 
    965    Return: The first character different from these (including EOF).
    966 */
    967 Char
    968 next_command(void)
    969 {
    970   Char c;
    971   while (1) {
    972     c = get_char();
    973     switch ((int) c) {
    974     case ' ':
    975     case '\t':
    976       break;
    977     case '\n':
    978       current_lineno++;
    979       break;
    980     case '#':			// comment
    981       skip_line();
    982       break;
    983     default:			// EOF or first essential character
    984       return c;
    985     }
    986   }
    987 }
    988 
    989 //////////////////////////////////////////////////////////////////////
    990 /* odd():
    991    Test whether argument is an odd number.
    992 
    993    n: In-parameter, the integer to be tested.
    994 
    995    Return: True if odd, false otherwise.
    996 */
    997 inline bool
    998 odd(const int n)
    999 {
   1000   return (n & 1) ? true : false;
   1001 }
   1002 
   1003 //////////////////////////////////////////////////////////////////////
   1004 /* position_to_end_of_args():
   1005    Move graphical pointer to end of drawn figure.
   1006 
   1007    This is used by the D commands that draw open geometrical figures.
   1008    The algorithm simply sums up all horizontal displacements (arguments
   1009    with even number) for the horizontal component.  Similarly, the
   1010    vertical component is the sum of the odd arguments.
   1011 
   1012    args: In-parameter, the arguments of a former drawing command.
   1013 */
   1014 void
   1015 position_to_end_of_args(const IntArray * const args)
   1016 {
   1017   size_t i;
   1018   const size_t n = args->len();
   1019   for (i = 0; i < n; i += 2)
   1020     current_env->hpos += (*args)[i];
   1021   for (i = 1; i < n; i += 2)
   1022     current_env->vpos += (*args)[i];
   1023 }
   1024 
   1025 //////////////////////////////////////////////////////////////////////
   1026 /* remember_filename():
   1027    Set global variable current_filename.
   1028 
   1029    The actual filename is stored in current_filename.  This is used by
   1030    the postprocessors, expecting the name "<standard input>" for stdin.
   1031 
   1032    filename: In-out-parameter; is changed to the new value also.
   1033 */
   1034 void
   1035 remember_filename(const char *filename)
   1036 {
   1037   char *fname;
   1038   if (strcmp(filename, "-") == 0)
   1039     fname = (char *)"<standard input>";
   1040   else
   1041     fname = (char *)filename;
   1042   size_t len = strlen(fname) + 1;
   1043   if (current_filename != 0)
   1044     free((char *)current_filename);
   1045   current_filename = (const char *)malloc(len);
   1046   if (current_filename == 0)
   1047     fatal("can't malloc space for filename");
   1048   strncpy((char *)current_filename, (char *)fname, len);
   1049 }
   1050 
   1051 //////////////////////////////////////////////////////////////////////
   1052 /* remember_source_filename():
   1053    Set global variable current_source_filename.
   1054 
   1055    The actual filename is stored in current_filename.  This is used by
   1056    the postprocessors, expecting the name "<standard input>" for stdin.
   1057 
   1058    filename: In-out-parameter; is changed to the new value also.
   1059 */
   1060 void
   1061 remember_source_filename(const char *filename)
   1062 {
   1063   char *fname;
   1064   if (strcmp(filename, "-") == 0)
   1065     fname = (char *)"<standard input>";
   1066   else
   1067     fname = (char *)filename;
   1068   size_t len = strlen(fname) + 1;
   1069   if (current_source_filename != 0)
   1070     free((char *)current_source_filename);
   1071   current_source_filename = (const char *)malloc(len);
   1072   if (current_source_filename == 0)
   1073     fatal("can't malloc space for filename");
   1074   strncpy((char *)current_source_filename, (char *)fname, len);
   1075 }
   1076 
   1077 //////////////////////////////////////////////////////////////////////
   1078 /* send_draw():
   1079    Call draw method of printer class.
   1080 
   1081    subcmd: Letter of actual D subcommand.
   1082    args: Array of integer arguments of actual D subcommand.
   1083 */
   1084 void
   1085 send_draw(const Char subcmd, const IntArray * const args)
   1086 {
   1087   EnvInt n = (EnvInt) args->len();
   1088   pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
   1089 }
   1090 
   1091 //////////////////////////////////////////////////////////////////////
   1092 /* skip_line():
   1093    Go to next line within the input queue.
   1094 
   1095    Skip the rest of the current line, including the newline character.
   1096    The global variable current_lineno is adjusted.
   1097    No errors are raised.
   1098 */
   1099 void
   1100 skip_line(void)
   1101 {
   1102   Char c = get_char();
   1103   while (1) {
   1104     if (c == '\n') {
   1105       current_lineno++;
   1106       break;
   1107     }
   1108     if (c == EOF)
   1109       break;
   1110     c = get_char();
   1111   }
   1112 }
   1113 
   1114 //////////////////////////////////////////////////////////////////////
   1115 /* skip_line_checked ():
   1116    Check that there aren't any arguments left on the rest of the line,
   1117    then skip line.
   1118 
   1119    Spaces, tabs, and a comment are allowed before newline or EOF.
   1120    All other characters raise an error.
   1121 */
   1122 bool
   1123 skip_line_checked(void)
   1124 {
   1125   bool ok = true;
   1126   Char c = get_char();
   1127   while (is_space_or_tab(c))
   1128     c = get_char();
   1129   switch((int) c) {
   1130   case '#':			// comment
   1131     skip_line();
   1132     break;
   1133   case '\n':
   1134     current_lineno++;
   1135     break;
   1136   case EOF:
   1137     break;
   1138   default:
   1139     ok = false;
   1140     skip_line();
   1141     break;
   1142   }
   1143   return ok;
   1144 }
   1145 
   1146 //////////////////////////////////////////////////////////////////////
   1147 /* skip_line_fatal ():
   1148    Fatal error if arguments left, otherwise skip line.
   1149 
   1150    Spaces, tabs, and a comment are allowed before newline or EOF.
   1151    All other characters trigger the error.
   1152 */
   1153 void
   1154 skip_line_fatal(void)
   1155 {
   1156   bool ok = skip_line_checked();
   1157   if (!ok) {
   1158     current_lineno--;
   1159     error("too many arguments");
   1160     current_lineno++;
   1161   }
   1162 }
   1163 
   1164 //////////////////////////////////////////////////////////////////////
   1165 /* skip_line_warn ():
   1166    Skip line, but warn if arguments are left on actual line.
   1167 
   1168    Spaces, tabs, and a comment are allowed before newline or EOF.
   1169    All other characters raise a warning
   1170 */
   1171 void
   1172 skip_line_warn(void)
   1173 {
   1174   bool ok = skip_line_checked();
   1175   if (!ok) {
   1176     current_lineno--;
   1177     warning("too many arguments on current line");
   1178     current_lineno++;
   1179   }
   1180 }
   1181 
   1182 //////////////////////////////////////////////////////////////////////
   1183 /* skip_line_D ():
   1184    Skip line in `D' commands.
   1185 
   1186    Decide whether in case of an additional argument a fatal error is
   1187    raised (the documented classical behavior), only a warning is
   1188    issued, or the line is just skipped (former groff behavior).
   1189    Actually decided for the warning.
   1190 */
   1191 void
   1192 skip_line_D(void)
   1193 {
   1194   skip_line_warn();
   1195   // or: skip_line_fatal();
   1196   // or: skip_line();
   1197 }
   1198 
   1199 //////////////////////////////////////////////////////////////////////
   1200 /* skip_line_x ():
   1201    Skip line in `x' commands.
   1202 
   1203    Decide whether in case of an additional argument a fatal error is
   1204    raised (the documented classical behavior), only a warning is
   1205    issued, or the line is just skipped (former groff behavior).
   1206    Actually decided for the warning.
   1207 */
   1208 void
   1209 skip_line_x(void)
   1210 {
   1211   skip_line_warn();
   1212   // or: skip_line_fatal();
   1213   // or: skip_line();
   1214 }
   1215 
   1216 //////////////////////////////////////////////////////////////////////
   1217 /* skip_to_end_of_line():
   1218    Go to the end of the current line.
   1219 
   1220    Skip the rest of the current line, excluding the newline character.
   1221    The global variable current_lineno is not changed.
   1222    No errors are raised.
   1223 */
   1224 void
   1225 skip_to_end_of_line(void)
   1226 {
   1227   Char c = get_char();
   1228   while (1) {
   1229     if (c == '\n') {
   1230       unget_char(c);
   1231       return;
   1232     }
   1233     if (c == EOF)
   1234       return;
   1235     c = get_char();
   1236   }
   1237 }
   1238 
   1239 //////////////////////////////////////////////////////////////////////
   1240 /* unget_char(c):
   1241    Restore character c onto input queue.
   1242 
   1243    Write a character back onto the input stream.
   1244    EOF is gracefully handled.
   1245 
   1246    c: In-parameter; character to be pushed onto the input queue.
   1247 */
   1248 inline void
   1249 unget_char(const Char c)
   1250 {
   1251   if (c != EOF) {
   1252     int ch = (int) c;
   1253     if (ungetc(ch, current_file) == EOF)
   1254       fatal("could not unget character");
   1255   }
   1256 }
   1257 
   1258 
   1259 /**********************************************************************
   1260                        parser subcommands
   1261  **********************************************************************/
   1262 
   1263 //////////////////////////////////////////////////////////////////////
   1264 /* parse_color_command:
   1265    Process the commands m and DF, but not Df.
   1266 
   1267    col: In-out-parameter; the color object to be set, must have
   1268         been initialized before.
   1269 */
   1270 void
   1271 parse_color_command(color *col)
   1272 {
   1273   ColorArg gray = 0;
   1274   ColorArg red = 0, green = 0, blue = 0;
   1275   ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
   1276   Char subcmd = next_arg_begin();
   1277   switch((int) subcmd) {
   1278   case 'c':			// DFc or mc: CMY
   1279     cyan = get_color_arg();
   1280     magenta = get_color_arg();
   1281     yellow = get_color_arg();
   1282     col->set_cmy(cyan, magenta, yellow);
   1283     break;
   1284   case 'd':			// DFd or md: set default color
   1285     col->set_default();
   1286     break;
   1287   case 'g':			// DFg or mg: gray
   1288     gray = get_color_arg();
   1289     col->set_gray(gray);
   1290     break;
   1291   case 'k':			// DFk or mk: CMYK
   1292     cyan = get_color_arg();
   1293     magenta = get_color_arg();
   1294     yellow = get_color_arg();
   1295     black = get_color_arg();
   1296     col->set_cmyk(cyan, magenta, yellow, black);
   1297     break;
   1298   case 'r':			// DFr or mr: RGB
   1299     red = get_color_arg();
   1300     green = get_color_arg();
   1301     blue = get_color_arg();
   1302     col->set_rgb(red, green, blue);
   1303     break;
   1304   default:
   1305     error("invalid color scheme `%1'", (int) subcmd);
   1306     break;
   1307   } // end of color subcommands
   1308 }
   1309 
   1310 //////////////////////////////////////////////////////////////////////
   1311 /* parse_D_command():
   1312    Parse the subcommands of graphical command D.
   1313 
   1314    This is the part of the do_file() parser that scans the graphical
   1315    subcommands.
   1316    - Error on lacking or wrong arguments.
   1317    - Warning on too many arguments.
   1318    - Line is always skipped.
   1319 */
   1320 void
   1321 parse_D_command()
   1322 {
   1323   Char subcmd = next_arg_begin();
   1324   switch((int) subcmd) {
   1325   case '~':			// D~: draw B-spline
   1326     // actually, this isn't available for some postprocessors
   1327     // fall through
   1328   default:			// unknown options are passed to device
   1329     {
   1330       IntArray *args = get_D_variable_args();
   1331       send_draw(subcmd, args);
   1332       position_to_end_of_args(args);
   1333       delete args;
   1334       break;
   1335     }
   1336   case 'a':			// Da: draw arc
   1337     {
   1338       IntArray *args = get_D_fixed_args(4);
   1339       send_draw(subcmd, args);
   1340       position_to_end_of_args(args);
   1341       delete args;
   1342       break;
   1343     }
   1344   case 'c':			// Dc: draw circle line
   1345     {
   1346       IntArray *args = get_D_fixed_args(1);
   1347       send_draw(subcmd, args);
   1348       // move to right end
   1349       current_env->hpos += (*args)[0];
   1350       delete args;
   1351       break;
   1352     }
   1353   case 'C':			// DC: draw solid circle
   1354     {
   1355       IntArray *args = get_D_fixed_args_odd_dummy(1);
   1356       send_draw(subcmd, args);
   1357       // move to right end
   1358       current_env->hpos += (*args)[0];
   1359       delete args;
   1360       break;
   1361     }
   1362   case 'e':			// De: draw ellipse line
   1363   case 'E':			// DE: draw solid ellipse
   1364     {
   1365       IntArray *args = get_D_fixed_args(2);
   1366       send_draw(subcmd, args);
   1367       // move to right end
   1368       current_env->hpos += (*args)[0];
   1369       delete args;
   1370       break;
   1371     }
   1372   case 'f':			// Df: set fill gray; obsoleted by DFg
   1373     {
   1374       IntArg arg = get_integer_arg();
   1375       if ((arg >= 0) && (arg <= 1000)) {
   1376 	// convert arg and treat it like DFg
   1377 	ColorArg gray = color_from_Df_command(arg);
   1378         current_env->fill->set_gray(gray);
   1379       }
   1380       else {
   1381 	// set fill color to the same value as the current outline color
   1382 	delete current_env->fill;
   1383 	current_env->fill = new color(current_env->col);
   1384       }
   1385       pr->change_fill_color(current_env);
   1386       // skip unused `vertical' component (\D'...' always emits pairs)
   1387       (void) get_integer_arg();
   1388 #   ifdef STUPID_DRAWING_POSITIONING
   1389       current_env->hpos += arg;
   1390 #   endif
   1391       skip_line_x();
   1392       break;
   1393     }
   1394   case 'F':			// DF: set fill color, several formats
   1395     parse_color_command(current_env->fill);
   1396     pr->change_fill_color(current_env);
   1397     // no positioning (setting-only command)
   1398     skip_line_x();
   1399     break;
   1400   case 'l':			// Dl: draw line
   1401     {
   1402       IntArray *args = get_D_fixed_args(2);
   1403       send_draw(subcmd, args);
   1404       position_to_end_of_args(args);
   1405       delete args;
   1406       break;
   1407     }
   1408   case 'p':			// Dp: draw closed polygon line
   1409   case 'P':			// DP: draw solid closed polygon
   1410     {
   1411       IntArray *args = get_D_variable_args();
   1412       send_draw(subcmd, args);
   1413 #   ifdef STUPID_DRAWING_POSITIONING
   1414       // final args positioning
   1415       position_to_end_of_args(args);
   1416 #   endif
   1417       delete args;
   1418       break;
   1419     }
   1420   case 't':			// Dt: set line thickness
   1421     {
   1422       IntArray *args = get_D_fixed_args_odd_dummy(1);
   1423       send_draw(subcmd, args);
   1424 #   ifdef STUPID_DRAWING_POSITIONING
   1425       // final args positioning
   1426       position_to_end_of_args(args);
   1427 #   endif
   1428       delete args;
   1429       break;
   1430     }
   1431   } // end of D subcommands
   1432 }
   1433 
   1434 //////////////////////////////////////////////////////////////////////
   1435 /* parse_x_command():
   1436    Parse subcommands of the device control command x.
   1437 
   1438    This is the part of the do_file() parser that scans the device
   1439    controlling commands.
   1440    - Error on duplicate prologue commands.
   1441    - Error on wrong or lacking arguments.
   1442    - Warning on too many arguments.
   1443    - Line is always skipped.
   1444 
   1445    Globals:
   1446    - current_env: is set by many subcommands.
   1447    - npages: page counting variable
   1448 
   1449    Return: boolean in the meaning of `stopped'
   1450            - true if parsing should be stopped (`x stop').
   1451            - false if parsing should continue.
   1452 */
   1453 bool
   1454 parse_x_command(void)
   1455 {
   1456   bool stopped = false;
   1457   char *subcmd_str = get_string_arg();
   1458   char subcmd = subcmd_str[0];
   1459   switch (subcmd) {
   1460   case 'f':			// x font: mount font
   1461     {
   1462       IntArg n = get_integer_arg();
   1463       char *name = get_string_arg();
   1464       pr->load_font(n, name);
   1465       a_delete name;
   1466       skip_line_x();
   1467       break;
   1468     }
   1469   case 'F':			// x Filename: set filename for errors
   1470     {
   1471       char *str_arg = get_string_arg();
   1472       if (str_arg == 0)
   1473 	warning("empty argument for `x F' command");
   1474       else {
   1475 	remember_source_filename(str_arg);
   1476 	a_delete str_arg;
   1477       }
   1478       break;
   1479     }
   1480   case 'H':			// x Height: set character height
   1481     current_env->height = get_integer_arg();
   1482     if (current_env->height == current_env->size)
   1483       current_env->height = 0;
   1484     skip_line_x();
   1485     break;
   1486   case 'i':			// x init: initialize device
   1487     error("duplicate `x init' command");
   1488     skip_line_x();
   1489     break;
   1490   case 'p':			// x pause: pause device
   1491     skip_line_x();
   1492     break;
   1493   case 'r':			// x res: set resolution
   1494     error("duplicate `x res' command");
   1495     skip_line_x();
   1496     break;
   1497   case 's':			// x stop: stop device
   1498     stopped = true;
   1499     skip_line_x();
   1500     break;
   1501   case 'S':			// x Slant: set slant
   1502     current_env->slant = get_integer_arg();
   1503     skip_line_x();
   1504     break;
   1505   case 't':			// x trailer: generate trailer info
   1506     skip_line_x();
   1507     break;
   1508   case 'T':			// x Typesetter: set typesetter
   1509     error("duplicate `x T' command");
   1510     skip_line();
   1511     break;
   1512   case 'u':			// x underline: from .cu
   1513     {
   1514       char *str_arg = get_string_arg();
   1515       pr->special(str_arg, current_env, 'u');
   1516       a_delete str_arg;
   1517       skip_line_x();
   1518       break;
   1519     }
   1520   case 'X':			// x X: send uninterpretedly to device
   1521     {
   1522       char *str_arg = get_extended_arg(); // includes line skip
   1523       if (npages <= 0)
   1524 	error("`x X' command invalid before first `p' command");
   1525       else if (str_arg && (strncmp(str_arg, "devtag:",
   1526 				   strlen("devtag:")) == 0))
   1527 	pr->devtag(str_arg, current_env);
   1528       else
   1529 	pr->special(str_arg, current_env);
   1530       a_delete str_arg;
   1531       break;
   1532     }
   1533   default:			// ignore unknown x commands, but warn
   1534     warning("unknown command `x %1'", subcmd);
   1535     skip_line();
   1536   }
   1537   a_delete subcmd_str;
   1538   return stopped;
   1539 }
   1540 
   1541 
   1542 /**********************************************************************
   1543                      exported part (by driver.h)
   1544  **********************************************************************/
   1545 
   1546 ////////////////////////////////////////////////////////////////////////
   1547 /* do_file():
   1548    Parse and postprocess groff intermediate output.
   1549 
   1550    filename: "-" for standard input, normal file name otherwise
   1551 */
   1552 void
   1553 do_file(const char *filename)
   1554 {
   1555   Char command;
   1556   bool stopped = false;		// terminating condition
   1557 
   1558 #ifdef USE_ENV_STACK
   1559   EnvStack env_stack = EnvStack();
   1560 #endif // USE_ENV_STACK
   1561 
   1562   // setup of global variables
   1563   npages = 0;
   1564   current_lineno = 1;
   1565   // `pr' is initialized after the prologue.
   1566   // `device' is set by the 1st prologue command.
   1567 
   1568   if (filename[0] == '-' && filename[1] == '\0')
   1569     current_file = stdin;
   1570   else {
   1571     errno = 0;
   1572     current_file = fopen(filename, "r");
   1573     if (errno != 0 || current_file == 0) {
   1574       error("can't open file `%1'", filename);
   1575       return;
   1576     }
   1577   }
   1578   remember_filename(filename);
   1579 
   1580   if (current_env != 0)
   1581     delete_current_env();
   1582   current_env = new environment;
   1583   current_env->col = new color;
   1584   current_env->fill = new color;
   1585   current_env->fontno = -1;
   1586   current_env->height = 0;
   1587   current_env->hpos = -1;
   1588   current_env->slant = 0;
   1589   current_env->size = 0;
   1590   current_env->vpos = -1;
   1591 
   1592   // parsing of prologue (first 3 commands)
   1593   {
   1594     char *str_arg;
   1595     IntArg int_arg;
   1596 
   1597     // 1st command `x T'
   1598     command = next_command();
   1599     if ((int) command == EOF)
   1600       return;
   1601     if ((int) command != 'x')
   1602       fatal("the first command must be `x T'");
   1603     str_arg = get_string_arg();
   1604     if (str_arg[0] != 'T')
   1605       fatal("the first command must be `x T'");
   1606     a_delete str_arg;
   1607     char *tmp_dev = get_string_arg();
   1608     if (pr == 0) {		// note: `pr' initialized after prologue
   1609       device = tmp_dev;
   1610       if (!font::load_desc())
   1611 	fatal("couldn't load DESC file, can't continue");
   1612     }
   1613     else {
   1614       if (device == 0 || strcmp(device, tmp_dev) != 0)
   1615 	fatal("all files must use the same device");
   1616       a_delete tmp_dev;
   1617     }
   1618     skip_line_x();		// ignore further arguments
   1619     current_env->size = 10 * font::sizescale;
   1620 
   1621     // 2nd command `x res'
   1622     command = next_command();
   1623     if ((int) command != 'x')
   1624       fatal("the second command must be `x res'");
   1625     str_arg = get_string_arg();
   1626     if (str_arg[0] != 'r')
   1627       fatal("the second command must be `x res'");
   1628     a_delete str_arg;
   1629     int_arg = get_integer_arg();
   1630     EnvInt font_res = font::res;
   1631     if (int_arg != font_res)
   1632       fatal("resolution does not match");
   1633     int_arg = get_integer_arg();
   1634     if (int_arg != font::hor)
   1635       fatal("minimum horizontal motion does not match");
   1636     int_arg = get_integer_arg();
   1637     if (int_arg != font::vert)
   1638       fatal("minimum vertical motion does not match");
   1639     skip_line_x();		// ignore further arguments
   1640 
   1641     // 3rd command `x init'
   1642     command = next_command();
   1643     if (command != 'x')
   1644       fatal("the third command must be `x init'");
   1645     str_arg = get_string_arg();
   1646     if (str_arg[0] != 'i')
   1647       fatal("the third command must be `x init'");
   1648     a_delete str_arg;
   1649     skip_line_x();
   1650   }
   1651 
   1652   // parsing of body
   1653   if (pr == 0)
   1654     pr = make_printer();
   1655   while (!stopped) {
   1656     command = next_command();
   1657     if (command == EOF)
   1658       break;
   1659     // spaces, tabs, comments, and newlines are skipped here
   1660     switch ((int) command) {
   1661     case '#':			// #: comment, ignore up to end of line
   1662       skip_line();
   1663       break;
   1664 #ifdef USE_ENV_STACK
   1665     case '{':			// {: start a new environment (a copy)
   1666       env_stack.push(current_env);
   1667       break;
   1668     case '}':			// }: pop previous env from stack
   1669       delete_current_env();
   1670       current_env = env_stack.pop();
   1671       break;
   1672 #endif // USE_ENV_STACK
   1673     case '0':			// ddc: obsolete jump and print command
   1674     case '1':
   1675     case '2':
   1676     case '3':
   1677     case '4':
   1678     case '5':
   1679     case '6':
   1680     case '7':
   1681     case '8':
   1682     case '9':
   1683       {				// expect 2 digits and a character
   1684 	char s[3];
   1685 	Char c = next_arg_begin();
   1686 	if (npages <= 0)
   1687 	  fatal_command(command);
   1688 	if (!isdigit((int) c)) {
   1689 	  error("digit expected");
   1690 	  c = 0;
   1691 	}
   1692 	s[0] = (char) command;
   1693 	s[1] = (char) c;
   1694 	s[2] = '\0';
   1695 	errno = 0;
   1696 	long int x = strtol(s, 0, 10);
   1697 	if (errno != 0)
   1698 	  error("couldn't convert 2 digits");
   1699 	EnvInt hor_pos = (EnvInt) x;
   1700 	current_env->hpos += hor_pos;
   1701 	c = next_arg_begin();
   1702 	if ((int) c == '\n' || (int) c == EOF)
   1703 	  error("character argument expected");
   1704 	else
   1705 	  pr->set_ascii_char((unsigned char) c, current_env);
   1706 	break;
   1707       }
   1708     case 'c':			// c: print ascii char without moving
   1709       {
   1710 	if (npages <= 0)
   1711 	  fatal_command(command);
   1712 	Char c = next_arg_begin();
   1713 	if (c == '\n' || c == EOF)
   1714 	  error("missing argument to `c' command");
   1715 	else
   1716 	  pr->set_ascii_char((unsigned char) c, current_env);
   1717 	break;
   1718       }
   1719     case 'C':			// C: print named special character
   1720       {
   1721 	if (npages <= 0)
   1722 	  fatal_command(command);
   1723 	char *str_arg = get_string_arg();
   1724 	pr->set_special_char(str_arg, current_env);
   1725 	a_delete str_arg;
   1726 	break;
   1727       }
   1728     case 'D':			// drawing commands
   1729       if (npages <= 0)
   1730 	fatal_command(command);
   1731       parse_D_command();
   1732       break;
   1733     case 'f':			// f: set font to number
   1734       current_env->fontno = get_integer_arg();
   1735       break;
   1736     case 'F':			// F: obsolete, replaced by `x F'
   1737       {
   1738 	char *str_arg = get_string_arg();
   1739 	remember_source_filename(str_arg);
   1740 	a_delete str_arg;
   1741 	break;
   1742       }
   1743     case 'h':			// h: relative horizontal move
   1744       current_env->hpos += (EnvInt) get_integer_arg();
   1745       break;
   1746     case 'H':			// H: absolute horizontal positioning
   1747       current_env->hpos = (EnvInt) get_integer_arg();
   1748       break;
   1749     case 'm':			// m: glyph color
   1750       parse_color_command(current_env->col);
   1751       pr->change_color(current_env);
   1752       break;
   1753     case 'n':			// n: print end of line
   1754 				// ignore two arguments (historically)
   1755       if (npages <= 0)
   1756 	fatal_command(command);
   1757       pr->end_of_line();
   1758       (void) get_integer_arg();
   1759       (void) get_integer_arg();
   1760       break;
   1761     case 'N':			// N: print char with given int code
   1762       if (npages <= 0)
   1763 	fatal_command(command);
   1764       pr->set_numbered_char(get_integer_arg(), current_env);
   1765       break;
   1766     case 'p':			// p: start new page with given number
   1767       if (npages > 0)
   1768 	pr->end_page(current_env->vpos);
   1769       npages++;			// increment # of processed pages
   1770       pr->begin_page(get_integer_arg());
   1771       current_env->vpos = 0;
   1772       break;
   1773     case 's':			// s: set point size
   1774       current_env->size = get_integer_arg();
   1775       if (current_env->height == current_env->size)
   1776 	current_env->height = 0;
   1777       break;
   1778     case 't':			// t: print a text word
   1779       {
   1780 	char c;
   1781 	if (npages <= 0)
   1782 	  fatal_command(command);
   1783 	char *str_arg = get_string_arg();
   1784 	size_t i = 0;
   1785 	while ((c = str_arg[i++]) != '\0') {
   1786 	  EnvInt w;
   1787 	  pr->set_ascii_char((unsigned char) c, current_env, &w);
   1788 	  current_env->hpos += w;
   1789 	}
   1790 	a_delete str_arg;
   1791 	break;
   1792       }
   1793     case 'u':			// u: print spaced word
   1794       {
   1795 	char c;
   1796 	if (npages <= 0)
   1797 	  fatal_command(command);
   1798 	EnvInt kern = (EnvInt) get_integer_arg();
   1799 	char *str_arg = get_string_arg();
   1800 	size_t i = 0;
   1801 	while ((c = str_arg[i++]) != '\0') {
   1802 	  EnvInt w;
   1803 	  pr->set_ascii_char((unsigned char) c, current_env, &w);
   1804 	  current_env->hpos += w + kern;
   1805 	}
   1806 	a_delete str_arg;
   1807 	break;
   1808       }
   1809     case 'v':			// v: relative vertical move
   1810       current_env->vpos += (EnvInt) get_integer_arg();
   1811       break;
   1812     case 'V':			// V: absolute vertical positioning
   1813       current_env->vpos = (EnvInt) get_integer_arg();
   1814       break;
   1815     case 'w':			// w: inform about paddable space
   1816       break;
   1817     case 'x':			// device controlling commands
   1818       stopped = parse_x_command();
   1819       break;
   1820     default:
   1821       warning("unrecognized command `%1'", (unsigned char) command);
   1822       skip_line();
   1823       break;
   1824     } // end of switch
   1825   } // end of while
   1826 
   1827   // end of file reached
   1828   if (npages > 0)
   1829     pr->end_page(current_env->vpos);
   1830   delete pr;
   1831   pr = 0;
   1832   fclose(current_file);
   1833   // If `stopped' is not `true' here then there wasn't any `x stop'.
   1834   if (!stopped)
   1835     warning("no final `x stop' command");
   1836   delete_current_env();
   1837 }
   1838