xref: /netbsd-src/external/gpl2/groff/dist/src/libs/libdriver/input.cpp (revision 4acc5b6b2013c23d840d952be7c84bc64d81149a)
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@jclark.com)
11    Major rewrite 2001 by Bernd Warken (bwarken@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);
operator [](const size_t i) const277   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);
get_data(void) const284   IntArg *get_data(void) const { return (IntArg *)data; }
len(void) const285   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:
Char(void)292   Char(void) : data('\0') {}
Char(const int c)293   Char(const int c) : data(c) {}
operator ==(char c) const294   bool operator==(char c) const { return (data == c) ? true : false; }
operator ==(int c) const295   bool operator==(int c) const { return (data == c) ? true : false; }
operator ==(const Char c) const296   bool operator==(const Char c) const
297 		  { return (data == c.data) ? true : false; }
operator !=(char c) const298   bool operator!=(char c) const { return !(*this == c); }
operator !=(int c) const299   bool operator!=(int c) const { return !(*this == c); }
operator !=(const Char c) const300   bool operator!=(const Char c) const { return !(*this == c); }
operator int() const301   operator int() const { return (int) data; }
operator unsigned char() const302   operator unsigned char() const { return (unsigned char) data; }
operator char() const303   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'
is_empty(void)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
EnvStack(void)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 
~EnvStack(void)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 *
pop(void)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
push(environment * e)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 
IntArray(void)501 IntArray::IntArray(void)
502 {
503   num_allocated = 4;
504   data = new IntArg[num_allocated];
505   num_stored = 0;
506 }
507 
IntArray(const size_t n)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 
~IntArray(void)517 IntArray::~IntArray(void)
518 {
519   a_delete data;
520 }
521 
522 void
append(IntArg x)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 
StringBuf(void)537 StringBuf::StringBuf(void)
538 {
539   num_stored = 0;
540   num_allocated = 128;
541   data = new Char[num_allocated];
542 }
543 
~StringBuf(void)544 StringBuf::~StringBuf(void)
545 {
546   a_delete data;
547 }
548 
549 void
append(const Char c)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 *
make_string(void)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
reset(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
color_from_Df_command(IntArg Df_gray)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 */
delete_current_env(void)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
fatal_command(char command)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
get_char(void)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
get_color_arg(void)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 *
get_D_fixed_args(const size_t number)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 *
get_D_fixed_args_odd_dummy(const size_t number)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 *
get_D_variable_args()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 *
get_extended_arg(void)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
get_integer_arg(void)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 *
get_possibly_integer_args()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 *
get_string_arg(void)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
is_space_or_tab(const Char c)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
next_arg_begin(void)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
next_command(void)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
odd(const int n)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
position_to_end_of_args(const IntArray * const args)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
remember_filename(const char * filename)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
remember_source_filename(const char * filename)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
send_draw(const Char subcmd,const IntArray * const args)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
skip_line(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
skip_line_checked(void)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
skip_line_fatal(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
skip_line_warn(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
skip_line_D(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
skip_line_x(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
skip_to_end_of_line(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
unget_char(const Char c)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
parse_color_command(color * col)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
parse_D_command()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
parse_x_command(void)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
do_file(const char * filename)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