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