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