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