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