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