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