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