4d01b6a942399b7b1568d77d53b4c76829758c09
[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           size_t 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               size_t 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               size_t prev_nv_1 = vm.rename_cnt;
188               size_t 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           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 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           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             assert (0);
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 ("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 ('=');
284           if (!parse_variables (default_dict, &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 ("MAP"))
290         {
291           struct dictionary *temp = dict_clone (default_dict);
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 (token == T_ID)
302             msg (SE, _("Unrecognized subcommand name `%s'."), tokid);
303           else
304             msg (SE, _("Subcommand name expected."));
305           goto done;
306         }
307
308       if (token == '.')
309         break;
310       if (token != '/')
311         {
312           msg (SE, _("`/' or `.' expected."));
313           goto done;
314         }
315       lex_get ();
316     }
317
318   if (already_encountered & (1 | 4))
319     {
320       /* Read the data. */
321       procedure (NULL, NULL);
322     }
323
324   if (!rearrange_dict (default_dict, &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                                   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     result = a->index < b->index ? -1 : a->index > b->index;
354   else
355     result = strcasecmp (a->name, b->name);
356   if (!ordering->forward)
357     result = -result;
358   return result;
359 }
360
361 /* Pairs a variable with a new name. */
362 struct var_renaming
363   {
364     struct variable *var;
365     char new_name[LONG_NAME_LEN + 1];
366   };
367
368 /* A algo_compare_func that compares new_name members in struct
369    var_renaming structures A and B. */
370 static int
371 compare_var_renaming_by_new_name (const void *a_, const void *b_,
372                                   void *foo UNUSED) 
373 {
374   const struct var_renaming *a = a_;
375   const struct var_renaming *b = b_;
376
377   return strcasecmp (a->new_name, b->new_name);
378 }
379
380 /* Returns true if performing VM on dictionary D would not cause
381    problems such as duplicate variable names.  Returns false
382    otherwise, and issues an error message. */
383 static int
384 validate_var_modification (const struct dictionary *d,
385                            const struct var_modification *vm) 
386 {
387   /* Variable reordering can't be a problem, so we don't simulate
388      it.  Variable renaming can cause duplicate names, but
389      dropping variables can eliminate them, so we simulate both
390      of those. */
391   struct variable **all_vars;
392   struct variable **keep_vars;
393   struct variable **drop_vars;
394   size_t keep_cnt, drop_cnt;
395   size_t all_cnt;
396
397   struct var_renaming *var_renaming;
398   int valid;
399   size_t i;
400
401   /* All variables, in index order. */
402   dict_get_vars (d, &all_vars, &all_cnt, 0);
403
404   /* Drop variables, in index order. */
405   drop_cnt = vm->drop_cnt;
406   drop_vars = xnmalloc (drop_cnt, sizeof *drop_vars);
407   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
408   sort (drop_vars, drop_cnt, sizeof *drop_vars,
409         compare_variables_given_ordering, &forward_positional_ordering);
410
411   /* Keep variables, in index order. */
412   assert (all_cnt >= drop_cnt);
413   keep_cnt = all_cnt - drop_cnt;
414   keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
415   if (set_difference (all_vars, all_cnt,
416                       drop_vars, drop_cnt,
417                       sizeof *all_vars,
418                       keep_vars,
419                       compare_variables_given_ordering,
420                       &forward_positional_ordering) != keep_cnt)
421     assert (0);
422
423   /* Copy variables into var_renaming array. */
424   var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
425   for (i = 0; i < keep_cnt; i++) 
426     {
427       var_renaming[i].var = keep_vars[i];
428       strcpy (var_renaming[i].new_name, keep_vars[i]->name);
429     }
430   
431   /* Rename variables in var_renaming array. */
432   for (i = 0; i < vm->rename_cnt; i++) 
433     {
434       struct variable *const *kv;
435       struct var_renaming *vr;
436
437       /* Get the var_renaming element. */
438       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
439                           &vm->rename_vars[i],
440                           compare_variables_given_ordering,
441                           &forward_positional_ordering);
442       if (kv == NULL)
443         continue;
444       vr = var_renaming + (kv - keep_vars);
445
446       strcpy (vr->new_name, vm->new_names[i]);
447     }
448
449   /* Sort var_renaming array by new names and check for
450      duplicates. */
451   sort (var_renaming, keep_cnt, sizeof *var_renaming,
452         compare_var_renaming_by_new_name, NULL);
453   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
454                                compare_var_renaming_by_new_name, NULL) == NULL;
455
456   /* Clean up. */
457   free (all_vars);
458   free (keep_vars);
459   free (drop_vars);
460   free (var_renaming);
461
462   return valid;
463 }
464
465 /* Reoders, removes, and renames variables in dictionary D
466    according to VM.  Returns nonzero if successful, zero if there
467    would have been duplicate variable names if the modifications
468    had been carried out.  In the latter case, the dictionary is
469    not modified. */
470 static int
471 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
472 {
473   char **rename_old_names;
474
475   struct variable **rename_vars;
476   char **rename_new_names;
477   size_t rename_cnt;
478
479   size_t i;
480
481   /* Check whether the modifications will cause duplicate
482      names. */
483   if (!validate_var_modification (d, vm))
484     return 0;
485
486   /* Record the old names of variables to rename.  After
487      variables are deleted, we can't depend on the variables to
488      still exist, but we can still look them up by name. */
489   rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
490   for (i = 0; i < vm->rename_cnt; i++)
491     rename_old_names[i] = xstrdup (vm->rename_vars[i]->name);
492
493   /* Reorder and delete variables. */
494   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
495   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
496
497   /* Compose lists of variables to rename and their new names. */
498   rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
499   rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
500   rename_cnt = 0;
501   for (i = 0; i < vm->rename_cnt; i++)
502     {
503       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
504       if (var == NULL)
505         continue;
506       
507       rename_vars[rename_cnt] = var;
508       rename_new_names[rename_cnt] = vm->new_names[i];
509       rename_cnt++;
510     }
511
512   /* Do renaming. */
513   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
514                         NULL) == 0)
515     assert (0);
516
517   /* Clean up. */
518   for (i = 0; i < vm->rename_cnt; i++)
519     free (rename_old_names[i]);
520   free (rename_old_names);
521   free (rename_vars);
522   free (rename_new_names);
523
524   return 1;
525 }