Fix up potential overflows in size calculations by replacing instances
[pspp-builds.git] / src / list.q
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 #include <config.h>
21 #include "error.h"
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include "alloc.h"
25 #include "case.h"
26 #include "command.h"
27 #include "devind.h"
28 #include "dictionary.h"
29 #include "lexer.h"
30 #include "error.h"
31 #include "magic.h"
32 #include "misc.h"
33 #include "htmlP.h"
34 #include "output.h"
35 #include "size_max.h"
36 #include "som.h"
37 #include "tab.h"
38 #include "var.h"
39 #include "vfm.h"
40 #include "format.h"
41
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44
45 /* (headers) */
46
47 #include "debug-print.h"
48
49 /* (specification)
50    list (lst_):
51      *variables=varlist("PV_NO_SCRATCH");
52      cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
53      format=numbering:numbered/!unnumbered,
54             wrap:!wrap/single,
55             weight:weight/!noweight.
56 */
57 /* (declarations) */
58 /* (functions) */
59
60 /* Layout for one output driver. */
61 struct list_ext
62   {
63     int type;           /* 0=Values and labels fit across the page. */
64     size_t n_vertical;  /* Number of labels to list vertically. */
65     size_t header_rows; /* Number of header rows. */
66     char **header;      /* The header itself. */
67   };
68
69 /* Parsed command. */
70 static struct cmd_list cmd;
71
72 /* Current case number. */
73 static int case_idx;
74
75 /* Line buffer. */
76 static char *line_buf;
77
78 /* TTY-style output functions. */
79 static unsigned n_lines_remaining (struct outp_driver *d);
80 static unsigned n_chars_width (struct outp_driver *d);
81 static void write_line (struct outp_driver *d, char *s);
82
83 /* Other functions. */
84 static int list_cases (struct ccase *, void *);
85 static void determine_layout (void);
86 static void clean_up (void);
87 static void write_header (struct outp_driver *);
88 static void write_all_headers (void *);
89
90 /* Returns the number of text lines that can fit on the remainder of
91    the page. */
92 static inline unsigned
93 n_lines_remaining (struct outp_driver *d)
94 {
95   int diff;
96
97   diff = d->length - d->cp_y;
98   return (diff > 0) ? (diff / d->font_height) : 0;
99 }
100
101 /* Returns the number of fixed-width character that can fit across the
102    page. */
103 static inline unsigned
104 n_chars_width (struct outp_driver *d)
105 {
106   return d->width / d->fixed_width;
107 }
108
109 /* Writes the line S at the current position and advances to the next
110    line.  */
111 static void
112 write_line (struct outp_driver *d, char *s)
113 {
114   struct outp_text text;
115   
116   assert (d->cp_y + d->font_height <= d->length);
117   text.options = OUTP_T_JUST_LEFT;
118   ls_init (&text.s, s, strlen (s));
119   text.x = d->cp_x;
120   text.y = d->cp_y;
121   d->class->text_draw (d, &text);
122   d->cp_x = 0;
123   d->cp_y += d->font_height;
124 }
125     
126 /* Parses and executes the LIST procedure. */
127 int
128 cmd_list (void)
129 {
130   struct variable casenum_var;
131
132   if (!parse_list (&cmd))
133     return CMD_FAILURE;
134   
135   /* Fill in defaults. */
136   if (cmd.step == NOT_LONG)
137     cmd.step = 1;
138   if (cmd.first == NOT_LONG)
139     cmd.first = 1;
140   if (cmd.last == NOT_LONG)
141     cmd.last = LONG_MAX;
142   if (!cmd.sbc_variables)
143     dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
144                    (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
145   if (cmd.n_variables == 0)
146     {
147       msg (SE, _("No variables specified."));
148       return CMD_FAILURE;
149     }
150
151   /* Verify arguments. */
152   if (cmd.first > cmd.last)
153     {
154       int t;
155       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
156            "specified.  The values will be swapped."), cmd.first, cmd.last);
157       t = cmd.first;
158       cmd.first = cmd.last;
159       cmd.last = t;
160     }
161   if (cmd.first < 1)
162     {
163       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
164            "being reset to 1."), cmd.first);
165       cmd.first = 1;
166     }
167   if (cmd.last < 1)
168     {
169       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
170            "being reset to 1."), cmd.last);
171       cmd.last = 1;
172     }
173   if (cmd.step < 1)
174     {
175       msg (SW, _("The step value %ld is less than 1.  The value is being "
176            "reset to 1."), cmd.step);
177       cmd.step = 1;
178     }
179
180   /* Weighting variable. */
181   if (cmd.weight == LST_WEIGHT)
182     {
183       if (dict_get_weight (default_dict) != NULL)
184         {
185           size_t i;
186
187           for (i = 0; i < cmd.n_variables; i++)
188             if (cmd.v_variables[i] == dict_get_weight (default_dict))
189               break;
190           if (i >= cmd.n_variables)
191             {
192               /* Add the weight variable to the end of the variable list. */
193               cmd.n_variables++;
194               cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
195                                            sizeof *cmd.v_variables);
196               cmd.v_variables[cmd.n_variables - 1]
197                 = dict_get_weight (default_dict);
198             }
199         }
200       else
201         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
202     }
203
204   /* Case number. */
205   if (cmd.numbering == LST_NUMBERED)
206     {
207       /* Initialize the case-number variable. */
208       strcpy (casenum_var.name, "Case#");
209       casenum_var.type = NUMERIC;
210       casenum_var.fv = -1;
211       casenum_var.print = make_output_format (FMT_F,
212                                               (cmd.last == LONG_MAX
213                                                ? 5 : intlog10 (cmd.last)), 0);
214
215       /* Add the weight variable at the beginning of the variable list. */
216       cmd.n_variables++;
217       cmd.v_variables = xnrealloc (cmd.v_variables,
218                                    cmd.n_variables, sizeof *cmd.v_variables);
219       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
220                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
221       cmd.v_variables[0] = &casenum_var;
222     }
223
224   determine_layout ();
225
226   case_idx = 0;
227   procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
228   free (line_buf);
229
230   clean_up ();
231
232   return CMD_SUCCESS;
233 }
234
235 /* Writes headers to all devices.  This is done at the beginning of
236    each SPLIT FILE group. */
237 static void
238 write_all_headers (void *aux UNUSED)
239 {
240   struct outp_driver *d;
241
242   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
243     {
244       if (!d->class->special)
245         {
246           d->cp_y += d->font_height;            /* Blank line. */
247           write_header (d);
248         }
249       else if (d->class == &html_class)
250         {
251           struct html_driver_ext *x = d->ext;
252   
253           assert (d->driver_open);
254           if (x->sequence_no == 0 && !d->class->open_page (d))
255             {
256               msg (ME, _("Cannot open first page on HTML device %s."),
257                    d->name);
258               return;
259             }
260
261           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file.file);
262           
263           {
264             size_t i;
265
266             for (i = 0; i < cmd.n_variables; i++)
267               fprintf (x->file.file, "    <TH><I><B>%s</B></I></TH>\n",
268                        cmd.v_variables[i]->name);
269           }
270
271           fputs ("  <TR>\n", x->file.file);
272         }
273       else if (d->class == &devind_class) 
274         {
275           /* FIXME */
276         }
277       else
278         assert (0);
279     }
280 }
281
282 /* Writes the headers.  Some of them might be vertical; most are
283    probably horizontal. */
284 static void
285 write_header (struct outp_driver *d)
286 {
287   struct list_ext *prc = d->prc;
288
289   if (!prc->header_rows)
290     return;
291   
292   if (n_lines_remaining (d) < prc->header_rows + 1)
293     {
294       outp_eject_page (d);
295       assert (n_lines_remaining (d) >= prc->header_rows + 1);
296     }
297
298   /* Design the header. */
299   if (!prc->header)
300     {
301       size_t i;
302       size_t x;
303       
304       /* Allocate, initialize header. */
305       prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
306       {
307         int w = n_chars_width (d);
308         for (i = 0; i < prc->header_rows; i++)
309           {
310             prc->header[i] = xmalloc (w + 1);
311             memset (prc->header[i], ' ', w);
312           }
313       }
314
315       /* Put in vertical names. */
316       for (i = x = 0; i < prc->n_vertical; i++)
317         {
318           struct variable *v = cmd.v_variables[i];
319           size_t j;
320
321           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
322           x += v->print.w - 1;
323           for (j = 0; j < strlen (v->name); j++)
324             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
325           x += 2;
326         }
327
328       /* Put in horizontal names. */
329       for (; i < cmd.n_variables; i++)
330         {
331           struct variable *v = cmd.v_variables[i];
332           
333           memset (&prc->header[prc->header_rows - 1][x], '-',
334                   max (v->print.w, (int) strlen (v->name)));
335           if ((int) strlen (v->name) < v->print.w)
336             x += v->print.w - strlen (v->name);
337           memcpy (&prc->header[0][x], v->name, strlen (v->name));
338           x += strlen (v->name) + 1;
339         }
340
341       /* Add null bytes. */
342       for (i = 0; i < prc->header_rows; i++)
343         {
344           for (x = n_chars_width (d); x >= 1; x--)
345             if (prc->header[i][x - 1] != ' ')
346               {
347                 prc->header[i][x] = 0;
348                 break;
349               }
350           assert (x);
351         }
352     }
353
354   /* Write out the header, in back-to-front order except for the last line. */
355   if (prc->header_rows >= 2) 
356     {
357       size_t i;
358         
359       for (i = prc->header_rows - 1; i-- != 0; )
360         write_line (d, prc->header[i]); 
361     }
362   write_line (d, prc->header[prc->header_rows - 1]);
363 }
364       
365   
366 /* Frees up all the memory we've allocated. */
367 static void
368 clean_up (void)
369 {
370   struct outp_driver *d;
371   
372   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
373     if (d->class->special == 0)
374       {
375         struct list_ext *prc = d->prc;
376         size_t i;
377
378         if (prc->header)
379           {
380             for (i = 0; i < prc->header_rows; i++)
381               free (prc->header[i]);
382             free (prc->header);
383           }
384         free (prc);
385       
386         d->class->text_set_font_by_name (d, "PROP");
387       }
388     else if (d->class == &html_class)
389       {
390         if (d->driver_open && d->page_open)
391           {
392             struct html_driver_ext *x = d->ext;
393
394             fputs ("</TABLE>\n", x->file.file);
395           }
396       }
397     else if (d->class == &devind_class) 
398       {
399         /* FIXME */
400       }
401     else
402       assert (0);
403   
404   free (cmd.v_variables);
405 }
406
407 /* Writes string STRING at the current position.  If the text would
408    fall off the side of the page, then advance to the next line,
409    indenting by amount INDENT. */
410 static void
411 write_varname (struct outp_driver *d, char *string, int indent)
412 {
413   struct outp_text text;
414
415   text.options = OUTP_T_JUST_LEFT;
416   ls_init (&text.s, string, strlen (string));
417   d->class->text_metrics (d, &text);
418   
419   if (d->cp_x + text.h > d->width)
420     {
421       d->cp_y += d->font_height;
422       if (d->cp_y + d->font_height > d->length)
423         outp_eject_page (d);
424       d->cp_x = indent;
425     }
426
427   text.x = d->cp_x;
428   text.y = d->cp_y;
429   d->class->text_draw (d, &text);
430   d->cp_x += text.h;
431 }
432
433 /* When we can't fit all the values across the page, we write out all
434    the variable names just once.  This is where we do it. */
435 static void
436 write_fallback_headers (struct outp_driver *d)
437 {
438   const int max_width = n_chars_width(d) - 10;
439   
440   int index = 0;
441   int width = 0;
442   int line_number = 0;
443
444   const char *Line = _("Line");
445   char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
446       
447   while (index < cmd.n_variables)
448     {
449       struct outp_text text;
450
451       /* Ensure that there is enough room for a line of text. */
452       if (d->cp_y + d->font_height > d->length)
453         outp_eject_page (d);
454       
455       /* The leader is a string like `Line 1: '.  Write the leader. */
456       sprintf(leader, "%s %d:", Line, ++line_number);
457       text.options = OUTP_T_JUST_LEFT;
458       ls_init (&text.s, leader, strlen (leader));
459       text.x = 0;
460       text.y = d->cp_y;
461       d->class->text_draw (d, &text);
462       d->cp_x = text.h;
463
464       goto entry;
465       do
466         {
467           width++;
468
469         entry:
470           {
471             int var_width = cmd.v_variables[index]->print.w;
472             if (width + var_width > max_width && width != 0)
473               {
474                 width = 0;
475                 d->cp_x = 0;
476                 d->cp_y += d->font_height;
477                 break;
478               }
479             width += var_width;
480           }
481           
482           {
483             char varname[10];
484             sprintf (varname, " %s", cmd.v_variables[index]->name);
485             write_varname (d, varname, text.h);
486           }
487         }
488       while (++index < cmd.n_variables);
489
490     }
491   d->cp_x = 0;
492   d->cp_y += d->font_height;
493   
494   local_free (leader);
495 }
496
497 /* There are three possible layouts for the LIST procedure:
498
499    1. If the values and their variables' name fit across the page,
500    then they are listed across the page in that way.
501
502    2. If the values can fit across the page, but not the variable
503    names, then as many variable names as necessary are printed
504    vertically to compensate.
505
506    3. If not even the values can fit across the page, the variable
507    names are listed just once, at the beginning, in a compact format,
508    and the values are listed with a variable name label at the
509    beginning of each line for easier reference.
510
511    This is complicated by the fact that we have to do all this for
512    every output driver, not just once.  */
513 static void
514 determine_layout (void)
515 {
516   struct outp_driver *d;
517   
518   /* This is the largest page width of any driver, so we can tell what
519      size buffer to allocate. */
520   int largest_page_width = 0;
521   
522   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
523     {
524       size_t column;    /* Current column. */
525       int width;        /* Accumulated width. */
526       int height;       /* Height of vertical names. */
527       int max_width;    /* Page width. */
528
529       struct list_ext *prc;
530
531       if (d->class == &html_class)
532         continue;
533       else if (d->class == &devind_class) 
534         {
535           /* FIXME */
536           tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
537           continue;
538         }
539       
540       assert (d->class->special == 0);
541
542       if (!d->page_open)
543         d->class->open_page (d);
544       
545       max_width = n_chars_width (d);
546       largest_page_width = max (largest_page_width, max_width);
547
548       prc = d->prc = xmalloc (sizeof *prc);
549       prc->type = 0;
550       prc->n_vertical = 0;
551       prc->header = NULL;
552
553       /* Try layout #1. */
554       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
555         {
556           struct variable *v = cmd.v_variables[column];
557           width += max (v->print.w, (int) strlen (v->name));
558         }
559       if (width <= max_width)
560         {
561           prc->header_rows = 2;
562           d->class->text_set_font_by_name (d, "FIXED");
563           continue;
564         }
565
566       /* Try layout #2. */
567       for (width = cmd.n_variables - 1, height = 0, column = 0;
568            column < cmd.n_variables && width <= max_width;
569            column++) 
570         {
571           struct variable *v = cmd.v_variables[column];
572           width += v->print.w;
573           if (strlen (v->name) > height)
574             height = strlen (v->name);
575         }
576       
577       /* If it fit then we need to determine how many labels can be
578          written horizontally. */
579       if (width <= max_width && height <= SHORT_NAME_LEN)
580         {
581 #ifndef NDEBUG
582           prc->n_vertical = SIZE_MAX;
583 #endif
584           for (column = cmd.n_variables; column-- != 0; )
585             {
586               struct variable *v = cmd.v_variables[column];
587               int trial_width = (width - v->print.w
588                                  + max (v->print.w, (int) strlen (v->name)));
589               
590               if (trial_width > max_width)
591                 {
592                   prc->n_vertical = column + 1;
593                   break;
594                 }
595               width = trial_width;
596             }
597           assert (prc->n_vertical != SIZE_MAX);
598
599           prc->n_vertical = cmd.n_variables;
600           /* Finally determine the length of the headers. */
601           for (prc->header_rows = 0, column = 0;
602                column < prc->n_vertical;
603                column++)
604             prc->header_rows = max (prc->header_rows,
605                                     strlen (cmd.v_variables[column]->name));
606           prc->header_rows++;
607
608           d->class->text_set_font_by_name (d, "FIXED");
609           continue;
610         }
611
612       /* Otherwise use the ugly fallback listing format. */
613       prc->type = 1;
614       prc->header_rows = 0;
615
616       d->cp_y += d->font_height;
617       write_fallback_headers (d);
618       d->cp_y += d->font_height;
619       d->class->text_set_font_by_name (d, "FIXED");
620     }
621
622   line_buf = xmalloc (max (1022, largest_page_width) + 2);
623 }
624
625 static int
626 list_cases (struct ccase *c, void *aux UNUSED)
627 {
628   struct outp_driver *d;
629   
630   case_idx++;
631   if (case_idx < cmd.first || case_idx > cmd.last
632       || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
633     return 1;
634
635   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
636     if (d->class->special == 0)
637       {
638         const struct list_ext *prc = d->prc;
639         const int max_width = n_chars_width (d);
640         int column;
641         int x = 0;
642
643         if (!prc->header_rows)
644           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
645       
646         for (column = 0; column < cmd.n_variables; column++)
647           {
648             struct variable *v = cmd.v_variables[column];
649             int width;
650
651             if (prc->type == 0 && column >= prc->n_vertical)
652               width = max ((int) strlen (v->name), v->print.w);
653             else
654               width = v->print.w;
655
656             if (width + x > max_width && x != 0)
657               {
658                 if (!n_lines_remaining (d))
659                   {
660                     outp_eject_page (d);
661                     write_header (d);
662                   }
663               
664                 line_buf[x] = 0;
665                 write_line (d, line_buf);
666
667                 x = 0;
668                 if (!prc->header_rows)
669                   x = nsprintf (line_buf, "%8s: ", v->name);
670               }
671
672             if (width > v->print.w)
673               {
674                 memset(&line_buf[x], ' ', width - v->print.w);
675                 x += width - v->print.w;
676               }
677
678             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
679               data_out (&line_buf[x], &v->print, case_data (c, v->fv));
680             else 
681               {
682                 union value case_idx_value;
683                 case_idx_value.f = case_idx;
684                 data_out (&line_buf[x], &v->print, &case_idx_value); 
685               }
686             x += v->print.w;
687           
688             line_buf[x++] = ' ';
689           }
690       
691         if (!n_lines_remaining (d))
692           {
693             outp_eject_page (d);
694             write_header (d);
695           }
696               
697         line_buf[x] = 0;
698         write_line (d, line_buf);
699       }
700     else if (d->class == &html_class)
701       {
702         struct html_driver_ext *x = d->ext;
703         int column;
704
705         fputs ("  <TR>\n", x->file.file);
706         
707         for (column = 0; column < cmd.n_variables; column++)
708           {
709             struct variable *v = cmd.v_variables[column];
710             char buf[41];
711             
712             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
713               data_out (buf, &v->print, case_data (c, v->fv));
714             else 
715               {
716                 union value case_idx_value;
717                 case_idx_value.f = case_idx;
718                 data_out (buf, &v->print, &case_idx_value); 
719               }
720             buf[v->print.w] = 0;
721
722             fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
723                      &buf[strspn (buf, " ")]);
724           }
725           
726         fputs ("  </TR>\n", x->file.file);
727       }
728     else if (d->class == &devind_class) 
729       {
730         /* FIXME */
731       }
732     else
733       assert (0);
734
735   return 1;
736 }
737
738 /* 
739    Local Variables:
740    mode: c
741    End:
742 */