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