MODIFY VARS: Style updates.
[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/bit-vector.h"
30 #include "libpspp/compiler.h"
31 #include "libpspp/i18n.h"
32 #include "libpspp/message.h"
33 #include "libpspp/misc.h"
34 #include "libpspp/str.h"
35
36 #include "gl/xalloc.h"
37
38 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 /* These control the ordering produced by
42    compare_variables_given_ordering(). */
43 struct ordering
44   {
45     bool forward;               /* true=FORWARD, false=BACKWARD. */
46     bool positional;            /* true=POSITIONAL, false=ALPHA. */
47   };
48
49 /* Increasing order of variable index. */
50 static struct ordering forward_positional_ordering = {1, 1};
51
52 static int compare_variables_given_ordering (const void *, const void *,
53                                              const void *ordering);
54
55 /* Explains how to modify the variables in a dictionary. */
56 struct var_modification
57   {
58     /* New variable ordering. */
59     struct variable **reorder_vars;
60     size_t n_reorder;
61
62     /* DROP/KEEP information. */
63     struct variable **drop_vars;
64     size_t n_drop;
65
66     /* New variable names. */
67     struct variable **rename_vars;
68     char **new_names;
69     size_t n_rename;
70   };
71
72 static bool rearrange_dict (struct dictionary *d,
73                            const struct var_modification *vm);
74
75 /* Performs MODIFY VARS command. */
76 int
77 cmd_modify_vars (struct lexer *lexer, struct dataset *ds)
78 {
79   if (proc_make_temporary_transformations_permanent (ds))
80     msg (SE, _("%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 ("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, "`('", NULL_SENTINEL);
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, "`)'", NULL_SENTINEL);
170                       free (v);
171                       goto done;
172                     }
173                 }
174               sort (&v[prev_nv], nv - prev_nv, sizeof *v,
175                     compare_variables_given_ordering, &ordering);
176             }
177           while (lex_token (lexer) != T_SLASH
178                  && lex_token (lexer) != T_ENDCMD);
179
180           vm.reorder_vars = v;
181           vm.n_reorder = nv;
182         }
183       else if (lex_match_id (lexer, "RENAME"))
184         {
185           if (already_encountered & 2)
186             {
187               lex_sbc_only_once ("RENAME");
188               goto done;
189             }
190           already_encountered |= 2;
191
192           lex_match (lexer, T_EQUALS);
193           do
194             {
195               size_t prev_nv_1 = vm.n_rename;
196               size_t prev_nv_2 = vm.n_rename;
197
198               if (!lex_match (lexer, T_LPAREN))
199                 {
200                   lex_error_expecting (lexer, "`('", NULL_SENTINEL);
201                   goto done;
202                 }
203               if (!parse_variables (lexer, dataset_dict (ds),
204                                     &vm.rename_vars, &vm.n_rename,
205                                     PV_APPEND | PV_NO_DUPLICATE))
206                 goto done;
207               if (!lex_match (lexer, T_EQUALS))
208                 {
209                   lex_error_expecting (lexer, "`='", NULL_SENTINEL);
210                   goto done;
211                 }
212
213               if (!parse_DATA_LIST_vars (lexer, dataset_dict (ds),
214                                          &vm.new_names, &prev_nv_1, PV_APPEND))
215                 goto done;
216               if (prev_nv_1 != vm.n_rename)
217                 {
218                   msg (SE, _("Differing number of variables in old name list "
219                              "(%zu) and in new name list (%zu)."),
220                        vm.n_rename - prev_nv_2, prev_nv_1 - prev_nv_2);
221                   for (size_t i = 0; i < prev_nv_1; i++)
222                     free (vm.new_names[i]);
223                   free (vm.new_names);
224                   vm.new_names = NULL;
225                   goto done;
226                 }
227               if (!lex_match (lexer, T_RPAREN))
228                 {
229                   lex_error_expecting (lexer, "`)'", NULL_SENTINEL);
230                   goto done;
231                 }
232             }
233           while (lex_token (lexer) != T_ENDCMD
234                  && lex_token (lexer) != T_SLASH);
235         }
236       else if (lex_match_id (lexer, "KEEP"))
237         {
238           if (already_encountered & 4)
239             {
240               msg (SE,
241                    _("%s subcommand may be given at most once.  It may "
242                      "not be given in conjunction with the %s subcommand."),
243                    "KEEP", "DROP");
244               goto done;
245             }
246           already_encountered |= 4;
247
248           struct variable **keep_vars, **drop_vars;
249           size_t n_keep, n_drop;
250           lex_match (lexer, T_EQUALS);
251           if (!parse_variables (lexer, dataset_dict (ds),
252                                 &keep_vars, &n_keep, PV_NONE))
253             goto done;
254
255           /* Transform the list of variables to keep into a list of
256              variables to drop.  First sort the keep list, then figure
257              out which variables are missing. */
258           sort (keep_vars, n_keep, sizeof *keep_vars,
259                 compare_variables_given_ordering,
260                 &forward_positional_ordering);
261
262           struct variable **all_vars;
263           size_t n_all;
264           dict_get_vars_mutable (dataset_dict (ds), &all_vars, &n_all, 0);
265           assert (n_all >= n_keep);
266
267           n_drop = n_all - n_keep;
268           drop_vars = xnmalloc (n_drop, sizeof *keep_vars);
269           if (set_difference (all_vars, n_all,
270                               keep_vars, n_keep,
271                               sizeof *all_vars,
272                               drop_vars,
273                               compare_variables_given_ordering,
274                               &forward_positional_ordering)
275               != n_drop)
276             NOT_REACHED ();
277
278           free (keep_vars);
279           free (all_vars);
280
281           vm.drop_vars = drop_vars;
282           vm.n_drop = n_drop;
283         }
284       else if (lex_match_id (lexer, "DROP"))
285         {
286           struct variable **drop_vars;
287           size_t n_drop;
288
289           if (already_encountered & 4)
290             {
291               msg (SE, _("%s subcommand may be given at most once.  It may "
292                          "not be given in conjunction with the %s "
293                          "subcommand."),
294                    "DROP", "KEEP"
295                    );
296               goto done;
297             }
298           already_encountered |= 4;
299
300           lex_match (lexer, T_EQUALS);
301           if (!parse_variables (lexer, dataset_dict (ds),
302                                 &drop_vars, &n_drop, PV_NONE))
303             goto done;
304           vm.drop_vars = drop_vars;
305           vm.n_drop = n_drop;
306         }
307       else if (lex_match_id (lexer, "MAP"))
308         {
309           struct dictionary *temp = dict_clone (dataset_dict (ds));
310           int success = rearrange_dict (temp, &vm);
311           if (success)
312             {
313               /* FIXME: display new dictionary. */
314             }
315           dict_unref (temp);
316         }
317       else
318         {
319           if (lex_token (lexer) == T_ID)
320             msg (SE, _("Unrecognized subcommand name `%s'."),
321                  lex_tokcstr (lexer));
322           else
323             msg (SE, _("Subcommand name expected."));
324           goto done;
325         }
326
327       if (lex_token (lexer) == T_ENDCMD)
328         break;
329       if (lex_token (lexer) != T_SLASH)
330         {
331           lex_error_expecting (lexer, "`/'", "`.'", NULL_SENTINEL);
332           goto done;
333         }
334       lex_get (lexer);
335     }
336
337   if (already_encountered & (1 | 4))
338     {
339       /* Read the data. */
340       if (!proc_execute (ds))
341         goto done;
342     }
343
344   if (!rearrange_dict (dataset_dict (ds), &vm))
345     goto done;
346
347   ret_code = CMD_SUCCESS;
348
349 done:
350   free (vm.reorder_vars);
351   free (vm.rename_vars);
352   for (size_t i = 0; i < vm.n_rename; i++)
353     free (vm.new_names[i]);
354   free (vm.new_names);
355   free (vm.drop_vars);
356   return ret_code;
357 }
358
359 /* Compares A and B according to the settings in ORDERING, returning a
360    strcmp()-type result. */
361 static int
362 compare_variables_given_ordering (const void *a_, const void *b_,
363                                   const void *ordering_)
364 {
365   struct variable *const *pa = a_;
366   struct variable *const *pb = b_;
367   const struct variable *a = *pa;
368   const struct variable *b = *pb;
369   const struct ordering *ordering = ordering_;
370
371   int result;
372   if (ordering->positional)
373     {
374       size_t a_index = var_get_dict_index (a);
375       size_t b_index = var_get_dict_index (b);
376       result = a_index < b_index ? -1 : a_index > b_index;
377     }
378   else
379     result = utf8_strcasecmp (var_get_name (a), var_get_name (b));
380   if (!ordering->forward)
381     result = -result;
382   return result;
383 }
384
385 /* Pairs a variable with a new name. */
386 struct var_renaming
387   {
388     struct variable *var;
389     const char *new_name;
390   };
391
392 /* A algo_compare_func that compares new_name members in struct var_renaming
393    structures A and B. */
394 static int
395 compare_var_renaming_by_new_name (const void *a_, const void *b_,
396                                   const void *aux UNUSED)
397 {
398   const struct var_renaming *a = a_;
399   const struct var_renaming *b = b_;
400
401   return utf8_strcasecmp (a->new_name, b->new_name);
402 }
403
404 /* Returns true if performing VM on dictionary D would not cause problems such
405    as duplicate variable names.  Returns false otherwise, and issues an error
406    message. */
407 static bool
408 validate_var_modification (const struct dictionary *d,
409                            const struct var_modification *vm)
410 {
411   /* Variable reordering can't be a problem, so we don't simulate
412      it.  Variable renaming can cause duplicate names, but
413      dropping variables can eliminate them, so we simulate both
414      of those. */
415
416   /* All variables, in index order. */
417   struct variable **all_vars;
418   size_t n_all;
419   dict_get_vars_mutable (d, &all_vars, &n_all, 0);
420
421   /* Drop variables, in index order. */
422   size_t n_drop = vm->n_drop;
423   struct variable **drop_vars = xnmalloc (n_drop, sizeof *drop_vars);
424   memcpy (drop_vars, vm->drop_vars, n_drop * sizeof *drop_vars);
425   sort (drop_vars, n_drop, sizeof *drop_vars,
426         compare_variables_given_ordering, &forward_positional_ordering);
427
428   /* Keep variables, in index order. */
429   assert (n_all >= n_drop);
430   size_t n_keep = n_all - n_drop;
431   struct variable **keep_vars = xnmalloc (n_keep, sizeof *keep_vars);
432   if (set_difference (all_vars, n_all,
433                       drop_vars, n_drop,
434                       sizeof *all_vars,
435                       keep_vars,
436                       compare_variables_given_ordering,
437                       &forward_positional_ordering) != n_keep)
438     NOT_REACHED ();
439
440   /* Copy variables into var_renaming array. */
441   struct var_renaming *var_renaming = xnmalloc (n_keep, sizeof *var_renaming);
442   for (size_t i = 0; i < n_keep; i++)
443     {
444       var_renaming[i].var = keep_vars[i];
445       var_renaming[i].new_name = var_get_name (keep_vars[i]);
446     }
447
448   /* Rename variables in var_renaming array. */
449   for (size_t i = 0; i < vm->n_rename; i++)
450     {
451       struct variable *const *kv;
452       struct var_renaming *vr;
453
454       /* Get the var_renaming element. */
455       kv = binary_search (keep_vars, n_keep, sizeof *keep_vars,
456                           &vm->rename_vars[i],
457                           compare_variables_given_ordering,
458                           &forward_positional_ordering);
459       if (kv == NULL)
460         continue;
461       vr = var_renaming + (kv - keep_vars);
462
463       vr->new_name = vm->new_names[i];
464     }
465
466   /* Sort var_renaming array by new names and check for duplicates. */
467   sort (var_renaming, n_keep, sizeof *var_renaming,
468         compare_var_renaming_by_new_name, NULL);
469   bool ok = !adjacent_find_equal (var_renaming, n_keep, sizeof *var_renaming,
470                                   compare_var_renaming_by_new_name, NULL);
471
472   /* Clean up. */
473   free (all_vars);
474   free (keep_vars);
475   free (drop_vars);
476   free (var_renaming);
477
478   return ok;
479 }
480
481 /* Reorders, removes, and renames variables in dictionary D according to VM.
482    Returns true if successful, false if there would have been duplicate
483    variable names if the modifications had been carried out.  In the latter
484    case, the dictionary is not modified. */
485 static bool
486 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
487 {
488   /* Check whether the modifications will cause duplicate names. */
489   if (!validate_var_modification (d, vm))
490     return false;
491
492   /* Record the old names of variables to rename.  After variables are deleted,
493      we can't depend on the variables to still exist, but we can still look
494      them up by name. */
495   char **rename_old_names = xnmalloc (vm->n_rename, sizeof *rename_old_names);
496   for (size_t i = 0; i < vm->n_rename; i++)
497     rename_old_names[i] = xstrdup (var_get_name (vm->rename_vars[i]));
498
499   /* Reorder and delete variables. */
500   dict_reorder_vars (d, vm->reorder_vars, vm->n_reorder);
501   dict_delete_vars (d, vm->drop_vars, vm->n_drop);
502
503   /* Compose lists of variables to rename and their new names. */
504   struct variable **rename_vars = xnmalloc (vm->n_rename, sizeof *rename_vars);
505   char **rename_new_names = xnmalloc (vm->n_rename, sizeof *rename_new_names);
506   size_t n_rename = 0;
507   for (size_t i = 0; i < vm->n_rename; i++)
508     {
509       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
510       if (var == NULL)
511         continue;
512
513       rename_vars[n_rename] = var;
514       rename_new_names[n_rename] = vm->new_names[i];
515       n_rename++;
516     }
517
518   /* Do renaming. */
519   if (dict_rename_vars (d, rename_vars, rename_new_names, n_rename,
520                         NULL) == 0)
521     NOT_REACHED ();
522
523   /* Clean up. */
524   for (size_t i = 0; i < vm->n_rename; i++)
525     free (rename_old_names[i]);
526   free (rename_old_names);
527   free (rename_vars);
528   free (rename_new_names);
529
530   return true;
531 }