Adopt use of gnulib for portability.
[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 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 /* Describes what to do when an output field is encountered. */
42 enum
43   {
44     PRT_ERROR,                  /* Invalid value. */
45     PRT_NEWLINE,                /* Newline. */
46     PRT_CONST,                  /* Constant string. */
47     PRT_VAR,                    /* Variable. */
48     PRT_SPACE                   /* A single space. */
49   };
50
51 /* Describes how to output one field. */
52 struct prt_out_spec
53   {
54     struct prt_out_spec *next;
55     int type;                   /* PRT_* constant. */
56     int fc;                     /* 0-based first column. */
57     union
58       {
59         char *c;                /* PRT_CONST: Associated string. */
60         struct
61           {
62             struct variable *v; /* PRT_VAR: Associated variable. */
63             struct fmt_spec f;  /* PRT_VAR: Output spec. */
64           }
65         v;
66       }
67     u;
68   };
69
70 /* Enums for use with print_trns's `options' field. */
71 enum
72   {
73     PRT_CMD_MASK = 1,           /* Command type mask. */
74     PRT_PRINT = 0,              /* PRINT transformation identifier. */
75     PRT_WRITE = 1,              /* WRITE transformation identifier. */
76     PRT_EJECT = 002,            /* Can be combined with CMD_PRINT only. */
77     PRT_BINARY = 004            /* File is binary, omit newlines. */
78   };
79
80 /* PRINT, PRINT EJECT, WRITE private data structure. */
81 struct print_trns
82   {
83     struct trns_header h;
84     struct dfm_writer *writer;  /* Output file, NULL=listing file. */
85     int options;                /* PRT_* bitmapped field. */
86     struct prt_out_spec *spec;  /* Output specifications. */
87     int max_width;              /* Maximum line width including null. */
88     char *line;                 /* Buffer for sticking lines in. */
89   };
90
91 /* PRT_PRINT or PRT_WRITE. */
92 int which_cmd;
93
94 /* Holds information on parsing the data file. */
95 static struct print_trns prt;
96
97 /* Last prt_out_spec in the chain.  Used for building the linked-list. */
98 static struct prt_out_spec *next;
99
100 /* Number of records. */
101 static int nrec;
102
103 static int internal_cmd_print (int flags);
104 static trns_proc_func print_trns_proc;
105 static trns_free_func print_trns_free;
106 static int parse_specs (void);
107 static void dump_table (const struct file_handle *);
108 static void append_var_spec (struct prt_out_spec *);
109 static void alloc_line (void);
110 \f
111 /* Basic parsing. */
112
113 /* Parses PRINT command. */
114 int
115 cmd_print (void)
116 {
117   return internal_cmd_print (PRT_PRINT);
118 }
119
120 /* Parses PRINT EJECT command. */
121 int
122 cmd_print_eject (void)
123 {
124   return internal_cmd_print (PRT_PRINT | PRT_EJECT);
125 }
126
127 /* Parses WRITE command. */
128 int
129 cmd_write (void)
130 {
131   return internal_cmd_print (PRT_WRITE);
132 }
133
134 /* Parses the output commands.  F is PRT_PRINT, PRT_WRITE, or
135    PRT_PRINT|PRT_EJECT. */
136 static int
137 internal_cmd_print (int f)
138 {
139   int table = 0;                /* Print table? */
140   struct print_trns *trns;      /* malloc()'d transformation. */
141   struct file_handle *fh = NULL;
142
143   /* Fill in prt to facilitate error-handling. */
144   prt.h.proc = print_trns_proc;
145   prt.h.free = print_trns_free;
146   prt.writer = NULL;
147   prt.options = f;
148   prt.spec = NULL;
149   prt.line = NULL;
150   next = NULL;
151   nrec = 0;
152
153   which_cmd = f & PRT_CMD_MASK;
154
155   /* Parse the command options. */
156   while (!lex_match ('/'))
157     {
158       if (lex_match_id ("OUTFILE"))
159         {
160           lex_match ('=');
161
162           fh = fh_parse ();
163           if (fh == NULL)
164             goto error;
165         }
166       else if (lex_match_id ("RECORDS"))
167         {
168           lex_match ('=');
169           lex_match ('(');
170           if (!lex_force_int ())
171             goto error;
172           nrec = lex_integer ();
173           lex_get ();
174           lex_match (')');
175         }
176       else if (lex_match_id ("TABLE"))
177         table = 1;
178       else if (lex_match_id ("NOTABLE"))
179         table = 0;
180       else
181         {
182           lex_error (_("expecting a valid subcommand"));
183           goto error;
184         }
185     }
186
187   /* Parse variables and strings. */
188   if (!parse_specs ())
189     goto error;
190
191   if (fh != NULL)
192     {
193       prt.writer = dfm_open_writer (fh);
194       if (prt.writer == NULL)
195         goto error;
196
197       if (handle_get_mode (fh) == MODE_BINARY)
198         prt.options |= PRT_BINARY;
199     }
200
201   /* Output the variable table if requested. */
202   if (table)
203     dump_table (fh);
204
205   /* Count the maximum line width.  Allocate linebuffer if
206      applicable. */
207   alloc_line ();
208
209   /* Put the transformation in the queue. */
210   trns = xmalloc (sizeof *trns);
211   memcpy (trns, &prt, sizeof *trns);
212   add_transformation ((struct trns_header *) trns);
213
214   return CMD_SUCCESS;
215
216  error:
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 (lex_is_number ())
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_c_str (&tokstr));
352   lex_get ();
353
354   /* Parse the included column range. */
355   if (lex_is_number ())
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_is_integer () || 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_is_integer ())
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 (lex_is_number ())
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 /* Verifies that FORMAT doesn't need a variable wider than WIDTH.
485    Returns true iff that is the case. */
486 static bool
487 check_string_width (const struct fmt_spec *format, const struct variable *v) 
488 {
489   if (get_format_var_width (format) > v->width)
490     {
491       msg (SE, _("Variable %s has width %d so it cannot be output "
492                  "as format %s."),
493            v->name, v->width, fmt_to_string (format));
494       return false;
495     }
496   return true;
497 }
498
499 /* Parses a column specification for parse_specs(). */
500 static int
501 fixed_parse_compatible (void)
502 {
503   int dividend;
504   int type;
505   int i;
506
507   type = fx.v[0]->type;
508   for (i = 1; i < fx.nv; i++)
509     if (type != fx.v[i]->type)
510       {
511         msg (SE, _("%s is not of the same type as %s.  To specify "
512                    "variables of different types in the same variable "
513                    "list, use a FORTRAN-like format specifier."),
514              fx.v[i]->name, fx.v[0]->name);
515         return 0;
516       }
517
518   if (!lex_force_int ())
519     return 0;
520   fx.fc = lex_integer () - 1;
521   if (fx.fc < 0)
522     {
523       msg (SE, _("Column positions for fields must be positive."));
524       return 0;
525     }
526   lex_get ();
527
528   lex_negative_to_dash ();
529   if (lex_match ('-'))
530     {
531       if (!lex_force_int ())
532         return 0;
533       fx.lc = lex_integer () - 1;
534       if (fx.lc < 0)
535         {
536           msg (SE, _("Column positions for fields must be positive."));
537           return 0;
538         }
539       else if (fx.lc < fx.fc)
540         {
541           msg (SE, _("The ending column for a field must not "
542                      "be less than the starting column."));
543           return 0;
544         }
545       lex_get ();
546     }
547   else
548     fx.lc = fx.fc;
549
550   fx.spec.u.v.f.w = fx.lc - fx.fc + 1;
551   if (lex_match ('('))
552     {
553       struct fmt_desc *fdp;
554
555       if (token == T_ID)
556         {
557           const char *cp;
558
559           fx.spec.u.v.f.type = parse_format_specifier_name (&cp, 0);
560           if (fx.spec.u.v.f.type == -1)
561             return 0;
562           if (*cp)
563             {
564               msg (SE, _("A format specifier on this line "
565                          "has extra characters on the end."));
566               return 0;
567             }
568           lex_get ();
569           lex_match (',');
570         }
571       else
572         fx.spec.u.v.f.type = FMT_F;
573
574       if (lex_is_number ())
575         {
576           if (!lex_force_int ())
577             return 0;
578           if (lex_integer () < 1)
579             {
580               msg (SE, _("The value for number of decimal places "
581                          "must be at least 1."));
582               return 0;
583             }
584           fx.spec.u.v.f.d = lex_integer ();
585           lex_get ();
586         }
587       else
588         fx.spec.u.v.f.d = 0;
589
590       fdp = &formats[fx.spec.u.v.f.type];
591       if (fdp->n_args < 2 && fx.spec.u.v.f.d)
592         {
593           msg (SE, _("Input format %s doesn't accept decimal places."),
594                fdp->name);
595           return 0;
596         }
597       if (fx.spec.u.v.f.d > 16)
598         fx.spec.u.v.f.d = 16;
599
600       if (!lex_force_match (')'))
601         return 0;
602     }
603   else
604     {
605       fx.spec.u.v.f.type = FMT_F;
606       fx.spec.u.v.f.d = 0;
607     }
608
609   fx.sc = fx.lc + 1;
610
611   if ((fx.lc - fx.fc + 1) % fx.nv)
612     {
613       msg (SE, _("The %d columns %d-%d can't be evenly divided into %d "
614                  "fields."), fx.lc - fx.fc + 1, fx.fc + 1, fx.lc + 1, fx.nv);
615       return 0;
616     }
617
618   dividend = (fx.lc - fx.fc + 1) / fx.nv;
619   fx.spec.u.v.f.w = dividend;
620   if (!check_output_specifier (&fx.spec.u.v.f, true)
621       || !check_specifier_type (&fx.spec.u.v.f, type, true))
622     return 0;
623   if (type == ALPHA)
624     {
625       for (i = 0; i < fx.nv; i++)
626         if (!check_string_width (&fx.spec.u.v.f, fx.v[i]))
627           return false;
628     }
629
630   fx.spec.type = PRT_VAR;
631   for (i = 0; i < fx.nv; i++)
632     {
633       fx.spec.fc = fx.fc + dividend * i;
634       fx.spec.u.v.v = fx.v[i];
635       append_var_spec (&fx.spec);
636     }
637   return 1;
638 }
639
640 /* Destroy a format list and, optionally, all its sublists. */
641 static void
642 destroy_fmt_list (struct fmt_list * f, int recurse)
643 {
644   struct fmt_list *next;
645
646   for (; f; f = next)
647     {
648       next = f->next;
649       if (recurse && f->f.type == FMT_DESCEND)
650         destroy_fmt_list (f->down, 1);
651       free (f);
652     }
653 }
654
655 /* Recursively puts the format list F (which represents a set of
656    FORTRAN-like format specifications, like 4(F10,2X)) into the
657    structure prt. */
658 static int
659 dump_fmt_list (struct fmt_list * f)
660 {
661   int i;
662
663   for (; f; f = f->next)
664     if (f->f.type == FMT_X)
665       fx.sc += f->count;
666     else if (f->f.type == FMT_T)
667       fx.sc = f->f.w;
668     else if (f->f.type == FMT_NEWREC)
669       {
670         fx.recno += f->count;
671         fx.sc = 1;
672         fx.spec.type = PRT_NEWLINE;
673         for (i = 0; i < f->count; i++)
674           append_var_spec (&fx.spec);
675       }
676     else
677       for (i = 0; i < f->count; i++)
678         if (f->f.type == FMT_DESCEND)
679           {
680             if (!dump_fmt_list (f->down))
681               return 0;
682           }
683         else
684           {
685             struct variable *v;
686
687             if (fx.cv >= fx.nv)
688               {
689                 msg (SE, _("The number of format "
690                            "specifications exceeds the number of variable "
691                            "names given."));
692                 return 0;
693               }
694
695             v = fx.v[fx.cv++];
696             if (!check_output_specifier (&f->f, true)
697                 || !check_specifier_type (&f->f, v->type, true)
698                 || !check_string_width (&f->f, v))
699               return false;
700
701             fx.spec.type = PRT_VAR;
702             fx.spec.u.v.v = v;
703             fx.spec.u.v.f = f->f;
704             fx.spec.fc = fx.sc - 1;
705             append_var_spec (&fx.spec);
706
707             fx.sc += f->f.w;
708           }
709   return 1;
710 }
711
712 /* Recursively parses a list of FORTRAN-like format specifiers.  Calls
713    itself to parse nested levels of parentheses.  Returns to its
714    original caller NULL, to indicate error, non-NULL, but nothing
715    useful, to indicate success (it returns a free()'d block). */
716 static struct fmt_list *
717 fixed_parse_fortran (void)
718 {
719   struct fmt_list *head = NULL;
720   struct fmt_list *fl = NULL;
721
722   lex_get ();                   /* skip opening parenthesis */
723   while (token != ')')
724     {
725       if (fl)
726         fl = fl->next = xmalloc (sizeof *fl);
727       else
728         head = fl = xmalloc (sizeof *fl);
729
730       if (lex_is_number ())
731         {
732           if (!lex_is_integer ())
733             goto fail;
734           fl->count = lex_integer ();
735           lex_get ();
736         }
737       else
738         fl->count = 1;
739
740       if (token == '(')
741         {
742           fl->f.type = FMT_DESCEND;
743           fx.level++;
744           fl->down = fixed_parse_fortran ();
745           fx.level--;
746           if (!fl->down)
747             goto fail;
748         }
749       else if (lex_match ('/'))
750         fl->f.type = FMT_NEWREC;
751       else if (!parse_format_specifier (&fl->f, FMTP_ALLOW_XT)
752                || !check_output_specifier (&fl->f, 1))
753         goto fail;
754
755       lex_match (',');
756     }
757   fl->next = NULL;
758   lex_get ();
759
760   if (fx.level)
761     return head;
762
763   fl->next = NULL;
764   dump_fmt_list (head);
765   destroy_fmt_list (head, 1);
766   if (fx.cv < fx.nv)
767     {
768       msg (SE, _("There aren't enough format specifications "
769            "to match the number of variable names given."));
770       goto fail;
771     }
772   return head;
773
774 fail:
775   fl->next = NULL;
776   destroy_fmt_list (head, 0);
777
778   return NULL;
779 }
780
781 /* Prints the table produced by the TABLE subcommand to the listing
782    file. */
783 static void
784 dump_table (const struct file_handle *fh)
785 {
786   struct prt_out_spec *spec;
787   struct tab_table *t;
788   int recno;
789   int nspec;
790
791   for (nspec = 0, spec = prt.spec; spec; spec = spec->next)
792     if (spec->type == PRT_CONST || spec->type == PRT_VAR)
793       nspec++;
794   t = tab_create (4, nspec + 1, 0);
795   tab_columns (t, TAB_COL_DOWN, 1);
796   tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, nspec);
797   tab_hline (t, TAL_2, 0, 3, 1);
798   tab_headers (t, 0, 0, 1, 0);
799   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
800   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
801   tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
802   tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
803   tab_dim (t, tab_natural_dimensions);
804   for (nspec = recno = 0, spec = prt.spec; spec; spec = spec->next)
805     switch (spec->type)
806       {
807       case PRT_NEWLINE:
808         recno++;
809         break;
810       case PRT_CONST:
811         {
812           int len = strlen (spec->u.c);
813           nspec++;
814           tab_text (t, 0, nspec, TAB_LEFT | TAT_FIX | TAT_PRINTF,
815                         "\"%s\"", spec->u.c);
816           tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
817           tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
818                         spec->fc + 1, spec->fc + len);
819           tab_text (t, 3, nspec, TAB_LEFT | TAT_FIX | TAT_PRINTF,
820                         "A%d", len);
821           break;
822         }
823       case PRT_VAR:
824         {
825           nspec++;
826           tab_text (t, 0, nspec, TAB_LEFT, spec->u.v.v->name);
827           tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
828           tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
829                         spec->fc + 1, spec->fc + spec->u.v.f.w);
830           tab_text (t, 3, nspec, TAB_LEFT | TAT_FIX,
831                         fmt_to_string (&spec->u.v.f));
832           break;
833         }
834       case PRT_SPACE:
835         break;
836       case PRT_ERROR:
837         assert (0);
838       }
839
840   if (fh != NULL)
841     tab_title (t, 1, _("Writing %d record(s) to file %s."),
842                recno, handle_get_filename (fh));
843   else
844     tab_title (t, 1, _("Writing %d record(s) to the listing file."), recno);
845   tab_submit (t);
846 }
847
848 /* PORTME: The number of characters in a line terminator. */
849 #ifdef __MSDOS__ 
850 #define LINE_END_WIDTH 2        /* \r\n */
851 #else
852 #define LINE_END_WIDTH 1        /* \n */
853 #endif
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           assert (0);
888           abort ();
889         }
890       if (pot_w > w)
891         w = pot_w;
892     }
893   prt.max_width = w + LINE_END_WIDTH + 1;
894   prt.line = xmalloc (prt.max_width);
895 }
896 \f
897 /* Transformation. */
898
899 /* Performs the transformation inside print_trns T on case C. */
900 static int
901 print_trns_proc (struct trns_header * trns, struct ccase * c,
902                  int case_num UNUSED)
903 {
904   /* Transformation. */
905   struct print_trns *t = (struct print_trns *) trns;
906
907   /* Iterator. */
908   struct prt_out_spec *i;
909
910   /* Line buffer. */
911   char *buf = t->line;
912
913   /* Length of the line in buf. */
914   int len = 0;
915   memset (buf, ' ', t->max_width);
916
917   if (t->options & PRT_EJECT)
918     som_eject_page ();
919
920   /* Note that a field written to a place where a field has
921      already been written truncates the record.  `PRINT /A B
922      (T10,F8,T1,F8).' only outputs B.  */
923   for (i = t->spec; i; i = i->next)
924     switch (i->type)
925       {
926       case PRT_NEWLINE:
927         if (t->writer == NULL)
928           {
929             buf[len] = 0;
930             tab_output_text (TAT_FIX | TAT_NOWRAP, buf);
931           }
932         else
933           {
934             if ((t->options & PRT_CMD_MASK) == PRT_PRINT
935                 || !(t->options & PRT_BINARY))
936               {
937                 /* PORTME: Line ends. */
938 #ifdef __MSDOS__
939                 buf[len++] = '\r';
940 #endif
941                 buf[len++] = '\n';
942               }
943
944             dfm_put_record (t->writer, buf, len);
945           }
946
947         memset (buf, ' ', t->max_width);
948         len = 0;
949         break;
950
951       case PRT_CONST:
952         /* FIXME: Should be revised to keep track of the string's
953            length outside the loop, probably in i->u.c[0]. */
954         memcpy (&buf[i->fc], i->u.c, strlen (i->u.c));
955         len = i->fc + strlen (i->u.c);
956         break;
957
958       case PRT_VAR:
959         data_out (&buf[i->fc], &i->u.v.f, case_data (c, i->u.v.v->fv));
960         len = i->fc + i->u.v.f.w;
961         break;
962
963       case PRT_SPACE:
964         /* PRT_SPACE always immediately follows PRT_VAR. */
965         buf[len++] = ' ';
966         break;
967
968       case PRT_ERROR:
969         assert (0);
970         break;
971       }
972
973   return -1;
974 }
975
976 /* Frees all the data inside print_trns T.  Does not free T. */
977 static void
978 print_trns_free (struct trns_header * t)
979 {
980   struct print_trns *prt = (struct print_trns *) t;
981   struct prt_out_spec *i, *n;
982
983   for (i = prt->spec; i; i = n)
984     {
985       switch (i->type)
986         {
987         case PRT_CONST:
988           free (i->u.c);
989           /* fall through */
990         case PRT_NEWLINE:
991         case PRT_VAR:
992         case PRT_SPACE:
993           /* nothing to do */
994           break;
995         case PRT_ERROR:
996           assert (0);
997           break;
998         }
999       n = i->next;
1000       free (i);
1001     }
1002   if (prt->writer != NULL)
1003     dfm_close_writer (prt->writer);
1004   free (prt->line);
1005 }
1006 \f
1007 /* PRINT SPACE. */
1008
1009 /* PRINT SPACE transformation. */
1010 struct print_space_trns
1011 {
1012   struct trns_header h;
1013
1014   struct dfm_writer *writer;    /* Output data file. */
1015   struct expression *e;         /* Number of lines; NULL=1. */
1016 }
1017 print_space_trns;
1018
1019 static trns_proc_func print_space_trns_proc;
1020 static trns_free_func print_space_trns_free;
1021
1022 int
1023 cmd_print_space (void)
1024 {
1025   struct print_space_trns *t;
1026   struct file_handle *fh;
1027   struct expression *e;
1028   struct dfm_writer *writer;
1029
1030   if (lex_match_id ("OUTFILE"))
1031     {
1032       lex_match ('=');
1033
1034       fh = fh_parse ();
1035       if (fh == NULL)
1036         return CMD_FAILURE;
1037       lex_get ();
1038     }
1039   else
1040     fh = NULL;
1041
1042   if (token != '.')
1043     {
1044       e = expr_parse (default_dict, EXPR_NUMBER);
1045       if (token != '.')
1046         {
1047           expr_free (e);
1048           lex_error (_("expecting end of command"));
1049           return CMD_FAILURE;
1050         }
1051     }
1052   else
1053     e = NULL;
1054
1055   if (fh != NULL)
1056     {
1057       writer = dfm_open_writer (fh);
1058       if (writer == NULL) 
1059         {
1060           expr_free (e);
1061           return CMD_FAILURE;
1062         } 
1063     }
1064   else
1065     writer = 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->writer = writer;
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   double n = 1.;
1086
1087   if (t->e)
1088     {
1089       n = expr_evaluate_num (t->e, c, case_num);
1090       if (n == SYSMIS) 
1091         msg (SW, _("The expression on PRINT SPACE evaluated to the "
1092                    "system-missing value."));
1093       else if (n < 0)
1094         msg (SW, _("The expression on PRINT SPACE evaluated to %g."), n);
1095       n = 1.;
1096     }
1097
1098   if (t->writer == NULL)
1099     while (n--)
1100       som_blank_line ();
1101   else
1102     {
1103       char buf[LINE_END_WIDTH];
1104
1105       /* PORTME: Line ends. */
1106 #ifdef __MSDOS__
1107       buf[0] = '\r';
1108       buf[1] = '\n';
1109 #else
1110       buf[0] = '\n';
1111 #endif
1112       while (n--)
1113         dfm_put_record (t->writer, buf, LINE_END_WIDTH);
1114     }
1115
1116   return -1;
1117 }
1118
1119 static void
1120 print_space_trns_free (struct trns_header * trns)
1121 {
1122   expr_free (((struct print_space_trns *) trns)->e);
1123 }