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