c775f49784eab659d715cc0bb2408a7c20ad21aa
[pspp-builds.git] / 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, T_SLASH))
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) != T_SLASH && lex_token (lexer) != T_ENDCMD)
91     {
92       if (lex_match_id (lexer, "NAME"))
93         {
94           if (!lex_force_match (lexer, T_EQUALS) || !lex_force_id (lexer))
95             goto error;
96           if (lex_tokcstr (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_tokcstr (lexer));
101               goto error;
102             }
103
104           free (mrset->name);
105           mrset->name = xstrdup (lex_tokcstr (lexer));
106           lex_get (lexer);
107         }
108       else if (lex_match_id (lexer, "VARIABLES"))
109         {
110           if (!lex_force_match (lexer, T_EQUALS))
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, T_EQUALS) || !lex_force_string (lexer))
129             goto error;
130
131           free (mrset->label);
132           mrset->label = ss_xstrdup (lex_tokss (lexer));
133           lex_get (lexer);
134         }
135       else if (type == MRSET_MD && lex_match_id (lexer, "LABELSOURCE"))
136         {
137           if (!lex_force_match (lexer, T_EQUALS)
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, T_EQUALS))
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 = lex_tokcstr (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, T_EQUALS))
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")
473       || !lex_force_match (lexer, T_EQUALS))
474     return false;
475
476   stringi_set_init (mrset_names);
477   if (lex_match (lexer, T_LBRACK))
478     {
479       while (!lex_match (lexer, T_RBRACK))
480         {
481           if (!lex_force_id (lexer))
482             return false;
483           if (dict_lookup_mrset (dict, lex_tokcstr (lexer)) == NULL)
484             {
485               msg (SE, _("No multiple response set named %s."),
486                    lex_tokcstr (lexer));
487               stringi_set_destroy (mrset_names);
488               return false;
489             }
490           stringi_set_insert (mrset_names, lex_tokcstr (lexer));
491           lex_get (lexer);
492         }
493     }
494   else if (lex_match (lexer, T_ALL))
495     {
496       size_t n_sets = dict_get_n_mrsets (dict);
497       size_t i;
498
499       for (i = 0; i < n_sets; i++)
500         stringi_set_insert (mrset_names, dict_get_mrset (dict, i)->name);
501     }
502
503   return true;
504 }
505
506 static bool
507 parse_delete (struct lexer *lexer, struct dictionary *dict)
508 {
509   const struct stringi_set_node *node;
510   struct stringi_set mrset_names;
511   const char *name;
512
513   if (!parse_mrset_names (lexer, dict, &mrset_names))
514     return false;
515
516   STRINGI_SET_FOR_EACH (name, node, &mrset_names)
517     dict_delete_mrset (dict, name);
518   stringi_set_destroy (&mrset_names);
519
520   return true;
521 }
522
523 static bool
524 parse_display (struct lexer *lexer, struct dictionary *dict)
525 {
526   struct string details, var_names;
527   struct stringi_set mrset_names_set;
528   char **mrset_names;
529   struct tab_table *table;
530   size_t i, n;
531
532   if (!parse_mrset_names (lexer, dict, &mrset_names_set))
533     return false;
534
535   n = stringi_set_count (&mrset_names_set);
536   if (n == 0)
537     {
538       if (dict_get_n_mrsets (dict) == 0)
539         msg (SN, _("The active file dictionary does not contain any multiple "
540                    "response sets."));
541       stringi_set_destroy (&mrset_names_set);
542       return true;
543     }
544
545   table = tab_create (3, n + 1);
546   tab_headers (table, 0, 0, 1, 0);
547   tab_box (table, TAL_1, TAL_1, TAL_1, TAL_1, 0, 0, 2, n);
548   tab_hline (table, TAL_2, 0, 2, 1);
549   tab_title (table, "%s", _("Multiple Response Sets"));
550   tab_text (table, 0, 0, TAB_EMPH | TAB_LEFT, _("Name"));
551   tab_text (table, 1, 0, TAB_EMPH | TAB_LEFT, _("Variables"));
552   tab_text (table, 2, 0, TAB_EMPH | TAB_LEFT, _("Details"));
553
554   ds_init_empty (&details);
555   ds_init_empty (&var_names);
556   mrset_names = stringi_set_get_sorted_array (&mrset_names_set);
557   for (i = 0; i < n; i++)
558     {
559       const struct mrset *mrset = dict_lookup_mrset (dict, mrset_names[i]);
560       const int row = i + 1;
561       size_t j;
562
563       /* Details. */
564       ds_clear (&details);
565       ds_put_format (&details, "%s\n", (mrset->type == MRSET_MD
566                                         ? _("Multiple dichotomy set")
567                                         : _("Multiple category set")));
568       if (mrset->label != NULL)
569         ds_put_format (&details, "%s: %s\n", _("Label"), mrset->label);
570       if (mrset->type == MRSET_MD)
571         {
572           if (mrset->label != NULL || mrset->label_from_var_label)
573             ds_put_format (&details, "%s: %s\n", _("Label source"),
574                            (mrset->label_from_var_label
575                             ? _("First variable label among variables")
576                             : _("Provided by user")));
577           ds_put_format (&details, "%s: ", _("Counted value"));
578           if (mrset->width == 0)
579             ds_put_format (&details, "%.0f\n", mrset->counted.f);
580           else
581             ds_put_format (&details, "`%.*s'\n", mrset->width,
582                            value_str (&mrset->counted, mrset->width));
583           ds_put_format (&details, "%s: %s\n", _("Category label source"),
584                          (mrset->cat_source == MRSET_VARLABELS
585                           ? _("Variable labels")
586                           : _("Value labels of counted value")));
587         }
588
589       /* Variable names. */
590       ds_clear (&var_names);
591       for (j = 0; j < mrset->n_vars; j++)
592         ds_put_format (&var_names, "%s\n", var_get_name (mrset->vars[j]));
593
594       tab_text (table, 0, row, TAB_LEFT, mrset_names[i]);
595       tab_text (table, 1, row, TAB_LEFT, ds_cstr (&var_names));
596       tab_text (table, 2, row, TAB_LEFT, ds_cstr (&details));
597     }
598   free (mrset_names);
599   ds_destroy (&var_names);
600   ds_destroy (&details);
601   stringi_set_destroy (&mrset_names_set);
602
603   tab_submit (table);
604
605   return true;
606 }