Fix bug #31052: Crash on histograms with only one datum
[pspp] / src / language / dictionary / mrsets.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "data/data-out.h"
20 #include "data/dictionary.h"
21 #include "data/mrset.h"
22 #include "data/procedure.h"
23 #include "data/value-labels.h"
24 #include "data/variable.h"
25 #include "language/command.h"
26 #include "language/lexer/lexer.h"
27 #include "language/lexer/variable-parser.h"
28 #include "libpspp/assertion.h"
29 #include "libpspp/hmap.h"
30 #include "libpspp/message.h"
31 #include "libpspp/str.h"
32 #include "libpspp/stringi-map.h"
33 #include "libpspp/stringi-set.h"
34 #include "output/tab.h"
35
36 #include "gl/xalloc.h"
37
38 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 static bool parse_group (struct lexer *, struct dictionary *, enum mrset_type);
42 static bool parse_delete (struct lexer *, struct dictionary *);
43 static bool parse_display (struct lexer *, struct dictionary *);
44
45 int
46 cmd_mrsets (struct lexer *lexer, struct dataset *ds)
47 {
48   struct dictionary *dict = dataset_dict (ds);
49
50   while (lex_match (lexer, '/'))
51     {
52       bool ok;
53
54       if (lex_match_id (lexer, "MDGROUP"))
55         ok = parse_group (lexer, dict, MRSET_MD);
56       else if (lex_match_id (lexer, "MCGROUP"))
57         ok = parse_group (lexer, dict, MRSET_MC);
58       else if (lex_match_id (lexer, "DELETE"))
59         ok = parse_delete (lexer, dict);
60       else if (lex_match_id (lexer, "DISPLAY"))
61         ok = parse_display (lexer, dict);
62       else
63         {
64           ok = false;
65           lex_error (lexer, NULL);
66         }
67
68       if (!ok)
69         return CMD_FAILURE;
70     }
71
72   return lex_end_of_command (lexer);
73 }
74
75 static bool
76 parse_group (struct lexer *lexer, struct dictionary *dict,
77              enum mrset_type type)
78 {
79   const char *subcommand_name = type == MRSET_MD ? "MDGROUP" : "MCGROUP";
80   struct mrset *mrset;
81   bool labelsource_varlabel;
82   bool has_value;
83
84   mrset = xzalloc (sizeof *mrset);
85   mrset->type = type;
86   mrset->cat_source = MRSET_VARLABELS;
87
88   labelsource_varlabel = false;
89   has_value = false;
90   while (lex_token (lexer) != '/' && lex_token (lexer) != '.')
91     {
92       if (lex_match_id (lexer, "NAME"))
93         {
94           if (!lex_force_match (lexer, '=') || !lex_force_id (lexer))
95             goto error;
96           if (lex_tokid (lexer)[0] != '$')
97             {
98               msg (SE, _("%s is not a valid name for a multiple response "
99                          "set.  Multiple response set names must begin with "
100                          "`$'."), lex_tokid (lexer));
101               goto error;
102             }
103
104           free (mrset->name);
105           mrset->name = xstrdup (lex_tokid (lexer));
106           lex_get (lexer);
107         }
108       else if (lex_match_id (lexer, "VARIABLES"))
109         {
110           if (!lex_force_match (lexer, '='))
111             goto error;
112
113           free (mrset->vars);
114           if (!parse_variables (lexer, dict, &mrset->vars, &mrset->n_vars,
115                                 PV_SAME_TYPE | PV_NO_SCRATCH))
116             goto error;
117
118           if (mrset->n_vars < 2)
119             {
120               msg (SE, _("VARIABLES specified only variable %s on %s, but "
121                          "at least two variables are required."),
122                    var_get_name (mrset->vars[0]), subcommand_name);
123               goto error;
124             }
125         }
126       else if (lex_match_id (lexer, "LABEL"))
127         {
128           if (!lex_force_match (lexer, '=') || !lex_force_string (lexer))
129             goto error;
130
131           free (mrset->label);
132           mrset->label = ds_xstrdup (lex_tokstr (lexer));
133           lex_get (lexer);
134         }
135       else if (type == MRSET_MD && lex_match_id (lexer, "LABELSOURCE"))
136         {
137           if (!lex_force_match (lexer, '=')
138               || !lex_force_match_id (lexer, "VARLABEL"))
139             goto error;
140
141           labelsource_varlabel = true;
142         }
143       else if (type == MRSET_MD && lex_match_id (lexer, "VALUE"))
144         {
145           if (!lex_force_match (lexer, '='))
146             goto error;
147
148           has_value = true;
149           if (lex_is_number (lexer))
150             {
151               if (!lex_is_integer (lexer))
152                 {
153                   msg (SE, _("Numeric VALUE must be an integer."));
154                   goto error;
155                 }
156               value_destroy (&mrset->counted, mrset->width);
157               mrset->counted.f = lex_integer (lexer);
158               mrset->width = 0;
159             }
160           else if (lex_is_string (lexer))
161             {
162               const char *s = ds_cstr (lex_tokstr (lexer));
163               int width;
164
165               /* Trim off trailing spaces, but don't trim the string until
166                  it's empty because a width of 0 is a numeric type. */
167               width = strlen (s);
168               while (width > 1 && s[width - 1] == ' ')
169                 width--;
170
171               value_destroy (&mrset->counted, mrset->width);
172               value_init (&mrset->counted, width);
173               memcpy (value_str_rw (&mrset->counted, width), s, width);
174               mrset->width = width;
175             }
176           else
177             {
178               lex_error (lexer, NULL);
179               goto error;
180             }
181           lex_get (lexer);
182         }
183       else if (type == MRSET_MD && lex_match_id (lexer, "CATEGORYLABELS"))
184         {
185           if (!lex_force_match (lexer, '='))
186             goto error;
187
188           if (lex_match_id (lexer, "VARLABELS"))
189             mrset->cat_source = MRSET_VARLABELS;
190           else if (lex_match_id (lexer, "COUNTEDVALUES"))
191             mrset->cat_source = MRSET_COUNTEDVALUES;
192           else
193             {
194               lex_error (lexer, NULL);
195               goto error;
196             }
197         }
198       else
199         {
200           lex_error (lexer, NULL);
201           goto error;
202         }
203     }
204
205   if (mrset->name == NULL)
206     {
207       msg (SE, _("Required %s specification missing from %s subcommand."),
208            "NAME", subcommand_name);
209       goto error;
210     }
211   else if (mrset->n_vars == 0)
212     {
213       msg (SE, _("Required %s specification missing from %s subcommand."),
214            "VARIABLES", subcommand_name);
215       goto error;
216     }
217
218   if (type == MRSET_MD)
219     {
220       /* Check that VALUE is specified and is valid for the VARIABLES. */
221       if (!has_value)
222         {
223           msg (SE, _("Required %s specification missing from %s subcommand."),
224                "VALUE", subcommand_name);
225           goto error;
226         }
227       else if (var_is_alpha (mrset->vars[0]))
228         {
229           if (mrset->width == 0)
230             {
231               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
232                          "VALUE, but the variables specified for this group "
233                          "are numeric."),
234                    mrset->name);
235               goto error;
236             }
237           else {
238             const struct variable *shortest_var;
239             int min_width;
240             size_t i;
241
242             shortest_var = NULL;
243             min_width = INT_MAX;
244             for (i = 0; i < mrset->n_vars; i++)
245               {
246                 int width = var_get_width (mrset->vars[i]);
247                 if (width < min_width)
248                   {
249                     shortest_var = mrset->vars[i];
250                     min_width = width;
251                   }
252               }
253             if (mrset->width > min_width)
254               {
255                 msg (SE, _("VALUE string on MDGROUP subcommand for group "
256                            "%s is %d bytes long, but it must be no longer "
257                            "than the narrowest variable in the group, "
258                            "which is %s with a width of %d bytes."),
259                      mrset->name, mrset->width,
260                      var_get_name (shortest_var), min_width);
261                 goto error;
262               }
263           }
264         }
265       else
266         {
267           if (mrset->width != 0)
268             {
269               msg (SE, _("MDGROUP subcommand for group %s specifies a string "
270                          "VALUE, but the variables specified for this group "
271                          "are numeric."),
272                    mrset->name);
273               goto error;
274             }
275         }
276
277       /* Implement LABELSOURCE=VARLABEL. */
278       if (labelsource_varlabel)
279         {
280           if (mrset->cat_source != MRSET_COUNTEDVALUES)
281             msg (SW, _("MDGROUP subcommand for group %s specifies "
282                        "LABELSOURCE=VARLABEL but not "
283                        "CATEGORYLABELS=COUNTEDVALUES.  "
284                        "Ignoring LABELSOURCE."),
285                  mrset->name);
286           else if (mrset->label)
287             msg (SW, _("MDGROUP subcommand for group %s specifies both LABEL "
288                        "and LABELSOURCE, but only one of these subcommands "
289                        "may be used at a time.  Ignoring LABELSOURCE."),
290                  mrset->name);
291           else
292             {
293               size_t i;
294
295               mrset->label_from_var_label = true;
296               for (i = 0; mrset->label == NULL && i < mrset->n_vars; i++)
297                 {
298                   const char *label = var_get_label (mrset->vars[i]);
299                   if (label != NULL)
300                     {
301                       mrset->label = xstrdup (label);
302                       break;
303                     }
304                 }
305             }
306         }
307
308       /* Warn if categories cannot be distinguished in output. */
309       if (mrset->cat_source == MRSET_VARLABELS)
310         {
311           struct stringi_map seen;
312           size_t i;
313
314           stringi_map_init (&seen);
315           for (i = 0; i < mrset->n_vars; i++)
316             {
317               const struct variable *var = mrset->vars[i];
318               const char *name = var_get_name (var);
319               const char *label = var_get_label (var);
320               if (label != NULL)
321                 {
322                   const char *other_name = stringi_map_find (&seen, label);
323
324                   if (other_name == NULL)
325                     stringi_map_insert (&seen, label, name);
326                   else
327                     msg (SW, _("Variables %s and %s specified as part of "
328                                "multiple dichotomy group %s have the same "
329                                "variable label.  Categories represented by "
330                                "these variables will not be distinguishable "
331                                "in output."),
332                          other_name, name, mrset->name);
333                 }
334             }
335           stringi_map_destroy (&seen);
336         }
337       else
338         {
339           struct stringi_map seen;
340           size_t i;
341
342           stringi_map_init (&seen);
343           for (i = 0; i < mrset->n_vars; i++)
344             {
345               const struct variable *var = mrset->vars[i];
346               const char *name = var_get_name (var);
347               const struct val_labs *val_labs;
348               union value value;
349               const char *label;
350
351               value_clone (&value, &mrset->counted, mrset->width);
352               value_resize (&value, mrset->width, var_get_width (var));
353
354               val_labs = var_get_value_labels (var);
355               label = val_labs_find (val_labs, &value);
356               if (label == NULL)
357                 msg (SW, _("Variable %s specified as part of multiple "
358                            "dichotomy group %s (which has "
359                            "CATEGORYLABELS=COUNTEDVALUES) has no value label "
360                            "for its counted value.  This category will not "
361                            "be distinguishable in output."),
362                      name, mrset->name);
363               else
364                 {
365                   const char *other_name = stringi_map_find (&seen, label);
366
367                   if (other_name == NULL)
368                     stringi_map_insert (&seen, label, name);
369                   else
370                     msg (SW, _("Variables %s and %s specified as part of "
371                                "multiple dichotomy group %s (which has "
372                                "CATEGORYLABELS=COUNTEDVALUES) have the same "
373                                "value label for the the group's counted "
374                                "value.  These categories will not be "
375                                "distinguishable in output."),
376                          other_name, name, mrset->name);
377                 }
378             }
379           stringi_map_destroy (&seen);
380         }
381     }
382   else                          /* MCGROUP. */
383     {
384       /* Warn if categories cannot be distinguished in output. */
385       struct category
386         {
387           struct hmap_node hmap_node;
388           union value value;
389           int width;
390           const char *label;
391           const char *var_name;
392           bool warned;
393         };
394
395       struct category *c, *next;
396       struct hmap categories;
397       size_t i;
398
399       hmap_init (&categories);
400       for (i = 0; i < mrset->n_vars; i++)
401         {
402           const struct variable *var = mrset->vars[i];
403           const char *name = var_get_name (var);
404           int width = var_get_width (var);
405           const struct val_labs *val_labs;
406           const struct val_lab *vl;
407
408           val_labs = var_get_value_labels (var);
409           for (vl = val_labs_first (val_labs); vl != NULL;
410                vl = val_labs_next (val_labs, vl))
411             {
412               const union value *value = val_lab_get_value (vl);
413               const char *label = val_lab_get_label (vl);
414               unsigned int hash = value_hash (value, width, 0);
415
416               HMAP_FOR_EACH_WITH_HASH (c, struct category, hmap_node,
417                                        hash, &categories)
418                 {
419                   if (width == c->width
420                       && value_equal (value, &c->value, width))
421                     {
422                       if (!c->warned && strcasecmp (c->label, label))
423                         {
424                           char *s = data_out (value, var_get_encoding (var),
425                                               var_get_print_format (var));
426                           c->warned = true;
427                           msg (SW, _("Variables specified on MCGROUP should "
428                                      "have the same categories, but %s and %s "
429                                      "(and possibly others) in multiple "
430                                      "category group %s have different "
431                                      "value labels for value %s."),
432                                c->var_name, name, mrset->name, s);
433                           free (s);
434                         }
435                       goto found;
436                     }
437                 }
438
439               c = xmalloc (sizeof *c);
440               value_clone (&c->value, value, width);
441               c->width = width;
442               c->label = label;
443               c->var_name = name;
444               c->warned = false;
445               hmap_insert (&categories, &c->hmap_node, hash);
446
447             found: ;
448             }
449         }
450
451       HMAP_FOR_EACH_SAFE (c, next, struct category, hmap_node, &categories)
452         {
453           value_destroy (&c->value, c->width);
454           hmap_delete (&categories, &c->hmap_node);
455           free (c);
456         }
457       hmap_destroy (&categories);
458     }
459
460   dict_add_mrset (dict, mrset);
461   return true;
462
463 error:
464   mrset_destroy (mrset);
465   return false;
466 }
467
468 static bool
469 parse_mrset_names (struct lexer *lexer, struct dictionary *dict,
470                    struct stringi_set *mrset_names)
471 {
472   if (!lex_force_match_id (lexer, "NAME") || !lex_force_match (lexer, '='))
473     return false;
474
475   stringi_set_init (mrset_names);
476   if (lex_match (lexer, '['))
477     {
478       while (!lex_match (lexer, ']'))
479         {
480           if (!lex_force_id (lexer))
481             return false;
482           if (dict_lookup_mrset (dict, lex_tokid (lexer)) == NULL)
483             {
484               msg (SE, _("No multiple response set named %s."),
485                    lex_tokid (lexer));
486               stringi_set_destroy (mrset_names);
487               return false;
488             }
489           stringi_set_insert (mrset_names, lex_tokid (lexer));
490           lex_get (lexer);
491         }
492     }
493   else if (lex_match (lexer, T_ALL))
494     {
495       size_t n_sets = dict_get_n_mrsets (dict);
496       size_t i;
497
498       for (i = 0; i < n_sets; i++)
499         stringi_set_insert (mrset_names, dict_get_mrset (dict, i)->name);
500     }
501
502   return true;
503 }
504
505 static bool
506 parse_delete (struct lexer *lexer, struct dictionary *dict)
507 {
508   const struct stringi_set_node *node;
509   struct stringi_set mrset_names;
510   const char *name;
511
512   if (!parse_mrset_names (lexer, dict, &mrset_names))
513     return false;
514
515   STRINGI_SET_FOR_EACH (name, node, &mrset_names)
516     dict_delete_mrset (dict, name);
517   stringi_set_destroy (&mrset_names);
518
519   return true;
520 }
521
522 static bool
523 parse_display (struct lexer *lexer, struct dictionary *dict)
524 {
525   struct string details, var_names;
526   struct stringi_set mrset_names_set;
527   char **mrset_names;
528   struct tab_table *table;
529   size_t i, n;
530
531   if (!parse_mrset_names (lexer, dict, &mrset_names_set))
532     return false;
533
534   n = stringi_set_count (&mrset_names_set);
535   if (n == 0)
536     {
537       if (dict_get_n_mrsets (dict) == 0)
538         msg (SN, _("The active file dictionary does not contain any multiple "
539                    "response sets."));
540       stringi_set_destroy (&mrset_names_set);
541       return true;
542     }
543
544   table = tab_create (3, n + 1);
545   tab_headers (table, 0, 0, 1, 0);
546   tab_box (table, TAL_1, TAL_1, TAL_1, TAL_1, 0, 0, 2, n);
547   tab_hline (table, TAL_2, 0, 2, 1);
548   tab_title (table, "%s", _("Multiple Response Sets"));
549   tab_text (table, 0, 0, TAB_EMPH | TAB_LEFT, _("Name"));
550   tab_text (table, 1, 0, TAB_EMPH | TAB_LEFT, _("Variables"));
551   tab_text (table, 2, 0, TAB_EMPH | TAB_LEFT, _("Details"));
552
553   ds_init_empty (&details);
554   ds_init_empty (&var_names);
555   mrset_names = stringi_set_get_sorted_array (&mrset_names_set);
556   for (i = 0; i < n; i++)
557     {
558       const struct mrset *mrset = dict_lookup_mrset (dict, mrset_names[i]);
559       const int row = i + 1;
560       size_t j;
561
562       /* Details. */
563       ds_clear (&details);
564       ds_put_format (&details, "%s\n", (mrset->type == MRSET_MD
565                                         ? _("Multiple dichotomy set")
566                                         : _("Multiple category set")));
567       if (mrset->label != NULL)
568         ds_put_format (&details, "%s: %s\n", _("Label"), mrset->label);
569       if (mrset->type == MRSET_MD)
570         {
571           if (mrset->label != NULL || mrset->label_from_var_label)
572             ds_put_format (&details, "%s: %s\n", _("Label source"),
573                            (mrset->label_from_var_label
574                             ? _("First variable label among variables")
575                             : _("Provided by user")));
576           ds_put_format (&details, "%s: ", _("Counted value"));
577           if (mrset->width == 0)
578             ds_put_format (&details, "%.0f\n", mrset->counted.f);
579           else
580             ds_put_format (&details, "`%.*s'\n", mrset->width,
581                            value_str (&mrset->counted, mrset->width));
582           ds_put_format (&details, "%s: %s\n", _("Category label source"),
583                          (mrset->cat_source == MRSET_VARLABELS
584                           ? _("Variable labels")
585                           : _("Value labels of counted value")));
586         }
587
588       /* Variable names. */
589       ds_clear (&var_names);
590       for (j = 0; j < mrset->n_vars; j++)
591         ds_put_format (&var_names, "%s\n", var_get_name (mrset->vars[j]));
592
593       tab_text (table, 0, row, TAB_LEFT, mrset_names[i]);
594       tab_text (table, 1, row, TAB_LEFT, ds_cstr (&var_names));
595       tab_text (table, 2, row, TAB_LEFT, ds_cstr (&details));
596     }
597   free (mrset_names);
598   ds_destroy (&var_names);
599   ds_destroy (&details);
600   stringi_set_destroy (&mrset_names_set);
601
602   tab_submit (table);
603
604   return true;
605 }