MISSING VALUES: Improve error messages.
[pspp] / src / language / dictionary / modify-variables.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 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 <stdlib.h>
20
21 #include "data/dataset.h"
22 #include "data/dictionary.h"
23 #include "data/variable.h"
24 #include "language/command.h"
25 #include "language/lexer/lexer.h"
26 #include "language/lexer/variable-parser.h"
27 #include "libpspp/array.h"
28 #include "libpspp/assertion.h"
29 #include "libpspp/compiler.h"
30 #include "libpspp/i18n.h"
31 #include "libpspp/message.h"
32 #include "libpspp/misc.h"
33 #include "libpspp/str.h"
34
35 #include "gl/xalloc.h"
36
37 #include "gettext.h"
38 #define _(msgid) gettext (msgid)
39
40 /* These control the ordering produced by
41    compare_variables_given_ordering(). */
42 struct ordering
43   {
44     bool forward;               /* true=FORWARD, false=BACKWARD. */
45     bool positional;            /* true=POSITIONAL, false=ALPHA. */
46   };
47
48 /* Increasing order of variable index. */
49 static struct ordering forward_positional_ordering = {1, 1};
50
51 static int compare_variables_given_ordering (const void *, const void *,
52                                              const void *ordering);
53
54 /* Explains how to modify the variables in a dictionary. */
55 struct var_modification
56   {
57     /* New variable ordering. */
58     struct variable **reorder_vars;
59     size_t n_reorder;
60
61     /* DROP/KEEP information. */
62     struct variable **drop_vars;
63     size_t n_drop;
64
65     /* New variable names. */
66     struct variable **rename_vars;
67     char **new_names;
68     size_t n_rename;
69   };
70
71 static bool rearrange_dict (struct dictionary *d,
72                            const struct var_modification *vm);
73
74 /* Performs MODIFY VARS command. */
75 int
76 cmd_modify_vars (struct lexer *lexer, struct dataset *ds)
77 {
78   if (proc_make_temporary_transformations_permanent (ds))
79     lex_ofs_error (lexer, 0, lex_ofs (lexer) - 1,
80                    _("%s may not be used after %s.  "
81                      "Temporary transformations will be made permanent."),
82                    "MODIFY VARS", "TEMPORARY");
83
84   /* Bits indicated whether we've already encountered a subcommand of this
85      type. */
86   unsigned int already_encountered = 0;
87
88   /* Return code. */
89   int ret_code = CMD_CASCADING_FAILURE;
90
91   /* What we are going to do to the active dataset. */
92   struct var_modification vm =
93     {
94       .reorder_vars = NULL,
95       .n_reorder = 0,
96       .rename_vars = NULL,
97       .new_names = NULL,
98       .n_rename = 0,
99       .drop_vars = NULL,
100       .n_drop = 0,
101     };
102
103   /* Parse each subcommand. */
104   lex_match (lexer, T_SLASH);
105   for (;;)
106     {
107       if (lex_match_id (lexer, "REORDER"))
108         {
109           if (already_encountered & 1)
110             {
111               lex_sbc_only_once (lexer, "REORDER");
112               goto done;
113             }
114           already_encountered |= 1;
115
116           struct variable **v = NULL;
117           size_t nv = 0;
118
119           lex_match (lexer, T_EQUALS);
120           do
121             {
122               struct ordering ordering;
123               size_t prev_nv = nv;
124
125               ordering.forward = ordering.positional = true;
126               for (;;)
127                 {
128                   if (lex_match_id (lexer, "FORWARD"))
129                     ordering.forward = true;
130                   else if (lex_match_id (lexer, "BACKWARD"))
131                     ordering.forward = false;
132                   else if (lex_match_id (lexer, "POSITIONAL"))
133                     ordering.positional = true;
134                   else if (lex_match_id (lexer, "ALPHA"))
135                     ordering.positional = false;
136                   else
137                     break;
138                 }
139
140               if (lex_match (lexer, T_ALL)
141                   || lex_token (lexer) == T_SLASH
142                   || lex_token (lexer) == T_ENDCMD)
143                 {
144                   if (prev_nv != 0)
145                     {
146                       msg (SE, _("Cannot specify ALL after specifying a set "
147                            "of variables."));
148                       goto done;
149                     }
150                   dict_get_vars_mutable (dataset_dict (ds), &v, &nv,
151                                          DC_SYSTEM);
152                 }
153               else
154                 {
155                   if (!lex_match (lexer, T_LPAREN))
156                     {
157                       lex_error_expecting (lexer, "`('");
158                       free (v);
159                       goto done;
160                     }
161                   if (!parse_variables (lexer, dataset_dict (ds), &v, &nv,
162                                         PV_APPEND | PV_NO_DUPLICATE))
163                     {
164                       free (v);
165                       goto done;
166                     }
167                   if (!lex_match (lexer, T_RPAREN))
168                     {
169                       lex_error_expecting (lexer, "`)'");
170                       free (v);
171                       goto done;
172                     }
173                 }
174
175               if (!ordering.positional)
176                 sort (&v[prev_nv], nv - prev_nv, sizeof *v,
177                       compare_variables_given_ordering, &ordering);
178               else if (!ordering.forward)
179                 reverse_array(&v[prev_nv], nv - prev_nv, sizeof *v);
180             }
181           while (lex_token (lexer) != T_SLASH
182                  && lex_token (lexer) != T_ENDCMD);
183
184           vm.reorder_vars = v;
185           vm.n_reorder = nv;
186         }
187       else if (lex_match_id (lexer, "RENAME"))
188         {
189           if (already_encountered & 2)
190             {
191               lex_sbc_only_once (lexer, "RENAME");
192               goto done;
193             }
194           already_encountered |= 2;
195
196           lex_match (lexer, T_EQUALS);
197           do
198             {
199               size_t prev_nv_1 = vm.n_rename;
200               size_t prev_nv_2 = vm.n_rename;
201
202               if (!lex_match (lexer, T_LPAREN))
203                 {
204                   lex_error_expecting (lexer, "`('");
205                   goto done;
206                 }
207               if (!parse_variables (lexer, dataset_dict (ds),
208                                     &vm.rename_vars, &vm.n_rename,
209                                     PV_APPEND | PV_NO_DUPLICATE))
210                 goto done;
211               if (!lex_match (lexer, T_EQUALS))
212                 {
213                   lex_error_expecting (lexer, "`='");
214                   goto done;
215                 }
216
217               if (!parse_DATA_LIST_vars (lexer, dataset_dict (ds),
218                                          &vm.new_names, &prev_nv_1, PV_APPEND))
219                 goto done;
220               if (prev_nv_1 != vm.n_rename)
221                 {
222                   msg (SE, _("Differing number of variables in old name list "
223                              "(%zu) and in new name list (%zu)."),
224                        vm.n_rename - prev_nv_2, prev_nv_1 - prev_nv_2);
225                   for (size_t i = 0; i < prev_nv_1; i++)
226                     free (vm.new_names[i]);
227                   free (vm.new_names);
228                   vm.new_names = NULL;
229                   goto done;
230                 }
231               if (!lex_match (lexer, T_RPAREN))
232                 {
233                   lex_error_expecting (lexer, "`)'");
234                   goto done;
235                 }
236             }
237           while (lex_token (lexer) != T_ENDCMD
238                  && lex_token (lexer) != T_SLASH);
239         }
240       else if (lex_match_id (lexer, "KEEP"))
241         {
242           if (already_encountered & 4)
243             {
244               lex_next_error (lexer, -1, -1,
245                               _("%s subcommand may be given at most once.  "
246                                 "It may not be given in conjunction with the "
247                                 "%s subcommand."),
248                               "KEEP", "DROP");
249               goto done;
250             }
251           already_encountered |= 4;
252
253           struct variable **keep_vars, **drop_vars;
254           size_t n_keep, n_drop;
255           lex_match (lexer, T_EQUALS);
256           if (!parse_variables (lexer, dataset_dict (ds),
257                                 &keep_vars, &n_keep, PV_NONE))
258             goto done;
259
260           /* Transform the list of variables to keep into a list of
261              variables to drop.  First sort the keep list, then figure
262              out which variables are missing. */
263           sort (keep_vars, n_keep, sizeof *keep_vars,
264                 compare_variables_given_ordering,
265                 &forward_positional_ordering);
266
267           struct variable **all_vars;
268           size_t n_all;
269           dict_get_vars_mutable (dataset_dict (ds), &all_vars, &n_all, 0);
270           assert (n_all >= n_keep);
271
272           n_drop = n_all - n_keep;
273           drop_vars = xnmalloc (n_drop, sizeof *keep_vars);
274           if (set_difference (all_vars, n_all,
275                               keep_vars, n_keep,
276                               sizeof *all_vars,
277                               drop_vars,
278                               compare_variables_given_ordering,
279                               &forward_positional_ordering)
280               != n_drop)
281             NOT_REACHED ();
282
283           free (keep_vars);
284           free (all_vars);
285
286           vm.drop_vars = drop_vars;
287           vm.n_drop = n_drop;
288         }
289       else if (lex_match_id (lexer, "DROP"))
290         {
291           struct variable **drop_vars;
292           size_t n_drop;
293
294           if (already_encountered & 4)
295             {
296               lex_next_error (lexer, -1, -1,
297                               _("%s subcommand may be given at most once.  "
298                                 "It may not be given in conjunction with the "
299                                 "%s subcommand."),
300                    "DROP", "KEEP"
301                 );
302               goto done;
303             }
304           already_encountered |= 4;
305
306           int start_ofs = lex_ofs (lexer) - 1;
307           lex_match (lexer, T_EQUALS);
308           if (!parse_variables (lexer, dataset_dict (ds),
309                                 &drop_vars, &n_drop, PV_NONE))
310             goto done;
311           int end_ofs = lex_ofs (lexer) - 1;
312           vm.drop_vars = drop_vars;
313           vm.n_drop = n_drop;
314
315           if (n_drop == dict_get_n_vars (dataset_dict (ds)))
316             {
317               lex_ofs_error (lexer, start_ofs, end_ofs,
318                              _("%s may not be used to delete all variables "
319                                "from the active dataset dictionary.  "
320                                "Use %s instead."), "MODIFY VARS", "NEW FILE");
321               goto done;
322             }
323         }
324       else if (lex_match_id (lexer, "MAP"))
325         {
326           struct dictionary *temp = dict_clone (dataset_dict (ds));
327           int success = rearrange_dict (temp, &vm);
328           if (success)
329             {
330               /* FIXME: display new dictionary. */
331             }
332           dict_unref (temp);
333         }
334       else
335         {
336           lex_error_expecting (lexer, "REORDER", "RENAME", "KEEP",
337                                "DROP", "MAP");
338           goto done;
339         }
340
341       if (lex_token (lexer) == T_ENDCMD)
342         break;
343       if (lex_token (lexer) != T_SLASH)
344         {
345           lex_error_expecting (lexer, "`/'", "`.'");
346           goto done;
347         }
348       lex_get (lexer);
349     }
350
351   if (already_encountered & (1 | 4))
352     {
353       /* Read the data. */
354       if (!proc_execute (ds))
355         goto done;
356     }
357
358   if (!rearrange_dict (dataset_dict (ds), &vm))
359     goto done;
360
361   ret_code = CMD_SUCCESS;
362
363 done:
364   free (vm.reorder_vars);
365   free (vm.rename_vars);
366   if (vm.new_names)
367     for (size_t i = 0; i < vm.n_rename; i++)
368       free (vm.new_names[i]);
369   free (vm.new_names);
370   free (vm.drop_vars);
371   return ret_code;
372 }
373
374 /* Compares A and B according to the settings in ORDERING, returning a
375    strcmp()-type result. */
376 static int
377 compare_variables_given_ordering (const void *a_, const void *b_,
378                                   const void *ordering_)
379 {
380   struct variable *const *pa = a_;
381   struct variable *const *pb = b_;
382   const struct variable *a = *pa;
383   const struct variable *b = *pb;
384   const struct ordering *ordering = ordering_;
385
386   int result;
387   if (ordering->positional)
388     {
389       size_t a_index = var_get_dict_index (a);
390       size_t b_index = var_get_dict_index (b);
391       result = a_index < b_index ? -1 : a_index > b_index;
392     }
393   else
394     result = utf8_strcasecmp (var_get_name (a), var_get_name (b));
395   if (!ordering->forward)
396     result = -result;
397   return result;
398 }
399
400 /* Pairs a variable with a new name. */
401 struct var_renaming
402   {
403     struct variable *var;
404     const char *new_name;
405   };
406
407 /* A algo_compare_func that compares new_name members in struct var_renaming
408    structures A and B. */
409 static int
410 compare_var_renaming_by_new_name (const void *a_, const void *b_,
411                                   const void *aux UNUSED)
412 {
413   const struct var_renaming *a = a_;
414   const struct var_renaming *b = b_;
415
416   return utf8_strcasecmp (a->new_name, b->new_name);
417 }
418
419 /* Returns true if performing VM on dictionary D would not cause problems such
420    as duplicate variable names.  Returns false otherwise, and issues an error
421    message. */
422 static bool
423 validate_var_modification (const struct dictionary *d,
424                            const struct var_modification *vm)
425 {
426   /* Variable reordering can't be a problem, so we don't simulate
427      it.  Variable renaming can cause duplicate names, but
428      dropping variables can eliminate them, so we simulate both
429      of those. */
430
431   /* All variables, in index order. */
432   struct variable **all_vars;
433   size_t n_all;
434   dict_get_vars_mutable (d, &all_vars, &n_all, 0);
435
436   /* Drop variables, in index order. */
437   size_t n_drop = vm->n_drop;
438   struct variable **drop_vars = xnmalloc (n_drop, sizeof *drop_vars);
439   memcpy (drop_vars, vm->drop_vars, n_drop * sizeof *drop_vars);
440   sort (drop_vars, n_drop, sizeof *drop_vars,
441         compare_variables_given_ordering, &forward_positional_ordering);
442
443   /* Keep variables, in index order. */
444   assert (n_all >= n_drop);
445   size_t n_keep = n_all - n_drop;
446   struct variable **keep_vars = xnmalloc (n_keep, sizeof *keep_vars);
447   if (set_difference (all_vars, n_all,
448                       drop_vars, n_drop,
449                       sizeof *all_vars,
450                       keep_vars,
451                       compare_variables_given_ordering,
452                       &forward_positional_ordering) != n_keep)
453     NOT_REACHED ();
454
455   /* Copy variables into var_renaming array. */
456   struct var_renaming *var_renaming = xnmalloc (n_keep, sizeof *var_renaming);
457   for (size_t i = 0; i < n_keep; i++)
458     {
459       var_renaming[i].var = keep_vars[i];
460       var_renaming[i].new_name = var_get_name (keep_vars[i]);
461     }
462
463   /* Rename variables in var_renaming array. */
464   for (size_t i = 0; i < vm->n_rename; i++)
465     {
466       struct variable *const *kv;
467       struct var_renaming *vr;
468
469       /* Get the var_renaming element. */
470       kv = binary_search (keep_vars, n_keep, sizeof *keep_vars,
471                           &vm->rename_vars[i],
472                           compare_variables_given_ordering,
473                           &forward_positional_ordering);
474       if (kv == NULL)
475         continue;
476       vr = var_renaming + (kv - keep_vars);
477
478       vr->new_name = vm->new_names[i];
479     }
480
481   /* Sort var_renaming array by new names and check for duplicates. */
482   sort (var_renaming, n_keep, sizeof *var_renaming,
483         compare_var_renaming_by_new_name, NULL);
484   bool ok = !adjacent_find_equal (var_renaming, n_keep, sizeof *var_renaming,
485                                   compare_var_renaming_by_new_name, NULL);
486
487   /* Clean up. */
488   free (all_vars);
489   free (keep_vars);
490   free (drop_vars);
491   free (var_renaming);
492
493   return ok;
494 }
495
496 /* Reorders, removes, and renames variables in dictionary D according to VM.
497    Returns true if successful, false if there would have been duplicate
498    variable names if the modifications had been carried out.  In the latter
499    case, the dictionary is not modified. */
500 static bool
501 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
502 {
503   /* Check whether the modifications will cause duplicate names. */
504   if (!validate_var_modification (d, vm))
505     return false;
506
507   /* Record the old names of variables to rename.  After variables are deleted,
508      we can't depend on the variables to still exist, but we can still look
509      them up by name. */
510   char **rename_old_names = xnmalloc (vm->n_rename, sizeof *rename_old_names);
511   for (size_t i = 0; i < vm->n_rename; i++)
512     rename_old_names[i] = xstrdup (var_get_name (vm->rename_vars[i]));
513
514   /* Reorder and delete variables. */
515   dict_reorder_vars (d, vm->reorder_vars, vm->n_reorder);
516   dict_delete_vars (d, vm->drop_vars, vm->n_drop);
517
518   /* Compose lists of variables to rename and their new names. */
519   struct variable **rename_vars = xnmalloc (vm->n_rename, sizeof *rename_vars);
520   char **rename_new_names = xnmalloc (vm->n_rename, sizeof *rename_new_names);
521   size_t n_rename = 0;
522   for (size_t i = 0; i < vm->n_rename; i++)
523     {
524       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
525       if (var == NULL)
526         continue;
527
528       rename_vars[n_rename] = var;
529       rename_new_names[n_rename] = vm->new_names[i];
530       n_rename++;
531     }
532
533   /* Do renaming. */
534   if (dict_rename_vars (d, rename_vars, rename_new_names, n_rename,
535                         NULL) == 0)
536     NOT_REACHED ();
537
538   /* Clean up. */
539   for (size_t i = 0; i < vm->n_rename; i++)
540     free (rename_old_names[i]);
541   free (rename_old_names);
542   free (rename_vars);
543   free (rename_new_names);
544
545   return true;
546 }