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