Tue Dec 30 22:42:57 2003 Ben Pfaff <blp@gnu.org>
[pspp-builds.git] / src / print.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include <assert.h>
22 #include <stdlib.h>
23 #include "alloc.h"
24 #include "command.h"
25 #include "dfm.h"
26 #include "error.h"
27 #include "expr.h"
28 #include "file-handle.h"
29 #include "lexer.h"
30 #include "misc.h"
31 #include "som.h"
32 #include "tab.h"
33 #include "var.h"
34
35 #include "debug-print.h"
36
37 /* Describes what to do when an output field is encountered. */
38 enum
39   {
40     PRT_ERROR,                  /* Invalid value. */
41     PRT_NEWLINE,                /* Newline. */
42     PRT_CONST,                  /* Constant string. */
43     PRT_VAR,                    /* Variable. */
44     PRT_SPACE                   /* A single space. */
45   };
46
47 /* Describes how to output one field. */
48 struct prt_out_spec
49   {
50     struct prt_out_spec *next;
51     int type;                   /* PRT_* constant. */
52     int fc;                     /* 0-based first column. */
53     union
54       {
55         char *c;                /* PRT_CONST: Associated string. */
56         struct
57           {
58             struct variable *v; /* PRT_VAR: Associated variable. */
59             struct fmt_spec f;  /* PRT_VAR: Output spec. */
60           }
61         v;
62       }
63     u;
64   };
65
66 /* Enums for use with print_trns's `options' field. */
67 enum
68   {
69     PRT_CMD_MASK = 1,           /* Command type mask. */
70     PRT_PRINT = 0,              /* PRINT transformation identifier. */
71     PRT_WRITE = 1,              /* WRITE transformation identifier. */
72     PRT_EJECT = 002             /* Can be combined with CMD_PRINT only. */
73   };
74
75 /* PRINT, PRINT EJECT, WRITE private data structure. */
76 struct print_trns
77   {
78     struct trns_header h;
79     struct file_handle *handle; /* Output file, NULL=listing file. */
80     int options;                /* PRT_* bitmapped field. */
81     struct prt_out_spec *spec;  /* Output specifications. */
82     int max_width;              /* Maximum line width including null. */
83 #if !PAGED_STACK
84     char *line;                 /* Buffer for sticking lines in. */
85 #endif
86   };
87
88 /* PRT_PRINT or PRT_WRITE. */
89 int which_cmd;
90
91 /* Holds information on parsing the data file. */
92 static struct print_trns prt;
93
94 /* Last prt_out_spec in the chain.  Used for building the linked-list. */
95 static struct prt_out_spec *next;
96
97 /* Number of records. */
98 static int nrec;
99
100 static int internal_cmd_print (int flags);
101 static int print_trns_proc (struct trns_header *, struct ccase *);
102 static void print_trns_free (struct trns_header *);
103 static int parse_specs (void);
104 static void dump_table (void);
105 static void append_var_spec (struct prt_out_spec *spec);
106 static void alloc_line (void);
107
108 #if DEBUGGING
109 void debug_print (void);
110 #endif
111 \f
112 /* Basic parsing. */
113
114 /* Parses PRINT command. */
115 int
116 cmd_print (void)
117 {
118   lex_match_id ("PRINT");
119   return internal_cmd_print (PRT_PRINT);
120 }
121
122 /* Parses PRINT EJECT command. */
123 int
124 cmd_print_eject (void)
125 {
126   lex_match_id ("EJECT");
127   return internal_cmd_print (PRT_PRINT | PRT_EJECT);
128 }
129
130 /* Parses WRITE command. */
131 int
132 cmd_write (void)
133 {
134   lex_match_id ("WRITE");
135   return internal_cmd_print (PRT_WRITE);
136 }
137
138 /* Parses the output commands.  F is PRT_PRINT, PRT_WRITE, or
139    PRT_PRINT|PRT_EJECT. */
140 static int
141 internal_cmd_print (int f)
142 {
143   /* 0=print no table, 1=print table.  (TABLE subcommand.)  */
144   int table = 0;
145
146   /* malloc()'d transformation. */
147   struct print_trns *trns;
148
149   /* Fill in prt to facilitate error-handling. */
150   prt.h.proc = print_trns_proc;
151   prt.h.free = print_trns_free;
152   prt.handle = NULL;
153   prt.options = f;
154   prt.spec = NULL;
155 #if !PAGED_STACK
156   prt.line = NULL;
157 #endif
158   next = NULL;
159   nrec = 0;
160
161   which_cmd = f & PRT_CMD_MASK;
162
163   /* Parse the command options. */
164   while (!lex_match ('/'))
165     {
166       if (lex_match_id ("OUTFILE"))
167         {
168           lex_match ('=');
169
170           prt.handle = fh_parse_file_handle ();
171           if (!prt.handle)
172             goto lossage;
173         }
174       else if (lex_match_id ("RECORDS"))
175         {
176           lex_match ('=');
177           lex_match ('(');
178           if (!lex_force_int ())
179             goto lossage;
180           nrec = lex_integer ();
181           lex_get ();
182           lex_match (')');
183         }
184       else if (lex_match_id ("TABLE"))
185         table = 1;
186       else if (lex_match_id ("NOTABLE"))
187         table = 0;
188       else
189         {
190           lex_error (_("expecting a valid subcommand"));
191           goto lossage;
192         }
193     }
194
195   /* Parse variables and strings. */
196   if (!parse_specs ())
197     goto lossage;
198   
199   /* Output the variable table if requested. */
200   if (table)
201     dump_table ();
202
203   /* Count the maximum line width.  Allocate linebuffer if
204      applicable. */
205   alloc_line ();
206
207   /* Put the transformation in the queue. */
208   trns = xmalloc (sizeof *trns);
209   memcpy (trns, &prt, sizeof *trns);
210   add_transformation ((struct trns_header *) trns);
211
212 #if 0 && DEBUGGING
213   debug_print ();
214 #endif
215
216   return CMD_SUCCESS;
217
218  lossage:
219   print_trns_free ((struct trns_header *) & prt);
220   return CMD_FAILURE;
221 }
222
223 /* Appends the field output specification SPEC to the list maintained
224    in prt. */
225 static void
226 append_var_spec (struct prt_out_spec *spec)
227 {
228   if (next == 0)
229     prt.spec = next = xmalloc (sizeof *spec);
230   else
231     next = next->next = xmalloc (sizeof *spec);
232
233   memcpy (next, spec, sizeof *spec);
234   next->next = NULL;
235 }
236 \f
237 /* Field parsing.  Mostly stolen from data-list.c. */
238
239 /* Used for chaining together fortran-like format specifiers. */
240 struct fmt_list
241 {
242   struct fmt_list *next;
243   int count;
244   struct fmt_spec f;
245   struct fmt_list *down;
246 };
247
248 /* Used as "local" variables among the fixed-format parsing funcs.  If
249    it were guaranteed that PSPP were going to be compiled by gcc,
250    I'd make all these functions a single set of nested functions. */
251 static struct
252   {
253     struct variable **v;                /* variable list */
254     int nv;                     /* number of variables in list */
255     int cv;                     /* number of variables from list used up so far
256                                    by the FORTRAN-like format specifiers */
257
258     int recno;                  /* current 1-based record number */
259     int sc;                     /* 1-based starting column for next variable */
260
261     struct prt_out_spec spec;           /* next format spec to append to list */
262     int fc, lc;                 /* first, last 1-based column number of current
263                                    var */
264
265     int level;                  /* recursion level for FORTRAN-like format
266                                    specifiers */
267   }
268 fx;
269
270 static int fixed_parse_compatible (void);
271 static struct fmt_list *fixed_parse_fortran (void);
272
273 static int parse_string_argument (void);
274 static int parse_variable_argument (void);
275
276 /* Parses all the variable and string specifications on a single
277    PRINT, PRINT EJECT, or WRITE command into the prt structure.
278    Returns success. */
279 static int
280 parse_specs (void)
281 {
282   /* Return code from called function. */
283   int code;
284
285   fx.recno = 1;
286   fx.sc = 1;
287
288   while (token != '.')
289     {
290       while (lex_match ('/'))
291         {
292           int prev_recno = fx.recno;
293
294           fx.recno++;
295           if (token == T_NUM)
296             {
297               if (!lex_force_int ())
298                 return 0;
299               if (lex_integer () < fx.recno)
300                 {
301                   msg (SE, _("The record number specified, %ld, is "
302                              "before the previous record, %d.  Data "
303                              "fields must be listed in order of "
304                              "increasing record number."),
305                        lex_integer (), fx.recno - 1);
306                   return 0;
307                 }
308               fx.recno = lex_integer ();
309               lex_get ();
310             }
311
312           fx.spec.type = PRT_NEWLINE;
313           while (prev_recno++ < fx.recno)
314             append_var_spec (&fx.spec);
315
316           fx.sc = 1;
317         }
318
319       if (token == T_STRING)
320         code = parse_string_argument ();
321       else
322         code = parse_variable_argument ();
323       if (!code)
324         return 0;
325     }
326   fx.spec.type = PRT_NEWLINE;
327   append_var_spec (&fx.spec);
328
329   if (!nrec)
330     nrec = fx.recno;
331   else if (fx.recno > nrec)
332     {
333       msg (SE, _("Variables are specified on records that "
334                  "should not exist according to RECORDS subcommand."));
335       return 0;
336     }
337       
338   if (token != '.')
339     {
340       lex_error (_("expecting end of command"));
341       return 0;
342     }
343   
344   return 1;
345 }
346
347 /* Parses a string argument to the PRINT commands.  Returns success. */
348 static int
349 parse_string_argument (void)
350 {
351   fx.spec.type = PRT_CONST;
352   fx.spec.fc = fx.sc - 1;
353   fx.spec.u.c = xstrdup (ds_value (&tokstr));
354   lex_get ();
355
356   /* Parse the included column range. */
357   if (token == T_NUM)
358     {
359       /* Width of column range in characters. */
360       int c_len;
361
362       /* Width of constant string in characters. */
363       int s_len;
364
365       /* 1-based index of last column in range. */
366       int lc;
367
368       if (!lex_integer_p () || lex_integer () <= 0)
369         {
370           msg (SE, _("%g is not a valid column location."), tokval);
371           goto fail;
372         }
373       fx.spec.fc = lex_integer () - 1;
374
375       lex_get ();
376       lex_negative_to_dash ();
377       if (lex_match ('-'))
378         {
379           if (!lex_integer_p ())
380             {
381               msg (SE, _("Column location expected following `%d-'."),
382                    fx.spec.fc + 1);
383               goto fail;
384             }
385           if (lex_integer () <= 0)
386             {
387               msg (SE, _("%g is not a valid column location."), tokval);
388               goto fail;
389             }
390           if (lex_integer () < fx.spec.fc + 1)
391             {
392               msg (SE, _("%d-%ld is not a valid column range.  The second "
393                    "column must be greater than or equal to the first."),
394                    fx.spec.fc + 1, lex_integer ());
395               goto fail;
396             }
397           lc = lex_integer () - 1;
398
399           lex_get ();
400         }
401       else
402         /* If only a starting location is specified then the field is
403            the width of the provided string. */
404         lc = fx.spec.fc + strlen (fx.spec.u.c) - 1;
405
406       /* Apply the range. */
407       c_len = lc - fx.spec.fc + 1;
408       s_len = strlen (fx.spec.u.c);
409       if (s_len > c_len)
410         fx.spec.u.c[c_len] = 0;
411       else if (s_len < c_len)
412         {
413           fx.spec.u.c = xrealloc (fx.spec.u.c, c_len + 1);
414           memset (&fx.spec.u.c[s_len], ' ', c_len - s_len);
415           fx.spec.u.c[c_len] = 0;
416         }
417
418       fx.sc = lc + 1;
419     }
420   else
421     /* If nothing is provided then the field is the width of the
422        provided string. */
423     fx.sc += strlen (fx.spec.u.c);
424
425   append_var_spec (&fx.spec);
426   return 1;
427
428 fail:
429   free (fx.spec.u.c);
430   return 0;
431 }
432
433 /* Parses a variable argument to the PRINT commands by passing it off
434    to fixed_parse_compatible() or fixed_parse_fortran() as appropriate.
435    Returns success. */
436 static int
437 parse_variable_argument (void)
438 {
439   if (!parse_variables (default_dict, &fx.v, &fx.nv, PV_DUPLICATE))
440     return 0;
441
442   if (token == T_NUM)
443     {
444       if (!fixed_parse_compatible ())
445         goto fail;
446     }
447   else if (token == '(')
448     {
449       fx.level = 0;
450       fx.cv = 0;
451       if (!fixed_parse_fortran ())
452         goto fail;
453     }
454   else
455     {
456       /* User wants dictionary format specifiers. */
457       int i;
458
459       lex_match ('*');
460       for (i = 0; i < fx.nv; i++)
461         {
462           /* Variable. */
463           fx.spec.type = PRT_VAR;
464           fx.spec.fc = fx.sc - 1;
465           fx.spec.u.v.v = fx.v[i];
466           fx.spec.u.v.f = fx.v[i]->print;
467           append_var_spec (&fx.spec);
468           fx.sc += fx.v[i]->print.w;
469
470           /* Space. */
471           fx.spec.type = PRT_SPACE;
472           fx.spec.fc = fx.sc - 1;
473           append_var_spec (&fx.spec);
474           fx.sc++;
475         }
476     }
477
478   free (fx.v);
479   return 1;
480
481 fail:
482   free (fx.v);
483   return 0;
484 }
485
486 /* Parses a column specification for parse_specs(). */
487 static int
488 fixed_parse_compatible (void)
489 {
490   int dividend;
491   int type;
492   int i;
493
494   type = fx.v[0]->type;
495   for (i = 1; i < fx.nv; i++)
496     if (type != fx.v[i]->type)
497       {
498         msg (SE, _("%s is not of the same type as %s.  To specify "
499                    "variables of different types in the same variable "
500                    "list, use a FORTRAN-like format specifier."),
501              fx.v[i]->name, fx.v[0]->name);
502         return 0;
503       }
504
505   if (!lex_force_int ())
506     return 0;
507   fx.fc = lex_integer () - 1;
508   if (fx.fc < 0)
509     {
510       msg (SE, _("Column positions for fields must be positive."));
511       return 0;
512     }
513   lex_get ();
514
515   lex_negative_to_dash ();
516   if (lex_match ('-'))
517     {
518       if (!lex_force_int ())
519         return 0;
520       fx.lc = lex_integer () - 1;
521       if (fx.lc < 0)
522         {
523           msg (SE, _("Column positions for fields must be positive."));
524           return 0;
525         }
526       else if (fx.lc < fx.fc)
527         {
528           msg (SE, _("The ending column for a field must not "
529                      "be less than the starting column."));
530           return 0;
531         }
532       lex_get ();
533     }
534   else
535     fx.lc = fx.fc;
536
537   fx.spec.u.v.f.w = fx.lc - fx.fc + 1;
538   if (lex_match ('('))
539     {
540       struct fmt_desc *fdp;
541
542       if (token == T_ID)
543         {
544           const char *cp;
545
546           fx.spec.u.v.f.type = parse_format_specifier_name (&cp, 0);
547           if (fx.spec.u.v.f.type == -1)
548             return 0;
549           if (*cp)
550             {
551               msg (SE, _("A format specifier on this line "
552                          "has extra characters on the end."));
553               return 0;
554             }
555           lex_get ();
556           lex_match (',');
557         }
558       else
559         fx.spec.u.v.f.type = FMT_F;
560
561       if (token == T_NUM)
562         {
563           if (!lex_force_int ())
564             return 0;
565           if (lex_integer () < 1)
566             {
567               msg (SE, _("The value for number of decimal places "
568                          "must be at least 1."));
569               return 0;
570             }
571           fx.spec.u.v.f.d = lex_integer ();
572           lex_get ();
573         }
574       else
575         fx.spec.u.v.f.d = 0;
576
577       fdp = &formats[fx.spec.u.v.f.type];
578       if (fdp->n_args < 2 && fx.spec.u.v.f.d)
579         {
580           msg (SE, _("Input format %s doesn't accept decimal places."),
581                fdp->name);
582           return 0;
583         }
584       if (fx.spec.u.v.f.d > 16)
585         fx.spec.u.v.f.d = 16;
586
587       if (!lex_force_match (')'))
588         return 0;
589     }
590   else
591     {
592       fx.spec.u.v.f.type = FMT_F;
593       fx.spec.u.v.f.d = 0;
594     }
595
596   fx.sc = fx.lc + 1;
597
598   if ((fx.lc - fx.fc + 1) % fx.nv)
599     {
600       msg (SE, _("The %d columns %d-%d can't be evenly divided into %d "
601                  "fields."), fx.lc - fx.fc + 1, fx.fc + 1, fx.lc + 1, fx.nv);
602       return 0;
603     }
604
605   dividend = (fx.lc - fx.fc + 1) / fx.nv;
606   fx.spec.u.v.f.w = dividend;
607   if (!check_output_specifier (&fx.spec.u.v.f))
608     return 0;
609   if ((type == ALPHA) ^ (formats[fx.spec.u.v.f.type].cat & FCAT_STRING))
610     {
611       msg (SE, _("%s variables cannot be displayed with format %s."),
612            type == ALPHA ? _("String") : _("Numeric"),
613            fmt_to_string (&fx.spec.u.v.f));
614       return 0;
615     }
616
617   /* Check that, for string variables, the user didn't specify a width
618      longer than an actual string width. */
619   if (type == ALPHA)
620     {
621       /* Minimum width of all the string variables specified. */
622       int min_len = fx.v[0]->width;
623
624       for (i = 1; i < fx.nv; i++)
625         min_len = min (min_len, fx.v[i]->width);
626       if (!check_string_specifier (&fx.spec.u.v.f, min_len))
627         return 0;
628     }
629
630   fx.spec.type = PRT_VAR;
631   for (i = 0; i < fx.nv; i++)
632     {
633       fx.spec.fc = fx.fc + dividend * i;
634       fx.spec.u.v.v = fx.v[i];
635       append_var_spec (&fx.spec);
636     }
637   return 1;
638 }
639
640 /* Destroy a format list and, optionally, all its sublists. */
641 static void
642 destroy_fmt_list (struct fmt_list * f, int recurse)
643 {
644   struct fmt_list *next;
645
646   for (; f; f = next)
647     {
648       next = f->next;
649       if (recurse && f->f.type == FMT_DESCEND)
650         destroy_fmt_list (f->down, 1);
651       free (f);
652     }
653 }
654
655 /* Recursively puts the format list F (which represents a set of
656    FORTRAN-like format specifications, like 4(F10,2X)) into the
657    structure prt. */
658 static int
659 dump_fmt_list (struct fmt_list * f)
660 {
661   int i;
662
663   for (; f; f = f->next)
664     if (f->f.type == FMT_X)
665       fx.sc += f->count;
666     else if (f->f.type == FMT_T)
667       fx.sc = f->f.w;
668     else if (f->f.type == FMT_NEWREC)
669       {
670         fx.recno += f->count;
671         fx.sc = 1;
672         fx.spec.type = PRT_NEWLINE;
673         for (i = 0; i < f->count; i++)
674           append_var_spec (&fx.spec);
675       }
676     else
677       for (i = 0; i < f->count; i++)
678         if (f->f.type == FMT_DESCEND)
679           {
680             if (!dump_fmt_list (f->down))
681               return 0;
682           }
683         else
684           {
685             struct variable *v;
686
687             if (fx.cv >= fx.nv)
688               {
689                 msg (SE, _("The number of format "
690                            "specifications exceeds the number of variable "
691                            "names given."));
692                 return 0;
693               }
694
695             v = fx.v[fx.cv++];
696             if ((v->type == ALPHA) ^ (formats[f->f.type].cat & FCAT_STRING))
697               {
698                 msg (SE, _("Display format %s may not be used with a "
699                            "%s variable."), fmt_to_string (&f->f),
700                      v->type == ALPHA ? _("string") : _("numeric"));
701                 return 0;
702               }
703             if (!check_string_specifier (&f->f, v->width))
704               return 0;
705
706             fx.spec.type = PRT_VAR;
707             fx.spec.u.v.v = v;
708             fx.spec.u.v.f = f->f;
709             fx.spec.fc = fx.sc - 1;
710             append_var_spec (&fx.spec);
711
712             fx.sc += f->f.w;
713           }
714   return 1;
715 }
716
717 /* Recursively parses a list of FORTRAN-like format specifiers.  Calls
718    itself to parse nested levels of parentheses.  Returns to its
719    original caller NULL, to indicate error, non-NULL, but nothing
720    useful, to indicate success (it returns a free()'d block). */
721 static struct fmt_list *
722 fixed_parse_fortran (void)
723 {
724   struct fmt_list *head;
725   struct fmt_list *fl = NULL;
726
727   lex_get ();                   /* skip opening parenthesis */
728   while (token != ')')
729     {
730       if (fl)
731         fl = fl->next = xmalloc (sizeof *fl);
732       else
733         head = fl = xmalloc (sizeof *fl);
734
735       if (token == T_NUM)
736         {
737           if (!lex_integer_p ())
738             goto fail;
739           fl->count = lex_integer ();
740           lex_get ();
741         }
742       else
743         fl->count = 1;
744
745       if (token == '(')
746         {
747           fl->f.type = FMT_DESCEND;
748           fx.level++;
749           fl->down = fixed_parse_fortran ();
750           fx.level--;
751           if (!fl->down)
752             goto fail;
753         }
754       else if (lex_match ('/'))
755         fl->f.type = FMT_NEWREC;
756       else if (!parse_format_specifier (&fl->f, 1)
757                || !check_output_specifier (&fl->f))
758         goto fail;
759
760       lex_match (',');
761     }
762   fl->next = NULL;
763   lex_get ();
764
765   if (fx.level)
766     return head;
767
768   fl->next = NULL;
769   dump_fmt_list (head);
770   destroy_fmt_list (head, 1);
771   if (fx.cv < fx.nv)
772     {
773       msg (SE, _("There aren't enough format specifications "
774            "to match the number of variable names given."));
775       goto fail;
776     }
777   return head;
778
779 fail:
780   fl->next = NULL;
781   destroy_fmt_list (head, 0);
782
783   return NULL;
784 }
785
786 /* Prints the table produced by the TABLE subcommand to the listing
787    file. */
788 static void
789 dump_table (void)
790 {
791   struct prt_out_spec *spec;
792   const char *filename;
793   struct tab_table *t;
794   int recno;
795   int nspec;
796
797   for (nspec = 0, spec = prt.spec; spec; spec = spec->next)
798     if (spec->type == PRT_CONST || spec->type == PRT_VAR)
799       nspec++;
800   t = tab_create (4, nspec + 1, 0);
801   tab_columns (t, TAB_COL_DOWN, 1);
802   tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, nspec);
803   tab_hline (t, TAL_2, 0, 3, 1);
804   tab_headers (t, 0, 0, 1, 0);
805   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
806   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
807   tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
808   tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
809   tab_dim (t, tab_natural_dimensions);
810   for (nspec = recno = 0, spec = prt.spec; spec; spec = spec->next)
811     switch (spec->type)
812       {
813       case PRT_NEWLINE:
814         recno++;
815         break;
816       case PRT_CONST:
817         {
818           int len = strlen (spec->u.c);
819           nspec++;
820           tab_text (t, 0, nspec, TAB_LEFT | TAT_FIX | TAT_PRINTF,
821                         "\"%s\"", spec->u.c);
822           tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
823           tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
824                         spec->fc + 1, spec->fc + len);
825           tab_text (t, 3, nspec, TAB_LEFT | TAT_FIX | TAT_PRINTF,
826                         "A%d", len);
827           break;
828         }
829       case PRT_VAR:
830         {
831           nspec++;
832           tab_text (t, 0, nspec, TAB_LEFT, spec->u.v.v->name);
833           tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
834           tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
835                         spec->fc + 1, spec->fc + spec->u.v.f.w);
836           tab_text (t, 3, nspec, TAB_LEFT | TAT_FIX,
837                         fmt_to_string (&spec->u.v.f));
838           break;
839         }
840       case PRT_SPACE:
841         break;
842       case PRT_ERROR:
843         assert (0);
844       }
845
846   filename = fh_handle_name (prt.handle);
847   tab_title (t, 1, (prt.handle != NULL
848                     ? _("Writing %3d records to file %s.")
849                     : _("Writing %3d records to the listing file.")),
850              recno, filename);
851   tab_submit (t);
852   fh_handle_name (NULL);
853 }
854
855 /* PORTME: The number of characters in a line terminator. */
856 #if __MSDOS__ 
857 #define LINE_END_WIDTH 2        /* \r\n */
858 #else
859 #define LINE_END_WIDTH 1        /* \n */
860 #endif
861
862 /* Calculates the maximum possible line width and allocates a buffer
863    big enough to contain it, if necessary (otherwise sets max_width).
864    (The action taken depends on compiler & OS as detected by pref.h.) */
865 static void
866 alloc_line (void)
867 {
868   /* Cumulative maximum line width (excluding null terminator) so far. */
869   int w = 0;
870
871   /* Width required by current this prt_out_spec. */
872   int pot_w;                    /* Potential w. */
873
874   /* Iterator. */
875   struct prt_out_spec *i;
876
877   for (i = prt.spec; i; i = i->next)
878     {
879       switch (i->type)
880         {
881         case PRT_NEWLINE:
882           pot_w = 0;
883           break;
884         case PRT_CONST:
885           pot_w = i->fc + strlen (i->u.c);
886           break;
887         case PRT_VAR:
888           pot_w = i->fc + i->u.v.f.w;
889           break;
890         case PRT_SPACE:
891           pot_w = i->fc + 1;
892           break;
893         case PRT_ERROR:
894           assert (0);
895           break;
896         }
897       if (pot_w > w)
898         w = pot_w;
899     }
900   prt.max_width = w + LINE_END_WIDTH + 1;
901 #if !PAGED_STACK
902   prt.line = xmalloc (prt.max_width);
903 #endif
904 }
905 \f
906 /* Transformation. */
907
908 /* Performs the transformation inside print_trns T on case C. */
909 static int
910 print_trns_proc (struct trns_header * trns, struct ccase * c)
911 {
912   /* Transformation. */
913   struct print_trns *t = (struct print_trns *) trns;
914
915   /* Iterator. */
916   struct prt_out_spec *i;
917
918   /* Line buffer. */
919 #if PAGED_STACK
920 #if __GNUC__ && !__STRICT_ANSI__
921   char buf[t->max_width];
922 #else /* !__GNUC__ */
923   char *buf = alloca (t->max_width);
924 #endif /* !__GNUC__ */
925 #else /* !PAGED_STACK */
926   char *buf = t->line;
927 #endif /* !PAGED_STACK */
928
929   /* Length of the line in buf. */
930   int len = 0;
931   memset (buf, ' ', t->max_width);
932
933   if (t->options & PRT_EJECT)
934     som_eject_page ();
935
936   /* Note that a field written to a place where a field has already
937      been written truncates the record.  `PRINT /A B (T10,F8,T1,F8).'
938      only outputs B.  This is an example of bug-for-bug compatibility,
939      in the author's opinion. */
940   for (i = t->spec; i; i = i->next)
941     switch (i->type)
942       {
943       case PRT_NEWLINE:
944         if (t->handle == NULL)
945           {
946             buf[len] = 0;
947             tab_output_text (TAT_FIX | TAT_NOWRAP, buf);
948           }
949         else
950           {
951             if ((t->options & PRT_CMD_MASK) == PRT_PRINT
952                 || t->handle->mode != FH_MD_BINARY)
953               {
954                 /* PORTME: Line ends. */
955 #if __MSDOS__
956                 buf[len++] = '\r';
957 #endif
958                 buf[len++] = '\n';
959               }
960
961             dfm_put_record (t->handle, buf, len);
962           }
963
964         memset (buf, ' ', t->max_width);
965         len = 0;
966         break;
967
968       case PRT_CONST:
969         /* FIXME: Should be revised to keep track of the string's
970            length outside the loop, probably in i->u.c[0]. */
971         memcpy (&buf[i->fc], i->u.c, strlen (i->u.c));
972         len = i->fc + strlen (i->u.c);
973         break;
974
975       case PRT_VAR:
976         if (i->u.v.v->type == NUMERIC)
977           data_out (&buf[i->fc], &i->u.v.f, &c->data[i->u.v.v->fv]);
978         else
979           {
980             union value t;
981             t.c = c->data[i->u.v.v->fv].s;
982             data_out (&buf[i->fc], &i->u.v.f, &t);
983           }
984         len = i->fc + i->u.v.f.w;
985         break;
986
987       case PRT_SPACE:
988         /* PRT_SPACE always immediately follows PRT_VAR. */
989         buf[len++] = ' ';
990         break;
991
992       case PRT_ERROR:
993         assert (0);
994         break;
995       }
996
997   return -1;
998 }
999
1000 /* Frees all the data inside print_trns T.  Does not free T. */
1001 static void
1002 print_trns_free (struct trns_header * t)
1003 {
1004   struct prt_out_spec *i, *n;
1005
1006   for (i = ((struct print_trns *) t)->spec; i; i = n)
1007     {
1008       switch (i->type)
1009         {
1010         case PRT_CONST:
1011           free (i->u.c);
1012           /* fall through */
1013         case PRT_NEWLINE:
1014         case PRT_VAR:
1015         case PRT_SPACE:
1016           /* nothing to do */
1017           break;
1018         case PRT_ERROR:
1019           assert (0);
1020           break;
1021         }
1022       n = i->next;
1023       free (i);
1024     }
1025 #if !PAGED_STACK
1026   free (((struct print_trns *) t)->line);
1027 #endif
1028 }
1029 \f
1030 /* PRINT SPACE. */
1031
1032 /* PRINT SPACE transformation. */
1033 struct print_space_trns
1034 {
1035   struct trns_header h;
1036
1037   struct file_handle *handle;   /* Output file, NULL=listing file. */
1038   struct expression *e;         /* Number of lines; NULL=1. */
1039 }
1040 print_space_trns;
1041
1042 static int print_space_trns_proc (struct trns_header *, struct ccase *);
1043 static void print_space_trns_free (struct trns_header *);
1044
1045 int
1046 cmd_print_space (void)
1047 {
1048   struct print_space_trns *t;
1049   struct file_handle *handle;
1050   struct expression *e;
1051
1052   lex_match_id ("SPACE");
1053   if (lex_match_id ("OUTFILE"))
1054     {
1055       lex_match ('=');
1056
1057       if (token == T_ID)
1058         handle = fh_get_handle_by_name (tokid);
1059       else if (token == T_STRING)
1060         handle = fh_get_handle_by_filename (tokid);
1061       else
1062         {
1063           msg (SE, _("A file name or handle was expected in the "
1064                      "OUTFILE subcommand."));
1065           return CMD_FAILURE;
1066         }
1067       
1068       if (!handle)
1069         return CMD_FAILURE;
1070       lex_get ();
1071     }
1072   else
1073     handle = NULL;
1074
1075   if (token != '.')
1076     {
1077       e = expr_parse (PXP_NUMERIC);
1078       if (token != '.')
1079         {
1080           expr_free (e);
1081           lex_error (_("expecting end of command"));
1082           return CMD_FAILURE;
1083         }
1084     }
1085   else
1086     e = NULL;
1087
1088   t = xmalloc (sizeof *t);
1089   t->h.proc = print_space_trns_proc;
1090   if (e)
1091     t->h.free = print_space_trns_free;
1092   else
1093     t->h.free = NULL;
1094   t->handle = handle;
1095   t->e = e;
1096
1097   add_transformation ((struct trns_header *) t);
1098   return CMD_SUCCESS;
1099 }
1100
1101 static int
1102 print_space_trns_proc (struct trns_header * trns, struct ccase * c)
1103 {
1104   struct print_space_trns *t = (struct print_space_trns *) trns;
1105   int n;
1106
1107   if (t->e)
1108     {
1109       union value v;
1110
1111       expr_evaluate (t->e, c, &v);
1112       n = v.f;
1113       if (n < 0)
1114         {
1115           msg (SW, _("The expression on PRINT SPACE evaluated to %d.  It's "
1116                      "not possible to PRINT SPACE a negative number of "
1117                      "lines."),
1118                n);
1119           n = 1;
1120         }
1121     }
1122   else
1123     n = 1;
1124
1125   if (t->handle == NULL)
1126     while (n--)
1127       som_blank_line ();
1128   else
1129     {
1130       char buf[LINE_END_WIDTH];
1131
1132       /* PORTME: Line ends. */
1133 #if __MSDOS__
1134       buf[0] = '\r';
1135       buf[1] = '\n';
1136 #else
1137       buf[0] = '\n';
1138 #endif
1139       while (n--)
1140         dfm_put_record (t->handle, buf, LINE_END_WIDTH);
1141     }
1142
1143   return -1;
1144 }
1145
1146 static void
1147 print_space_trns_free (struct trns_header * trns)
1148 {
1149   expr_free (((struct print_space_trns *) trns)->e);
1150 }
1151 \f
1152 /* Debugging code. */
1153
1154 #if 0 && DEBUGGING
1155 void
1156 debug_print (void)
1157 {
1158   struct prt_out_spec *p;
1159
1160   if (prt.handle == NULL)
1161     {
1162       printf ("PRINT");
1163       if (prt.eject)
1164         printf (" EJECT");
1165     }
1166   else
1167     printf ("WRITE OUTFILE=%s", handle_name (prt.handle));
1168   printf (" MAX_WIDTH=%d", prt.max_width);
1169   printf (" /");
1170   for (p = prt.spec; p; p = p->next)
1171     switch (p->type)
1172       {
1173       case PRT_ERROR:
1174         printf (_("<ERROR>"));
1175         break;
1176       case PRT_NEWLINE:
1177         printf ("\n /");
1178         break;
1179       case PRT_CONST:
1180         printf (" \"%s\" %d-%d", p->u.c, p->fc + 1, p->fc + strlen (p->u.c));
1181         break;
1182       case PRT_VAR:
1183         printf (" %s %d %d-%d (%s)", p->u.v.v->name, p->u.v.v->fv, p->fc + 1,
1184                 p->fc + p->u.v.v->print.w, fmt_to_string (&p->u.v.v->print));
1185         break;
1186       case PRT_SPACE:
1187         printf (" \" \" %d", p->fc + 1);
1188         break;
1189       }
1190   printf (".\n");
1191 }
1192 #endif /* DEBUGGING */