Change type of variables whose addresses are passed to dict_get_vars()
[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., 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 keep_cnt, drop_cnt;
394   int all_cnt;
395
396   struct var_renaming *var_renaming;
397   int valid;
398   size_t i;
399
400   /* All variables, in index order. */
401   dict_get_vars (d, &all_vars, &all_cnt, 0);
402
403   /* Drop variables, in index order. */
404   drop_cnt = vm->drop_cnt;
405   drop_vars = xmalloc (drop_cnt * sizeof *drop_vars);
406   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
407   sort (drop_vars, drop_cnt, sizeof *drop_vars,
408         compare_variables_given_ordering, &forward_positional_ordering);
409
410   /* Keep variables, in index order. */
411   keep_cnt = all_cnt - drop_cnt;
412   keep_vars = xmalloc (keep_cnt * sizeof *keep_vars);
413   if (set_difference (all_vars, all_cnt,
414                       drop_vars, drop_cnt,
415                       sizeof *all_vars,
416                       keep_vars,
417                       compare_variables_given_ordering,
418                       &forward_positional_ordering) != keep_cnt)
419     assert (0);
420
421   /* Copy variables into var_renaming array. */
422   var_renaming = xmalloc (keep_cnt * sizeof *var_renaming);
423   for (i = 0; i < keep_cnt; i++) 
424     {
425       var_renaming[i].var = keep_vars[i];
426       strcpy (var_renaming[i].new_name, keep_vars[i]->name);
427     }
428   
429   /* Rename variables in var_renaming array. */
430   for (i = 0; i < vm->rename_cnt; i++) 
431     {
432       struct variable *const *kv;
433       struct var_renaming *vr;
434
435       /* Get the var_renaming element. */
436       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
437                           &vm->rename_vars[i],
438                           compare_variables_given_ordering,
439                           &forward_positional_ordering);
440       if (kv == NULL)
441         continue;
442       vr = var_renaming + (kv - keep_vars);
443
444       strcpy (vr->new_name, vm->new_names[i]);
445     }
446
447   /* Sort var_renaming array by new names and check for
448      duplicates. */
449   sort (var_renaming, keep_cnt, sizeof *var_renaming,
450         compare_var_renaming_by_new_name, NULL);
451   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
452                                compare_var_renaming_by_new_name, NULL) == NULL;
453
454   /* Clean up. */
455   free (all_vars);
456   free (keep_vars);
457   free (drop_vars);
458   free (var_renaming);
459
460   return valid;
461 }
462
463 /* Reoders, removes, and renames variables in dictionary D
464    according to VM.  Returns nonzero if successful, zero if there
465    would have been duplicate variable names if the modifications
466    had been carried out.  In the latter case, the dictionary is
467    not modified. */
468 static int
469 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
470 {
471   char **rename_old_names;
472
473   struct variable **rename_vars;
474   char **rename_new_names;
475   size_t rename_cnt;
476
477   size_t i;
478
479   /* Check whether the modifications will cause duplicate
480      names. */
481   if (!validate_var_modification (d, vm))
482     return 0;
483
484   /* Record the old names of variables to rename.  After
485      variables are deleted, we can't depend on the variables to
486      still exist, but we can still look them up by name. */
487   rename_old_names = xmalloc (vm->rename_cnt * sizeof *rename_old_names);
488   for (i = 0; i < vm->rename_cnt; i++)
489     rename_old_names[i] = xstrdup (vm->rename_vars[i]->name);
490
491   /* Reorder and delete variables. */
492   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
493   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
494
495   /* Compose lists of variables to rename and their new names. */
496   rename_vars = xmalloc (vm->rename_cnt * sizeof *rename_vars);
497   rename_new_names = xmalloc (vm->rename_cnt * sizeof *rename_new_names);
498   rename_cnt = 0;
499   for (i = 0; i < vm->rename_cnt; i++)
500     {
501       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
502       if (var == NULL)
503         continue;
504       
505       rename_vars[rename_cnt] = var;
506       rename_new_names[rename_cnt] = vm->new_names[i];
507       rename_cnt++;
508     }
509
510   /* Do renaming. */
511   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
512                         NULL) == 0)
513     assert (0);
514
515   /* Clean up. */
516   for (i = 0; i < vm->rename_cnt; i++)
517     free (rename_old_names[i]);
518   free (rename_old_names);
519   free (rename_vars);
520   free (rename_new_names);
521
522   return 1;
523 }