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