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