Apply patch #5225, assertions.
[pspp-builds.git] / src / language / data-io / 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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 /* FIXME: seems like a lot of code duplication with data-list.c. */
21
22 #include <config.h>
23
24 #include <stdlib.h>
25
26 #include <data/case.h>
27 #include <data/procedure.h>
28 #include <data/transformations.h>
29 #include <data/variable.h>
30 #include <language/command.h>
31 #include <language/data-io/data-writer.h>
32 #include <language/data-io/file-handle.h>
33 #include <language/expressions/public.h>
34 #include <language/lexer/lexer.h>
35 #include <language/lexer/variable-parser.h>
36 #include <libpspp/alloc.h>
37 #include <libpspp/assertion.h>
38 #include <libpspp/compiler.h>
39 #include <libpspp/message.h>
40 #include <libpspp/message.h>
41 #include <libpspp/misc.h>
42 #include <output/manager.h>
43 #include <output/table.h>
44
45 #include "gettext.h"
46 #define _(msgid) gettext (msgid)
47
48 /* Describes what to do when an output field is encountered. */
49 enum
50   {
51     PRT_ERROR,                  /* Invalid value. */
52     PRT_NEWLINE,                /* Newline. */
53     PRT_CONST,                  /* Constant string. */
54     PRT_VAR,                    /* Variable. */
55     PRT_SPACE                   /* A single space. */
56   };
57
58 /* Describes how to output one field. */
59 struct prt_out_spec
60   {
61     struct prt_out_spec *next;
62     int type;                   /* PRT_* constant. */
63     int fc;                     /* 0-based first column. */
64     union
65       {
66         char *c;                /* PRT_CONST: Associated string. */
67         struct
68           {
69             struct variable *v; /* PRT_VAR: Associated variable. */
70             struct fmt_spec f;  /* PRT_VAR: Output spec. */
71           }
72         v;
73       }
74     u;
75   };
76
77 /* Enums for use with print_trns's `options' field. */
78 enum
79   {
80     PRT_CMD_MASK = 1,           /* Command type mask. */
81     PRT_PRINT = 0,              /* PRINT transformation identifier. */
82     PRT_WRITE = 1,              /* WRITE transformation identifier. */
83     PRT_EJECT = 002,            /* Can be combined with CMD_PRINT only. */
84     PRT_BINARY = 004            /* File is binary, omit newlines. */
85   };
86
87 /* PRINT, PRINT EJECT, WRITE private data structure. */
88 struct print_trns
89   {
90     struct dfm_writer *writer;  /* Output file, NULL=listing file. */
91     int options;                /* PRT_* bitmapped field. */
92     struct prt_out_spec *spec;  /* Output specifications. */
93     int max_width;              /* Maximum line width including null. */
94     char *line;                 /* Buffer for sticking lines in. */
95   };
96
97 /* PRT_PRINT or PRT_WRITE. */
98 int which_cmd;
99
100 /* Holds information on parsing the data file. */
101 static struct print_trns prt;
102
103 /* Last prt_out_spec in the chain.  Used for building the linked-list. */
104 static struct prt_out_spec *next;
105
106 /* Number of records. */
107 static int nrec;
108
109 static int internal_cmd_print (int flags);
110 static trns_proc_func print_trns_proc;
111 static trns_free_func print_trns_free;
112 static int parse_specs (void);
113 static void dump_table (const struct file_handle *);
114 static void append_var_spec (struct prt_out_spec *);
115 static void alloc_line (void);
116 \f
117 /* Basic parsing. */
118
119 /* Parses PRINT command. */
120 int
121 cmd_print (void)
122 {
123   return internal_cmd_print (PRT_PRINT);
124 }
125
126 /* Parses PRINT EJECT command. */
127 int
128 cmd_print_eject (void)
129 {
130   return internal_cmd_print (PRT_PRINT | PRT_EJECT);
131 }
132
133 /* Parses WRITE command. */
134 int
135 cmd_write (void)
136 {
137   return internal_cmd_print (PRT_WRITE);
138 }
139
140 /* Parses the output commands.  F is PRT_PRINT, PRT_WRITE, or
141    PRT_PRINT|PRT_EJECT. */
142 static int
143 internal_cmd_print (int f)
144 {
145   int table = 0;                /* Print table? */
146   struct print_trns *trns = NULL; /* malloc()'d transformation. */
147   struct file_handle *fh = NULL;
148
149   /* Fill in prt to facilitate error-handling. */
150   prt.writer = NULL;
151   prt.options = f;
152   prt.spec = NULL;
153   prt.line = NULL;
154   next = NULL;
155   nrec = 0;
156
157   which_cmd = f & PRT_CMD_MASK;
158
159   /* Parse the command options. */
160   while (!lex_match ('/'))
161     {
162       if (lex_match_id ("OUTFILE"))
163         {
164           lex_match ('=');
165
166           fh = fh_parse (FH_REF_FILE);
167           if (fh == NULL)
168             goto error;
169         }
170       else if (lex_match_id ("RECORDS"))
171         {
172           lex_match ('=');
173           lex_match ('(');
174           if (!lex_force_int ())
175             goto error;
176           nrec = lex_integer ();
177           lex_get ();
178           lex_match (')');
179         }
180       else if (lex_match_id ("TABLE"))
181         table = 1;
182       else if (lex_match_id ("NOTABLE"))
183         table = 0;
184       else
185         {
186           lex_error (_("expecting a valid subcommand"));
187           goto error;
188         }
189     }
190
191   /* Parse variables and strings. */
192   if (!parse_specs ())
193     goto error;
194
195   if (fh != NULL)
196     {
197       prt.writer = dfm_open_writer (fh);
198       if (prt.writer == NULL)
199         goto error;
200
201       if (fh_get_mode (fh) == FH_MODE_BINARY)
202         prt.options |= PRT_BINARY;
203     }
204
205   /* Output the variable table if requested. */
206   if (table)
207     dump_table (fh);
208
209   /* Count the maximum line width.  Allocate linebuffer if
210      applicable. */
211   alloc_line ();
212
213   /* Put the transformation in the queue. */
214   trns = xmalloc (sizeof *trns);
215   memcpy (trns, &prt, sizeof *trns);
216   add_transformation (print_trns_proc, print_trns_free, trns);
217
218   return CMD_SUCCESS;
219
220  error:
221   print_trns_free (&prt);
222   return CMD_FAILURE;
223 }
224
225 /* Appends the field output specification SPEC to the list maintained
226    in prt. */
227 static void
228 append_var_spec (struct prt_out_spec *spec)
229 {
230   if (next == 0)
231     prt.spec = next = xmalloc (sizeof *spec);
232   else
233     next = next->next = xmalloc (sizeof *spec);
234
235   memcpy (next, spec, sizeof *spec);
236   next->next = NULL;
237 }
238 \f
239 /* Field parsing.  Mostly stolen from data-list.c. */
240
241 /* Used for chaining together fortran-like format specifiers. */
242 struct fmt_list
243 {
244   struct fmt_list *next;
245   int count;
246   struct fmt_spec f;
247   struct fmt_list *down;
248 };
249
250 /* Used as "local" variables among the fixed-format parsing funcs.  If
251    it were guaranteed that PSPP were going to be compiled by gcc,
252    I'd make all these functions a single set of nested functions. */
253 static struct
254   {
255     struct variable **v;                /* variable list */
256     size_t nv;                  /* number of variables in list */
257     size_t cv;                  /* number of variables from list used up so far
258                                    by the FORTRAN-like format specifiers */
259
260     int recno;                  /* current 1-based record number */
261     int sc;                     /* 1-based starting column for next variable */
262
263     struct prt_out_spec spec;           /* next format spec to append to list */
264     int fc, lc;                 /* first, last 1-based column number of current
265                                    var */
266
267     int level;                  /* recursion level for FORTRAN-like format
268                                    specifiers */
269   }
270 fx;
271
272 static int fixed_parse_compatible (void);
273 static struct fmt_list *fixed_parse_fortran (void);
274
275 static int parse_string_argument (void);
276 static int parse_variable_argument (void);
277
278 /* Parses all the variable and string specifications on a single
279    PRINT, PRINT EJECT, or WRITE command into the prt structure.
280    Returns success. */
281 static int
282 parse_specs (void)
283 {
284   /* Return code from called function. */
285   int code;
286
287   fx.recno = 1;
288   fx.sc = 1;
289
290   while (token != '.')
291     {
292       while (lex_match ('/'))
293         {
294           int prev_recno = fx.recno;
295
296           fx.recno++;
297           if (lex_is_number ())
298             {
299               if (!lex_force_int ())
300                 return 0;
301               if (lex_integer () < fx.recno)
302                 {
303                   msg (SE, _("The record number specified, %ld, is "
304                              "before the previous record, %d.  Data "
305                              "fields must be listed in order of "
306                              "increasing record number."),
307                        lex_integer (), fx.recno - 1);
308                   return 0;
309                 }
310               fx.recno = lex_integer ();
311               lex_get ();
312             }
313
314           fx.spec.type = PRT_NEWLINE;
315           while (prev_recno++ < fx.recno)
316             append_var_spec (&fx.spec);
317
318           fx.sc = 1;
319         }
320
321       if (token == T_STRING)
322         code = parse_string_argument ();
323       else
324         code = parse_variable_argument ();
325       if (!code)
326         return 0;
327     }
328   fx.spec.type = PRT_NEWLINE;
329   append_var_spec (&fx.spec);
330
331   if (!nrec)
332     nrec = fx.recno;
333   else if (fx.recno > nrec)
334     {
335       msg (SE, _("Variables are specified on records that "
336                  "should not exist according to RECORDS subcommand."));
337       return 0;
338     }
339       
340   if (token != '.')
341     {
342       lex_error (_("expecting end of command"));
343       return 0;
344     }
345   
346   return 1;
347 }
348
349 /* Parses a string argument to the PRINT commands.  Returns success. */
350 static int
351 parse_string_argument (void)
352 {
353   fx.spec.type = PRT_CONST;
354   fx.spec.fc = fx.sc - 1;
355   fx.spec.u.c = ds_xstrdup (&tokstr);
356   lex_get ();
357
358   /* Parse the included column range. */
359   if (lex_is_number ())
360     {
361       /* Width of column range in characters. */
362       int c_len;
363
364       /* Width of constant string in characters. */
365       int s_len;
366
367       /* 1-based index of last column in range. */
368       int lc;
369
370       if (!lex_is_integer () || lex_integer () <= 0)
371         {
372           msg (SE, _("%g is not a valid column location."), tokval);
373           goto fail;
374         }
375       fx.spec.fc = lex_integer () - 1;
376
377       lex_get ();
378       lex_negative_to_dash ();
379       if (lex_match ('-'))
380         {
381           if (!lex_is_integer ())
382             {
383               msg (SE, _("Column location expected following `%d-'."),
384                    fx.spec.fc + 1);
385               goto fail;
386             }
387           if (lex_integer () <= 0)
388             {
389               msg (SE, _("%g is not a valid column location."), tokval);
390               goto fail;
391             }
392           if (lex_integer () < fx.spec.fc + 1)
393             {
394               msg (SE, _("%d-%ld is not a valid column range.  The second "
395                    "column must be greater than or equal to the first."),
396                    fx.spec.fc + 1, lex_integer ());
397               goto fail;
398             }
399           lc = lex_integer () - 1;
400
401           lex_get ();
402         }
403       else
404         /* If only a starting location is specified then the field is
405            the width of the provided string. */
406         lc = fx.spec.fc + strlen (fx.spec.u.c) - 1;
407
408       /* Apply the range. */
409       c_len = lc - fx.spec.fc + 1;
410       s_len = strlen (fx.spec.u.c);
411       if (s_len > c_len)
412         fx.spec.u.c[c_len] = 0;
413       else if (s_len < c_len)
414         {
415           fx.spec.u.c = xrealloc (fx.spec.u.c, c_len + 1);
416           memset (&fx.spec.u.c[s_len], ' ', c_len - s_len);
417           fx.spec.u.c[c_len] = 0;
418         }
419
420       fx.sc = lc + 1;
421     }
422   else
423     /* If nothing is provided then the field is the width of the
424        provided string. */
425     fx.sc += strlen (fx.spec.u.c);
426
427   append_var_spec (&fx.spec);
428   return 1;
429
430 fail:
431   free (fx.spec.u.c);
432   return 0;
433 }
434
435 /* Parses a variable argument to the PRINT commands by passing it off
436    to fixed_parse_compatible() or fixed_parse_fortran() as appropriate.
437    Returns success. */
438 static int
439 parse_variable_argument (void)
440 {
441   if (!parse_variables (default_dict, &fx.v, &fx.nv, PV_DUPLICATE))
442     return 0;
443
444   if (lex_is_number ())
445     {
446       if (!fixed_parse_compatible ())
447         goto fail;
448     }
449   else if (token == '(')
450     {
451       fx.level = 0;
452       fx.cv = 0;
453       if (!fixed_parse_fortran ())
454         goto fail;
455     }
456   else
457     {
458       /* User wants dictionary format specifiers. */
459       size_t i;
460
461       lex_match ('*');
462       for (i = 0; i < fx.nv; i++)
463         {
464           /* Variable. */
465           fx.spec.type = PRT_VAR;
466           fx.spec.fc = fx.sc - 1;
467           fx.spec.u.v.v = fx.v[i];
468           fx.spec.u.v.f = fx.v[i]->print;
469           append_var_spec (&fx.spec);
470           fx.sc += fx.v[i]->print.w;
471
472           /* Space. */
473           fx.spec.type = PRT_SPACE;
474           fx.spec.fc = fx.sc - 1;
475           append_var_spec (&fx.spec);
476           fx.sc++;
477         }
478     }
479
480   free (fx.v);
481   return 1;
482
483 fail:
484   free (fx.v);
485   return 0;
486 }
487
488 /* Verifies that FORMAT doesn't need a variable wider than WIDTH.
489    Returns true iff that is the case. */
490 static bool
491 check_string_width (const struct fmt_spec *format, const struct variable *v) 
492 {
493   if (get_format_var_width (format) > v->width)
494     {
495       msg (SE, _("Variable %s has width %d so it cannot be output "
496                  "as format %s."),
497            v->name, v->width, fmt_to_string (format));
498       return false;
499     }
500   return true;
501 }
502
503 /* Parses a column specification for parse_specs(). */
504 static int
505 fixed_parse_compatible (void)
506 {
507   int individual_var_width;
508   int type;
509   size_t i;
510
511   type = fx.v[0]->type;
512   for (i = 1; i < fx.nv; i++)
513     if (type != fx.v[i]->type)
514       {
515         msg (SE, _("%s is not of the same type as %s.  To specify "
516                    "variables of different types in the same variable "
517                    "list, use a FORTRAN-like format specifier."),
518              fx.v[i]->name, fx.v[0]->name);
519         return 0;
520       }
521
522   if (!lex_force_int ())
523     return 0;
524   fx.fc = lex_integer () - 1;
525   if (fx.fc < 0)
526     {
527       msg (SE, _("Column positions for fields must be positive."));
528       return 0;
529     }
530   lex_get ();
531
532   lex_negative_to_dash ();
533   if (lex_match ('-'))
534     {
535       if (!lex_force_int ())
536         return 0;
537       fx.lc = lex_integer () - 1;
538       if (fx.lc < 0)
539         {
540           msg (SE, _("Column positions for fields must be positive."));
541           return 0;
542         }
543       else if (fx.lc < fx.fc)
544         {
545           msg (SE, _("The ending column for a field must not "
546                      "be less than the starting column."));
547           return 0;
548         }
549       lex_get ();
550     }
551   else
552     fx.lc = fx.fc;
553
554   fx.spec.u.v.f.w = fx.lc - fx.fc + 1;
555   if (lex_match ('('))
556     {
557       struct fmt_desc *fdp;
558
559       if (token == T_ID)
560         {
561           const char *cp;
562
563           fx.spec.u.v.f.type = parse_format_specifier_name (&cp, 0);
564           if (fx.spec.u.v.f.type == -1)
565             return 0;
566           if (*cp)
567             {
568               msg (SE, _("A format specifier on this line "
569                          "has extra characters on the end."));
570               return 0;
571             }
572           lex_get ();
573           lex_match (',');
574         }
575       else
576         fx.spec.u.v.f.type = FMT_F;
577
578       if (lex_is_number ())
579         {
580           if (!lex_force_int ())
581             return 0;
582           if (lex_integer () < 1)
583             {
584               msg (SE, _("The value for number of decimal places "
585                          "must be at least 1."));
586               return 0;
587             }
588           fx.spec.u.v.f.d = lex_integer ();
589           lex_get ();
590         }
591       else
592         fx.spec.u.v.f.d = 0;
593
594       fdp = &formats[fx.spec.u.v.f.type];
595       if (fdp->n_args < 2 && fx.spec.u.v.f.d)
596         {
597           msg (SE, _("Input format %s doesn't accept decimal places."),
598                fdp->name);
599           return 0;
600         }
601       if (fx.spec.u.v.f.d > 16)
602         fx.spec.u.v.f.d = 16;
603
604       if (!lex_force_match (')'))
605         return 0;
606     }
607   else
608     {
609       fx.spec.u.v.f.type = FMT_F;
610       fx.spec.u.v.f.d = 0;
611     }
612
613   fx.sc = fx.lc + 1;
614
615   if ((fx.lc - fx.fc + 1) % fx.nv)
616     {
617       msg (SE, _("The %d columns %d-%d can't be evenly divided into %u "
618                  "fields."),
619            fx.lc - fx.fc + 1, fx.fc + 1, fx.lc + 1, (unsigned) fx.nv);
620       return 0;
621     }
622
623   individual_var_width = (fx.lc - fx.fc + 1) / fx.nv;
624   fx.spec.u.v.f.w = individual_var_width;
625   if (!check_output_specifier (&fx.spec.u.v.f, true)
626       || !check_specifier_type (&fx.spec.u.v.f, type, true))
627     return 0;
628   if (type == ALPHA)
629     {
630       for (i = 0; i < fx.nv; i++)
631         if (!check_string_width (&fx.spec.u.v.f, fx.v[i]))
632           return false;
633     }
634
635   fx.spec.type = PRT_VAR;
636   for (i = 0; i < fx.nv; i++)
637     {
638       fx.spec.fc = fx.fc + individual_var_width * i;
639       fx.spec.u.v.v = fx.v[i];
640       append_var_spec (&fx.spec);
641     }
642   return 1;
643 }
644
645 /* Destroy a format list and, optionally, all its sublists. */
646 static void
647 destroy_fmt_list (struct fmt_list *f, int recurse)
648 {
649   struct fmt_list *next;
650
651   for (; f; f = next)
652     {
653       next = f->next;
654       if (recurse && f->f.type == FMT_DESCEND)
655         destroy_fmt_list (f->down, 1);
656       free (f);
657     }
658 }
659
660 /* Recursively puts the format list F (which represents a set of
661    FORTRAN-like format specifications, like 4(F10,2X)) into the
662    structure prt. */
663 static int
664 dump_fmt_list (struct fmt_list *f)
665 {
666   int i;
667
668   for (; f; f = f->next)
669     if (f->f.type == FMT_X)
670       fx.sc += f->count;
671     else if (f->f.type == FMT_T)
672       fx.sc = f->f.w;
673     else if (f->f.type == FMT_NEWREC)
674       {
675         fx.recno += f->count;
676         fx.sc = 1;
677         fx.spec.type = PRT_NEWLINE;
678         for (i = 0; i < f->count; i++)
679           append_var_spec (&fx.spec);
680       }
681     else
682       for (i = 0; i < f->count; i++)
683         if (f->f.type == FMT_DESCEND)
684           {
685             if (!dump_fmt_list (f->down))
686               return 0;
687           }
688         else
689           {
690             struct variable *v;
691
692             if (fx.cv >= fx.nv)
693               {
694                 msg (SE, _("The number of format "
695                            "specifications exceeds the number of variable "
696                            "names given."));
697                 return 0;
698               }
699
700             v = fx.v[fx.cv++];
701             if (!check_output_specifier (&f->f, true)
702                 || !check_specifier_type (&f->f, v->type, true)
703                 || !check_string_width (&f->f, v))
704               return false;
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 = NULL;
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 (lex_is_number ())
736         {
737           if (!lex_is_integer ())
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, FMTP_ALLOW_XT)
757                || !check_output_specifier (&fl->f, 1))
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 (const struct file_handle *fh)
790 {
791   struct prt_out_spec *spec;
792   struct tab_table *t;
793   int recno;
794   int nspec;
795
796   for (nspec = 0, spec = prt.spec; spec; spec = spec->next)
797     if (spec->type == PRT_CONST || spec->type == PRT_VAR)
798       nspec++;
799   t = tab_create (4, nspec + 1, 0);
800   tab_columns (t, TAB_COL_DOWN, 1);
801   tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, nspec);
802   tab_hline (t, TAL_2, 0, 3, 1);
803   tab_headers (t, 0, 0, 1, 0);
804   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
805   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
806   tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
807   tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
808   tab_dim (t, tab_natural_dimensions);
809   for (nspec = recno = 0, spec = prt.spec; spec; spec = spec->next)
810     switch (spec->type)
811       {
812       case PRT_NEWLINE:
813         recno++;
814         break;
815       case PRT_CONST:
816         {
817           int len = strlen (spec->u.c);
818           nspec++;
819           tab_text (t, 0, nspec, TAB_LEFT | TAB_FIX | TAT_PRINTF,
820                         "\"%s\"", spec->u.c);
821           tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
822           tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
823                         spec->fc + 1, spec->fc + len);
824           tab_text (t, 3, nspec, TAB_LEFT | TAB_FIX | TAT_PRINTF,
825                         "A%d", len);
826           break;
827         }
828       case PRT_VAR:
829         {
830           nspec++;
831           tab_text (t, 0, nspec, TAB_LEFT, spec->u.v.v->name);
832           tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
833           tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
834                         spec->fc + 1, spec->fc + spec->u.v.f.w);
835           tab_text (t, 3, nspec, TAB_LEFT | TAB_FIX,
836                         fmt_to_string (&spec->u.v.f));
837           break;
838         }
839       case PRT_SPACE:
840         break;
841       case PRT_ERROR:
842         NOT_REACHED ();
843       }
844
845   if (fh != NULL)
846     tab_title (t, ngettext ("Writing %d record to %s.",
847                             "Writing %d records to %s.", recno),
848                recno, fh_get_name (fh));
849   else
850     tab_title (t, ngettext ("Writing %d record.",
851                             "Writing %d records.", recno), recno);
852   tab_submit (t);
853 }
854
855 /* Calculates the maximum possible line width and allocates a buffer
856    big enough to contain it */
857 static void
858 alloc_line (void)
859 {
860   /* Cumulative maximum line width (excluding null terminator) so far. */
861   int w = 0;
862
863   /* Width required by current this prt_out_spec. */
864   int pot_w;                    /* Potential w. */
865
866   /* Iterator. */
867   struct prt_out_spec *i;
868
869   for (i = prt.spec; i; i = i->next)
870     {
871       switch (i->type)
872         {
873         case PRT_NEWLINE:
874           pot_w = 0;
875           break;
876         case PRT_CONST:
877           pot_w = i->fc + strlen (i->u.c);
878           break;
879         case PRT_VAR:
880           pot_w = i->fc + i->u.v.f.w;
881           break;
882         case PRT_SPACE:
883           pot_w = i->fc + 1;
884           break;
885         case PRT_ERROR:
886         default:
887           NOT_REACHED ();
888         }
889       if (pot_w > w)
890         w = pot_w;
891     }
892   prt.max_width = w + 2;
893   prt.line = xmalloc (prt.max_width);
894 }
895 \f
896 /* Transformation. */
897
898 /* Performs the transformation inside print_trns T on case C. */
899 static int
900 print_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
901 {
902   /* Transformation. */
903   struct print_trns *t = trns_;
904
905   /* Iterator. */
906   struct prt_out_spec *i;
907
908   /* Line buffer. */
909   char *buf = t->line;
910
911   /* Length of the line in buf. */
912   int len = 0;
913   memset (buf, ' ', t->max_width);
914
915   if (t->options & PRT_EJECT)
916     som_eject_page ();
917
918   /* Note that a field written to a place where a field has
919      already been written truncates the record.  `PRINT /A B
920      (T10,F8,T1,F8).' only outputs B.  */
921   for (i = t->spec; i; i = i->next)
922     switch (i->type)
923       {
924       case PRT_NEWLINE:
925         if (t->writer == NULL)
926           {
927             buf[len] = 0;
928             tab_output_text (TAB_FIX | TAT_NOWRAP, buf);
929           }
930         else
931           {
932             if ((t->options & PRT_CMD_MASK) == PRT_PRINT
933                 || !(t->options & PRT_BINARY))
934               buf[len++] = '\n';
935
936             dfm_put_record (t->writer, buf, len);
937           }
938
939         memset (buf, ' ', t->max_width);
940         len = 0;
941         break;
942
943       case PRT_CONST:
944         /* FIXME: Should be revised to keep track of the string's
945            length outside the loop, probably in i->u.c[0]. */
946         memcpy (&buf[i->fc], i->u.c, strlen (i->u.c));
947         len = i->fc + strlen (i->u.c);
948         break;
949
950       case PRT_VAR:
951         data_out (&buf[i->fc], &i->u.v.f, case_data (c, i->u.v.v->fv));
952         len = i->fc + i->u.v.f.w;
953         break;
954
955       case PRT_SPACE:
956         /* PRT_SPACE always immediately follows PRT_VAR. */
957         buf[len++] = ' ';
958         break;
959
960       case PRT_ERROR:
961         NOT_REACHED ();
962       }
963
964   if (t->writer != NULL && dfm_write_error (t->writer))
965     return TRNS_ERROR;
966   return TRNS_CONTINUE;
967 }
968
969 /* Frees all the data inside print_trns PRT.  Does not free PRT. */
970 static bool
971 print_trns_free (void *prt_)
972 {
973   struct print_trns *prt = prt_;
974   struct prt_out_spec *i, *n;
975   bool ok = true;
976
977   for (i = prt->spec; i; i = n)
978     {
979       switch (i->type)
980         {
981         case PRT_CONST:
982           free (i->u.c);
983           /* fall through */
984         case PRT_NEWLINE:
985         case PRT_VAR:
986         case PRT_SPACE:
987           /* nothing to do */
988           break;
989         case PRT_ERROR:
990           NOT_REACHED ();
991         }
992       n = i->next;
993       free (i);
994     }
995   if (prt->writer != NULL)
996     ok = dfm_close_writer (prt->writer);
997   free (prt->line);
998   return ok;
999 }
1000 \f
1001 /* PRINT SPACE. */
1002
1003 /* PRINT SPACE transformation. */
1004 struct print_space_trns
1005 {
1006   struct dfm_writer *writer;    /* Output data file. */
1007   struct expression *e;         /* Number of lines; NULL=1. */
1008 }
1009 print_space_trns;
1010
1011 static trns_proc_func print_space_trns_proc;
1012 static trns_free_func print_space_trns_free;
1013
1014 int
1015 cmd_print_space (void)
1016 {
1017   struct print_space_trns *t;
1018   struct file_handle *fh;
1019   struct expression *e;
1020   struct dfm_writer *writer;
1021
1022   if (lex_match_id ("OUTFILE"))
1023     {
1024       lex_match ('=');
1025
1026       fh = fh_parse (FH_REF_FILE);
1027       if (fh == NULL)
1028         return CMD_FAILURE;
1029       lex_get ();
1030     }
1031   else
1032     fh = NULL;
1033
1034   if (token != '.')
1035     {
1036       e = expr_parse (default_dict, EXPR_NUMBER);
1037       if (token != '.')
1038         {
1039           expr_free (e);
1040           lex_error (_("expecting end of command"));
1041           return CMD_FAILURE;
1042         }
1043     }
1044   else
1045     e = NULL;
1046
1047   if (fh != NULL)
1048     {
1049       writer = dfm_open_writer (fh);
1050       if (writer == NULL) 
1051         {
1052           expr_free (e);
1053           return CMD_FAILURE;
1054         } 
1055     }
1056   else
1057     writer = NULL;
1058   
1059   t = xmalloc (sizeof *t);
1060   t->writer = writer;
1061   t->e = e;
1062
1063   add_transformation (print_space_trns_proc, print_space_trns_free, t);
1064   return CMD_SUCCESS;
1065 }
1066
1067 /* Executes a PRINT SPACE transformation. */
1068 static int
1069 print_space_trns_proc (void *t_, struct ccase *c,
1070                        int case_num UNUSED)
1071 {
1072   struct print_space_trns *t = t_;
1073   int n;
1074
1075   n = 1;
1076   if (t->e)
1077     {
1078       double f = expr_evaluate_num (t->e, c, case_num);
1079       if (f == SYSMIS) 
1080         msg (SW, _("The expression on PRINT SPACE evaluated to the "
1081                    "system-missing value."));
1082       else if (f < 0 || f > INT_MAX)
1083         msg (SW, _("The expression on PRINT SPACE evaluated to %g."), f);
1084       else
1085         n = f;
1086     }
1087
1088   while (n--)
1089     if (t->writer == NULL)
1090       som_blank_line ();
1091     else
1092       dfm_put_record (t->writer, "\n", 1);
1093
1094   if (t->writer != NULL && dfm_write_error (t->writer))
1095     return TRNS_ERROR;
1096   return TRNS_CONTINUE;
1097 }
1098
1099 /* Frees a PRINT SPACE transformation.
1100    Returns true if successful, false if an I/O error occurred. */
1101 static bool
1102 print_space_trns_free (void *trns_)
1103 {
1104   struct print_space_trns *trns = trns_;
1105   bool ok = dfm_close_writer (trns->writer);
1106   expr_free (trns->e);
1107   free (trns);
1108   return ok;
1109 }