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