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