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