Revise.
[pspp] / src / modify-vars.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 #include <stdlib.h>
22 #include "error.h"
23 #include "algorithm.h"
24 #include "alloc.h"
25 #include "bitvector.h"
26 #include "command.h"
27 #include "dictionary.h"
28 #include "error.h"
29 #include "hash.h"
30 #include "lexer.h"
31 #include "misc.h"
32 #include "str.h"
33 #include "var.h"
34 #include "vfm.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38
39 /* FIXME: should change weighting variable, etc. */
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                                              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 int rearrange_dict (struct dictionary *d,
72                            const struct var_modification *vm);
73
74 /* Performs MODIFY VARS command. */
75 int
76 cmd_modify_vars (void)
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_FAILURE;
87
88   size_t i;
89
90   if (temporary != 0)
91     {
92       msg (SE, _("MODIFY VARS may not be used after TEMPORARY.  "
93                  "Temporary transformations will be made permanent."));
94       cancel_temporary (); 
95     }
96
97   vm.reorder_vars = NULL;
98   vm.reorder_cnt = 0;
99   vm.rename_vars = NULL;
100   vm.new_names = NULL;
101   vm.rename_cnt = 0;
102   vm.drop_vars = NULL;
103   vm.drop_cnt = 0;
104
105   /* Parse each subcommand. */
106   lex_match ('/');
107   for (;;)
108     {
109       if (lex_match_id ("REORDER"))
110         {
111           struct variable **v = NULL;
112           int nv = 0;
113
114           if (already_encountered & 1)
115             {
116               msg (SE, _("REORDER subcommand may be given at most once."));
117               goto done;
118             }
119           already_encountered |= 1;
120
121           lex_match ('=');
122           do
123             {
124               struct ordering ordering;
125               int prev_nv = nv;
126
127               ordering.forward = ordering.positional = 1;
128               if (lex_match_id ("FORWARD"));
129               else if (lex_match_id ("BACKWARD"))
130                 ordering.forward = 0;
131               if (lex_match_id ("POSITIONAL"));
132               else if (lex_match_id ("ALPHA"))
133                 ordering.positional = 0;
134
135               if (lex_match (T_ALL) || token == '/' || token == '.')
136                 {
137                   if (prev_nv != 0)
138                     {
139                       msg (SE, _("Cannot specify ALL after specifying a set "
140                            "of variables."));
141                       goto done;
142                     }
143                   dict_get_vars (default_dict, &v, &nv, 1u << DC_SYSTEM);
144                 }
145               else
146                 {
147                   if (!lex_match ('('))
148                     {
149                       msg (SE, _("`(' expected on REORDER subcommand."));
150                       free (v);
151                       goto done;
152                     }
153                   if (!parse_variables (default_dict, &v, &nv,
154                                         PV_APPEND | PV_NO_DUPLICATE))
155                     {
156                       free (v);
157                       goto done;
158                     }
159                   if (!lex_match (')'))
160                     {
161                       msg (SE, _("`)' expected following variable names on "
162                            "REORDER subcommand."));
163                       free (v);
164                       goto done;
165                     }
166                 }
167               sort (&v[prev_nv], nv - prev_nv, sizeof *v,
168                     compare_variables_given_ordering, &ordering);
169             }
170           while (token != '/' && token != '.');
171
172           vm.reorder_vars = v;
173           vm.reorder_cnt = nv;
174         }
175       else if (lex_match_id ("RENAME"))
176         {
177           if (already_encountered & 2)
178             {
179               msg (SE, _("RENAME subcommand may be given at most once."));
180               goto done;
181             }
182           already_encountered |= 2;
183
184           lex_match ('=');
185           do
186             {
187               int prev_nv_1 = vm.rename_cnt;
188               int prev_nv_2 = vm.rename_cnt;
189
190               if (!lex_match ('('))
191                 {
192                   msg (SE, _("`(' expected on RENAME subcommand."));
193                   goto done;
194                 }
195               if (!parse_variables (default_dict, &vm.rename_vars, &vm.rename_cnt,
196                                     PV_APPEND | PV_NO_DUPLICATE))
197                 goto done;
198               if (!lex_match ('='))
199                 {
200                   msg (SE, _("`=' expected between lists of new and old variable "
201                        "names on RENAME subcommand."));
202                   goto done;
203                 }
204               if (!parse_DATA_LIST_vars (&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                        "(%d) and in new name list (%d)."),
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 (')'))
218                 {
219                   msg (SE, _("`)' expected after variable lists on RENAME "
220                        "subcommand."));
221                   goto done;
222                 }
223             }
224           while (token != '.' && token != '/');
225         }
226       else if (lex_match_id ("KEEP"))
227         {
228           struct variable **keep_vars, **all_vars, **drop_vars;
229           int 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 not"
234                    "be given in conjunction with the DROP subcommand."));
235               goto done;
236             }
237           already_encountered |= 4;
238
239           lex_match ('=');
240           if (!parse_variables (default_dict, &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 (default_dict, &all_vars, &all_cnt, 0);
250
251           drop_cnt = all_cnt - keep_cnt;
252           drop_vars = xmalloc (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             assert (0);
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 ("DROP"))
269         {
270           struct variable **drop_vars;
271           int 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 ('=');
283           if (!parse_variables (default_dict, &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 ("MAP"))
289         {
290           struct dictionary *temp = dict_clone (default_dict);
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 (token == T_ID)
301             msg (SE, _("Unrecognized subcommand name `%s'."), tokid);
302           else
303             msg (SE, _("Subcommand name expected."));
304           goto done;
305         }
306
307       if (token == '.')
308         break;
309       if (token != '/')
310         {
311           msg (SE, _("`/' or `.' expected."));
312           goto done;
313         }
314       lex_get ();
315     }
316
317   if (already_encountered & (1 | 4))
318     {
319       /* Read the data. */
320       procedure (NULL, NULL);
321     }
322
323   if (!rearrange_dict (default_dict, &vm))
324     goto done; 
325
326   ret_code = CMD_SUCCESS;
327
328 done:
329   free (vm.reorder_vars);
330   free (vm.rename_vars);
331   for (i = 0; i < vm.rename_cnt; i++)
332     free (vm.new_names[i]);
333   free (vm.new_names);
334   free (vm.drop_vars);
335   return ret_code;
336 }
337
338 /* Compares A and B according to the settings in
339    ORDERING, returning a strcmp()-type result. */
340 static int
341 compare_variables_given_ordering (const void *a_, const void *b_,
342                                   void *ordering_)
343 {
344   struct variable *const *pa = a_;
345   struct variable *const *pb = b_;
346   const struct variable *a = *pa;
347   const struct variable *b = *pb;
348   const struct ordering *ordering = ordering_;
349
350   int result;
351   if (ordering->positional)
352     result = a->index < b->index ? -1 : a->index > b->index;
353   else
354     result = strcasecmp (a->name, b->name);
355   if (!ordering->forward)
356     result = -result;
357   return result;
358 }
359
360 /* Pairs a variable with a new name. */
361 struct var_renaming
362   {
363     struct variable *var;
364     char new_name[LONG_NAME_LEN + 1];
365   };
366
367 /* A algo_compare_func that compares new_name members in struct
368    var_renaming structures A and B. */
369 static int
370 compare_var_renaming_by_new_name (const void *a_, const void *b_,
371                                   void *foo UNUSED) 
372 {
373   const struct var_renaming *a = a_;
374   const struct var_renaming *b = b_;
375
376   return strcasecmp (a->new_name, b->new_name);
377 }
378
379 /* Returns true if performing VM on dictionary D would not cause
380    problems such as duplicate variable names.  Returns false
381    otherwise, and issues an error message. */
382 static int
383 validate_var_modification (const struct dictionary *d,
384                            const struct var_modification *vm) 
385 {
386   /* Variable reordering can't be a problem, so we don't simulate
387      it.  Variable renaming can cause duplicate names, but
388      dropping variables can eliminate them, so we simulate both
389      of those. */
390   struct variable **all_vars;
391   struct variable **keep_vars;
392   struct variable **drop_vars;
393   size_t all_cnt, keep_cnt, drop_cnt;
394
395   struct var_renaming *var_renaming;
396   int valid;
397   size_t i;
398
399   /* All variables, in index order. */
400   dict_get_vars (d, &all_vars, &all_cnt, 0);
401
402   /* Drop variables, in index order. */
403   drop_cnt = vm->drop_cnt;
404   drop_vars = xmalloc (drop_cnt * sizeof *drop_vars);
405   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
406   sort (drop_vars, drop_cnt, sizeof *drop_vars,
407         compare_variables_given_ordering, &forward_positional_ordering);
408
409   /* Keep variables, in index order. */
410   keep_cnt = all_cnt - drop_cnt;
411   keep_vars = xmalloc (keep_cnt * sizeof *keep_vars);
412   if (set_difference (all_vars, all_cnt,
413                       drop_vars, drop_cnt,
414                       sizeof *all_vars,
415                       keep_vars,
416                       compare_variables_given_ordering,
417                       &forward_positional_ordering) != keep_cnt)
418     assert (0);
419
420   /* Copy variables into var_renaming array. */
421   var_renaming = xmalloc (keep_cnt * sizeof *var_renaming);
422   for (i = 0; i < keep_cnt; i++) 
423     {
424       var_renaming[i].var = keep_vars[i];
425       strcpy (var_renaming[i].new_name, keep_vars[i]->name);
426     }
427   
428   /* Rename variables in var_renaming array. */
429   for (i = 0; i < vm->rename_cnt; i++) 
430     {
431       struct variable *const *kv;
432       struct var_renaming *vr;
433
434       /* Get the var_renaming element. */
435       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
436                           &vm->rename_vars[i],
437                           compare_variables_given_ordering,
438                           &forward_positional_ordering);
439       if (kv == NULL)
440         continue;
441       vr = var_renaming + (kv - keep_vars);
442
443       strcpy (vr->new_name, vm->new_names[i]);
444     }
445
446   /* Sort var_renaming array by new names and check for
447      duplicates. */
448   sort (var_renaming, keep_cnt, sizeof *var_renaming,
449         compare_var_renaming_by_new_name, NULL);
450   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
451                                compare_var_renaming_by_new_name, NULL) == NULL;
452
453   /* Clean up. */
454   free (all_vars);
455   free (keep_vars);
456   free (drop_vars);
457   free (var_renaming);
458
459   return valid;
460 }
461
462 /* Reoders, removes, and renames variables in dictionary D
463    according to VM.  Returns nonzero if successful, zero if there
464    would have been duplicate variable names if the modifications
465    had been carried out.  In the latter case, the dictionary is
466    not modified. */
467 static int
468 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
469 {
470   char **rename_old_names;
471
472   struct variable **rename_vars;
473   char **rename_new_names;
474   size_t rename_cnt;
475
476   size_t i;
477
478   /* Check whether the modifications will cause duplicate
479      names. */
480   if (!validate_var_modification (d, vm))
481     return 0;
482
483   /* Record the old names of variables to rename.  After
484      variables are deleted, we can't depend on the variables to
485      still exist, but we can still look them up by name. */
486   rename_old_names = xmalloc (vm->rename_cnt * sizeof *rename_old_names);
487   for (i = 0; i < vm->rename_cnt; i++)
488     rename_old_names[i] = xstrdup (vm->rename_vars[i]->name);
489
490   /* Reorder and delete variables. */
491   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
492   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
493
494   /* Compose lists of variables to rename and their new names. */
495   rename_vars = xmalloc (vm->rename_cnt * sizeof *rename_vars);
496   rename_new_names = xmalloc (vm->rename_cnt * sizeof *rename_new_names);
497   rename_cnt = 0;
498   for (i = 0; i < vm->rename_cnt; i++)
499     {
500       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
501       if (var == NULL)
502         continue;
503       
504       rename_vars[rename_cnt] = var;
505       rename_new_names[rename_cnt] = vm->new_names[i];
506       rename_cnt++;
507     }
508
509   /* Do renaming. */
510   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
511                         NULL) == 0)
512     assert (0);
513
514   /* Clean up. */
515   for (i = 0; i < vm->rename_cnt; i++)
516     free (rename_old_names[i]);
517   free (rename_old_names);
518   free (rename_vars);
519   free (rename_new_names);
520
521   return 1;
522 }