9fc6fa55c452c3a03ffeba7e7e6633c376f48bc2
[pspp-builds.git] / src / language / dictionary / modify-variables.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000 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/dictionary.h>
22 #include <data/procedure.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/assertion.h>
28 #include <libpspp/array.h>
29 #include <libpspp/bit-vector.h>
30 #include <libpspp/compiler.h>
31 #include <libpspp/hash.h>
32 #include <libpspp/message.h>
33 #include <libpspp/misc.h>
34 #include <libpspp/str.h>
35
36 #include "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're gonna do to the active file. */
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, '/');
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               msg (SE, _("REORDER subcommand may be given at most once."));
115               goto done;
116             }
117           already_encountered |= 1;
118
119           lex_match (lexer, '=');
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) == '/' || lex_token (lexer) == '.')
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, 1u << DC_SYSTEM);
142                 }
143               else
144                 {
145                   if (!lex_match (lexer, '('))
146                     {
147                       msg (SE, _("`(' expected on REORDER subcommand."));
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, ')'))
158                     {
159                       msg (SE, _("`)' expected following variable names on "
160                            "REORDER subcommand."));
161                       free (v);
162                       goto done;
163                     }
164                 }
165               sort (&v[prev_nv], nv - prev_nv, sizeof *v,
166                     compare_variables_given_ordering, &ordering);
167             }
168           while (lex_token (lexer) != '/' && lex_token (lexer) != '.');
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, _("RENAME subcommand may be given at most once."));
178               goto done;
179             }
180           already_encountered |= 2;
181
182           lex_match (lexer, '=');
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, '('))
189                 {
190                   msg (SE, _("`(' expected on RENAME subcommand."));
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, '='))
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, &vm.new_names,
204                                          &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, ')'))
218                 {
219                   msg (SE, _("`)' expected after variable lists on RENAME "
220                        "subcommand."));
221                   goto done;
222                 }
223             }
224           while (lex_token (lexer) != '.' && lex_token (lexer) != '/');
225         }
226       else if (lex_match_id (lexer, "KEEP"))
227         {
228           struct variable **keep_vars, **all_vars, **drop_vars;
229           size_t keep_cnt, all_cnt, drop_cnt;
230
231           if (already_encountered & 4)
232             {
233               msg (SE, _("KEEP subcommand may be given at most once.  It may "
234                    "not be given in conjunction with the DROP subcommand."));
235               goto done;
236             }
237           already_encountered |= 4;
238
239           lex_match (lexer, '=');
240           if (!parse_variables (lexer, dataset_dict (ds), &keep_vars, &keep_cnt, PV_NONE))
241             goto done;
242
243           /* Transform the list of variables to keep into a list of
244              variables to drop.  First sort the keep list, then figure
245              out which variables are missing. */
246           sort (keep_vars, keep_cnt, sizeof *keep_vars,
247                 compare_variables_given_ordering, &forward_positional_ordering);
248
249           dict_get_vars_mutable (dataset_dict (ds), &all_vars, &all_cnt, 0);
250           assert (all_cnt >= keep_cnt);
251
252           drop_cnt = all_cnt - keep_cnt;
253           drop_vars = xnmalloc (drop_cnt, sizeof *keep_vars);
254           if (set_difference (all_vars, all_cnt,
255                               keep_vars, keep_cnt,
256                               sizeof *all_vars,
257                               drop_vars,
258                               compare_variables_given_ordering,
259                               &forward_positional_ordering)
260               != drop_cnt)
261             NOT_REACHED ();
262
263           free (keep_vars);
264           free (all_vars);
265
266           vm.drop_vars = drop_vars;
267           vm.drop_cnt = drop_cnt;
268         }
269       else if (lex_match_id (lexer, "DROP"))
270         {
271           struct variable **drop_vars;
272           size_t drop_cnt;
273
274           if (already_encountered & 4)
275             {
276               msg (SE, _("DROP subcommand may be given at most once.  It may "
277                          "not be given in conjunction with the KEEP "
278                          "subcommand."));
279               goto done;
280             }
281           already_encountered |= 4;
282
283           lex_match (lexer, '=');
284           if (!parse_variables (lexer, dataset_dict (ds), &drop_vars, &drop_cnt, PV_NONE))
285             goto done;
286           vm.drop_vars = drop_vars;
287           vm.drop_cnt = drop_cnt;
288         }
289       else if (lex_match_id (lexer, "MAP"))
290         {
291           struct dictionary *temp = dict_clone (dataset_dict (ds));
292           int success = rearrange_dict (temp, &vm);
293           if (success)
294             {
295               /* FIXME: display new dictionary. */
296             }
297           dict_destroy (temp);
298         }
299       else
300         {
301           if (lex_token (lexer) == T_ID)
302             msg (SE, _("Unrecognized subcommand name `%s'."), lex_tokid (lexer));
303           else
304             msg (SE, _("Subcommand name expected."));
305           goto done;
306         }
307
308       if (lex_token (lexer) == '.')
309         break;
310       if (lex_token (lexer) != '/')
311         {
312           msg (SE, _("`/' or `.' expected."));
313           goto done;
314         }
315       lex_get (lexer);
316     }
317
318   if (already_encountered & (1 | 4))
319     {
320       /* Read the data. */
321       if (!proc_execute (ds))
322         goto done;
323     }
324
325   if (!rearrange_dict (dataset_dict (ds), &vm))
326     goto done;
327
328   ret_code = CMD_SUCCESS;
329
330 done:
331   free (vm.reorder_vars);
332   free (vm.rename_vars);
333   for (i = 0; i < vm.rename_cnt; i++)
334     free (vm.new_names[i]);
335   free (vm.new_names);
336   free (vm.drop_vars);
337   return ret_code;
338 }
339
340 /* Compares A and B according to the settings in
341    ORDERING, returning a strcmp()-type result. */
342 static int
343 compare_variables_given_ordering (const void *a_, const void *b_,
344                                   const void *ordering_)
345 {
346   struct variable *const *pa = a_;
347   struct variable *const *pb = b_;
348   const struct variable *a = *pa;
349   const struct variable *b = *pb;
350   const struct ordering *ordering = ordering_;
351
352   int result;
353   if (ordering->positional)
354     {
355       size_t a_index = var_get_dict_index (a);
356       size_t b_index = var_get_dict_index (b);
357       result = a_index < b_index ? -1 : a_index > b_index;
358     }
359   else
360     result = strcasecmp (var_get_name (a), var_get_name (b));
361   if (!ordering->forward)
362     result = -result;
363   return result;
364 }
365
366 /* Pairs a variable with a new name. */
367 struct var_renaming
368   {
369     struct variable *var;
370     char new_name[LONG_NAME_LEN + 1];
371   };
372
373 /* A algo_compare_func that compares new_name members in struct
374    var_renaming structures A and B. */
375 static int
376 compare_var_renaming_by_new_name (const void *a_, const void *b_,
377                                   const void *aux UNUSED)
378 {
379   const struct var_renaming *a = a_;
380   const struct var_renaming *b = b_;
381
382   return strcasecmp (a->new_name, b->new_name);
383 }
384
385 /* Returns true if performing VM on dictionary D would not cause
386    problems such as duplicate variable names.  Returns false
387    otherwise, and issues an error message. */
388 static int
389 validate_var_modification (const struct dictionary *d,
390                            const struct var_modification *vm)
391 {
392   /* Variable reordering can't be a problem, so we don't simulate
393      it.  Variable renaming can cause duplicate names, but
394      dropping variables can eliminate them, so we simulate both
395      of those. */
396   struct variable **all_vars;
397   struct variable **keep_vars;
398   struct variable **drop_vars;
399   size_t keep_cnt, drop_cnt;
400   size_t all_cnt;
401
402   struct var_renaming *var_renaming;
403   int valid;
404   size_t i;
405
406   /* All variables, in index order. */
407   dict_get_vars_mutable (d, &all_vars, &all_cnt, 0);
408
409   /* Drop variables, in index order. */
410   drop_cnt = vm->drop_cnt;
411   drop_vars = xnmalloc (drop_cnt, sizeof *drop_vars);
412   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
413   sort (drop_vars, drop_cnt, sizeof *drop_vars,
414         compare_variables_given_ordering, &forward_positional_ordering);
415
416   /* Keep variables, in index order. */
417   assert (all_cnt >= drop_cnt);
418   keep_cnt = all_cnt - drop_cnt;
419   keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
420   if (set_difference (all_vars, all_cnt,
421                       drop_vars, drop_cnt,
422                       sizeof *all_vars,
423                       keep_vars,
424                       compare_variables_given_ordering,
425                       &forward_positional_ordering) != keep_cnt)
426     NOT_REACHED ();
427
428   /* Copy variables into var_renaming array. */
429   var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
430   for (i = 0; i < keep_cnt; i++)
431     {
432       var_renaming[i].var = keep_vars[i];
433       strcpy (var_renaming[i].new_name, var_get_name (keep_vars[i]));
434     }
435
436   /* Rename variables in var_renaming array. */
437   for (i = 0; i < vm->rename_cnt; i++)
438     {
439       struct variable *const *kv;
440       struct var_renaming *vr;
441
442       /* Get the var_renaming element. */
443       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
444                           &vm->rename_vars[i],
445                           compare_variables_given_ordering,
446                           &forward_positional_ordering);
447       if (kv == NULL)
448         continue;
449       vr = var_renaming + (kv - keep_vars);
450
451       strcpy (vr->new_name, vm->new_names[i]);
452     }
453
454   /* Sort var_renaming array by new names and check for
455      duplicates. */
456   sort (var_renaming, keep_cnt, sizeof *var_renaming,
457         compare_var_renaming_by_new_name, NULL);
458   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
459                                compare_var_renaming_by_new_name, NULL) == NULL;
460
461   /* Clean up. */
462   free (all_vars);
463   free (keep_vars);
464   free (drop_vars);
465   free (var_renaming);
466
467   return valid;
468 }
469
470 /* Reoders, removes, and renames variables in dictionary D
471    according to VM.  Returns true if successful, false if there
472    would have been duplicate variable names if the modifications
473    had been carried out.  In the latter case, the dictionary is
474    not modified. */
475 static bool
476 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
477 {
478   char **rename_old_names;
479
480   struct variable **rename_vars;
481   char **rename_new_names;
482   size_t rename_cnt;
483
484   size_t i;
485
486   /* Check whether the modifications will cause duplicate
487      names. */
488   if (!validate_var_modification (d, vm))
489     return false;
490
491   /* Record the old names of variables to rename.  After
492      variables are deleted, we can't depend on the variables to
493      still exist, but we can still look them up by name. */
494   rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
495   for (i = 0; i < vm->rename_cnt; i++)
496     rename_old_names[i] = xstrdup (var_get_name (vm->rename_vars[i]));
497
498   /* Reorder and delete variables. */
499   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
500   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
501
502   /* Compose lists of variables to rename and their new names. */
503   rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
504   rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
505   rename_cnt = 0;
506   for (i = 0; i < vm->rename_cnt; i++)
507     {
508       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
509       if (var == NULL)
510         continue;
511
512       rename_vars[rename_cnt] = var;
513       rename_new_names[rename_cnt] = vm->new_names[i];
514       rename_cnt++;
515     }
516
517   /* Do renaming. */
518   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
519                         NULL) == 0)
520     NOT_REACHED ();
521
522   /* Clean up. */
523   for (i = 0; i < vm->rename_cnt; i++)
524     free (rename_old_names[i]);
525   free (rename_old_names);
526   free (rename_vars);
527   free (rename_new_names);
528
529   return true;
530 }