fc91894eee2f14bf326a32a602016d9cc21d0529
[pspp-builds.git] / src / language / dictionary / modify-variables.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 "message.h"
23 #include "array.h"
24 #include "alloc.h"
25 #include "bit-vector.h"
26 #include "command.h"
27 #include "dictionary.h"
28 #include "message.h"
29 #include "hash.h"
30 #include "lexer.h"
31 #include "misc.h"
32 #include "str.h"
33 #include "variable.h"
34 #include "procedure.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_CASCADING_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       if (!procedure (NULL, NULL)) 
322         goto done; 
323     }
324
325   if (!rearrange_dict (default_dict, &vm))
326     goto done; 
327
328   ret_code = CMD_SUCCESS;
329
330 done:
331   free (vm.reorder_vars);
332   free (vm.rename_vars);
333   for (i = 0; i < vm.rename_cnt; i++)
334     free (vm.new_names[i]);
335   free (vm.new_names);
336   free (vm.drop_vars);
337   return ret_code;
338 }
339
340 /* Compares A and B according to the settings in
341    ORDERING, returning a strcmp()-type result. */
342 static int
343 compare_variables_given_ordering (const void *a_, const void *b_,
344                                   void *ordering_)
345 {
346   struct variable *const *pa = a_;
347   struct variable *const *pb = b_;
348   const struct variable *a = *pa;
349   const struct variable *b = *pb;
350   const struct ordering *ordering = ordering_;
351
352   int result;
353   if (ordering->positional)
354     result = a->index < b->index ? -1 : a->index > b->index;
355   else
356     result = strcasecmp (a->name, b->name);
357   if (!ordering->forward)
358     result = -result;
359   return result;
360 }
361
362 /* Pairs a variable with a new name. */
363 struct var_renaming
364   {
365     struct variable *var;
366     char new_name[LONG_NAME_LEN + 1];
367   };
368
369 /* A algo_compare_func that compares new_name members in struct
370    var_renaming structures A and B. */
371 static int
372 compare_var_renaming_by_new_name (const void *a_, const void *b_,
373                                   void *foo UNUSED) 
374 {
375   const struct var_renaming *a = a_;
376   const struct var_renaming *b = b_;
377
378   return strcasecmp (a->new_name, b->new_name);
379 }
380
381 /* Returns true if performing VM on dictionary D would not cause
382    problems such as duplicate variable names.  Returns false
383    otherwise, and issues an error message. */
384 static int
385 validate_var_modification (const struct dictionary *d,
386                            const struct var_modification *vm) 
387 {
388   /* Variable reordering can't be a problem, so we don't simulate
389      it.  Variable renaming can cause duplicate names, but
390      dropping variables can eliminate them, so we simulate both
391      of those. */
392   struct variable **all_vars;
393   struct variable **keep_vars;
394   struct variable **drop_vars;
395   size_t keep_cnt, drop_cnt;
396   size_t all_cnt;
397
398   struct var_renaming *var_renaming;
399   int valid;
400   size_t i;
401
402   /* All variables, in index order. */
403   dict_get_vars (d, &all_vars, &all_cnt, 0);
404
405   /* Drop variables, in index order. */
406   drop_cnt = vm->drop_cnt;
407   drop_vars = xnmalloc (drop_cnt, sizeof *drop_vars);
408   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
409   sort (drop_vars, drop_cnt, sizeof *drop_vars,
410         compare_variables_given_ordering, &forward_positional_ordering);
411
412   /* Keep variables, in index order. */
413   assert (all_cnt >= drop_cnt);
414   keep_cnt = all_cnt - drop_cnt;
415   keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
416   if (set_difference (all_vars, all_cnt,
417                       drop_vars, drop_cnt,
418                       sizeof *all_vars,
419                       keep_vars,
420                       compare_variables_given_ordering,
421                       &forward_positional_ordering) != keep_cnt)
422     assert (0);
423
424   /* Copy variables into var_renaming array. */
425   var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
426   for (i = 0; i < keep_cnt; i++) 
427     {
428       var_renaming[i].var = keep_vars[i];
429       strcpy (var_renaming[i].new_name, keep_vars[i]->name);
430     }
431   
432   /* Rename variables in var_renaming array. */
433   for (i = 0; i < vm->rename_cnt; i++) 
434     {
435       struct variable *const *kv;
436       struct var_renaming *vr;
437
438       /* Get the var_renaming element. */
439       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
440                           &vm->rename_vars[i],
441                           compare_variables_given_ordering,
442                           &forward_positional_ordering);
443       if (kv == NULL)
444         continue;
445       vr = var_renaming + (kv - keep_vars);
446
447       strcpy (vr->new_name, vm->new_names[i]);
448     }
449
450   /* Sort var_renaming array by new names and check for
451      duplicates. */
452   sort (var_renaming, keep_cnt, sizeof *var_renaming,
453         compare_var_renaming_by_new_name, NULL);
454   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
455                                compare_var_renaming_by_new_name, NULL) == NULL;
456
457   /* Clean up. */
458   free (all_vars);
459   free (keep_vars);
460   free (drop_vars);
461   free (var_renaming);
462
463   return valid;
464 }
465
466 /* Reoders, removes, and renames variables in dictionary D
467    according to VM.  Returns nonzero if successful, zero if there
468    would have been duplicate variable names if the modifications
469    had been carried out.  In the latter case, the dictionary is
470    not modified. */
471 static int
472 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
473 {
474   char **rename_old_names;
475
476   struct variable **rename_vars;
477   char **rename_new_names;
478   size_t rename_cnt;
479
480   size_t i;
481
482   /* Check whether the modifications will cause duplicate
483      names. */
484   if (!validate_var_modification (d, vm))
485     return 0;
486
487   /* Record the old names of variables to rename.  After
488      variables are deleted, we can't depend on the variables to
489      still exist, but we can still look them up by name. */
490   rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
491   for (i = 0; i < vm->rename_cnt; i++)
492     rename_old_names[i] = xstrdup (vm->rename_vars[i]->name);
493
494   /* Reorder and delete variables. */
495   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
496   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
497
498   /* Compose lists of variables to rename and their new names. */
499   rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
500   rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
501   rename_cnt = 0;
502   for (i = 0; i < vm->rename_cnt; i++)
503     {
504       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
505       if (var == NULL)
506         continue;
507       
508       rename_vars[rename_cnt] = var;
509       rename_new_names[rename_cnt] = vm->new_names[i];
510       rename_cnt++;
511     }
512
513   /* Do renaming. */
514   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
515                         NULL) == 0)
516     assert (0);
517
518   /* Clean up. */
519   for (i = 0; i < vm->rename_cnt; i++)
520     free (rename_old_names[i]);
521   free (rename_old_names);
522   free (rename_vars);
523   free (rename_new_names);
524
525   return 1;
526 }