Continue reforming procedure execution. In this phase, move
[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/procedure.h>
35 #include <data/variable.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 (proc_make_temporary_transformations_permanent ())
92     msg (SE, _("MODIFY VARS may not be used after TEMPORARY.  "
93                "Temporary transformations will be made permanent."));
94
95   vm.reorder_vars = NULL;
96   vm.reorder_cnt = 0;
97   vm.rename_vars = NULL;
98   vm.new_names = NULL;
99   vm.rename_cnt = 0;
100   vm.drop_vars = NULL;
101   vm.drop_cnt = 0;
102
103   /* Parse each subcommand. */
104   lex_match ('/');
105   for (;;)
106     {
107       if (lex_match_id ("REORDER"))
108         {
109           struct variable **v = NULL;
110           size_t nv = 0;
111
112           if (already_encountered & 1)
113             {
114               msg (SE, _("REORDER subcommand may be given at most once."));
115               goto done;
116             }
117           already_encountered |= 1;
118
119           lex_match ('=');
120           do
121             {
122               struct ordering ordering;
123               size_t prev_nv = nv;
124
125               ordering.forward = ordering.positional = 1;
126               if (lex_match_id ("FORWARD"));
127               else if (lex_match_id ("BACKWARD"))
128                 ordering.forward = 0;
129               if (lex_match_id ("POSITIONAL"));
130               else if (lex_match_id ("ALPHA"))
131                 ordering.positional = 0;
132
133               if (lex_match (T_ALL) || token == '/' || token == '.')
134                 {
135                   if (prev_nv != 0)
136                     {
137                       msg (SE, _("Cannot specify ALL after specifying a set "
138                            "of variables."));
139                       goto done;
140                     }
141                   dict_get_vars (default_dict, &v, &nv, 1u << DC_SYSTEM);
142                 }
143               else
144                 {
145                   if (!lex_match ('('))
146                     {
147                       msg (SE, _("`(' expected on REORDER subcommand."));
148                       free (v);
149                       goto done;
150                     }
151                   if (!parse_variables (default_dict, &v, &nv,
152                                         PV_APPEND | PV_NO_DUPLICATE))
153                     {
154                       free (v);
155                       goto done;
156                     }
157                   if (!lex_match (')'))
158                     {
159                       msg (SE, _("`)' expected following variable names on "
160                            "REORDER subcommand."));
161                       free (v);
162                       goto done;
163                     }
164                 }
165               sort (&v[prev_nv], nv - prev_nv, sizeof *v,
166                     compare_variables_given_ordering, &ordering);
167             }
168           while (token != '/' && token != '.');
169
170           vm.reorder_vars = v;
171           vm.reorder_cnt = nv;
172         }
173       else if (lex_match_id ("RENAME"))
174         {
175           if (already_encountered & 2)
176             {
177               msg (SE, _("RENAME subcommand may be given at most once."));
178               goto done;
179             }
180           already_encountered |= 2;
181
182           lex_match ('=');
183           do
184             {
185               size_t prev_nv_1 = vm.rename_cnt;
186               size_t prev_nv_2 = vm.rename_cnt;
187
188               if (!lex_match ('('))
189                 {
190                   msg (SE, _("`(' expected on RENAME subcommand."));
191                   goto done;
192                 }
193               if (!parse_variables (default_dict, &vm.rename_vars, &vm.rename_cnt,
194                                     PV_APPEND | PV_NO_DUPLICATE))
195                 goto done;
196               if (!lex_match ('='))
197                 {
198                   msg (SE, _("`=' expected between lists of new and old variable "
199                        "names on RENAME subcommand."));
200                   goto done;
201                 }
202               if (!parse_DATA_LIST_vars (&vm.new_names, &prev_nv_1, PV_APPEND))
203                 goto done;
204               if (prev_nv_1 != vm.rename_cnt)
205                 {
206                   msg (SE, _("Differing number of variables in old name list "
207                        "(%d) and in new name list (%d)."),
208                        vm.rename_cnt - prev_nv_2, prev_nv_1 - prev_nv_2);
209                   for (i = 0; i < prev_nv_1; i++)
210                     free (vm.new_names[i]);
211                   free (vm.new_names);
212                   vm.new_names = NULL;
213                   goto done;
214                 }
215               if (!lex_match (')'))
216                 {
217                   msg (SE, _("`)' expected after variable lists on RENAME "
218                        "subcommand."));
219                   goto done;
220                 }
221             }
222           while (token != '.' && token != '/');
223         }
224       else if (lex_match_id ("KEEP"))
225         {
226           struct variable **keep_vars, **all_vars, **drop_vars;
227           size_t keep_cnt, all_cnt, drop_cnt;
228
229           if (already_encountered & 4)
230             {
231               msg (SE, _("KEEP subcommand may be given at most once.  It may not"
232                    "be given in conjunction with the DROP subcommand."));
233               goto done;
234             }
235           already_encountered |= 4;
236
237           lex_match ('=');
238           if (!parse_variables (default_dict, &keep_vars, &keep_cnt, PV_NONE))
239             goto done;
240
241           /* Transform the list of variables to keep into a list of
242              variables to drop.  First sort the keep list, then figure
243              out which variables are missing. */
244           sort (keep_vars, keep_cnt, sizeof *keep_vars,
245                 compare_variables_given_ordering, &forward_positional_ordering);
246
247           dict_get_vars (default_dict, &all_vars, &all_cnt, 0);
248           assert (all_cnt >= keep_cnt);
249
250           drop_cnt = all_cnt - keep_cnt;
251           drop_vars = xnmalloc (drop_cnt, sizeof *keep_vars);
252           if (set_difference (all_vars, all_cnt,
253                               keep_vars, keep_cnt,
254                               sizeof *all_vars,
255                               drop_vars,
256                               compare_variables_given_ordering,
257                               &forward_positional_ordering)
258               != drop_cnt)
259             assert (0);
260
261           free (keep_vars);
262           free (all_vars);
263
264           vm.drop_vars = drop_vars;
265           vm.drop_cnt = drop_cnt;
266         }
267       else if (lex_match_id ("DROP"))
268         {
269           struct variable **drop_vars;
270           size_t drop_cnt;
271
272           if (already_encountered & 4)
273             {
274               msg (SE, _("DROP subcommand may be given at most once.  It may "
275                          "not be given in conjunction with the KEEP "
276                          "subcommand."));
277               goto done;
278             }
279           already_encountered |= 4;
280
281           lex_match ('=');
282           if (!parse_variables (default_dict, &drop_vars, &drop_cnt, PV_NONE))
283             goto done;
284           vm.drop_vars = drop_vars;
285           vm.drop_cnt = drop_cnt;
286         }
287       else if (lex_match_id ("MAP"))
288         {
289           struct dictionary *temp = dict_clone (default_dict);
290           int success = rearrange_dict (temp, &vm);
291           if (success) 
292             {
293               /* FIXME: display new dictionary. */ 
294             }
295           dict_destroy (temp);
296         }
297       else
298         {
299           if (token == T_ID)
300             msg (SE, _("Unrecognized subcommand name `%s'."), tokid);
301           else
302             msg (SE, _("Subcommand name expected."));
303           goto done;
304         }
305
306       if (token == '.')
307         break;
308       if (token != '/')
309         {
310           msg (SE, _("`/' or `.' expected."));
311           goto done;
312         }
313       lex_get ();
314     }
315
316   if (already_encountered & (1 | 4))
317     {
318       /* Read the data. */
319       if (!procedure (NULL, NULL)) 
320         goto done; 
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   size_t 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 = xnmalloc (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   assert (all_cnt >= drop_cnt);
412   keep_cnt = all_cnt - drop_cnt;
413   keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
414   if (set_difference (all_vars, all_cnt,
415                       drop_vars, drop_cnt,
416                       sizeof *all_vars,
417                       keep_vars,
418                       compare_variables_given_ordering,
419                       &forward_positional_ordering) != keep_cnt)
420     assert (0);
421
422   /* Copy variables into var_renaming array. */
423   var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
424   for (i = 0; i < keep_cnt; i++) 
425     {
426       var_renaming[i].var = keep_vars[i];
427       strcpy (var_renaming[i].new_name, keep_vars[i]->name);
428     }
429   
430   /* Rename variables in var_renaming array. */
431   for (i = 0; i < vm->rename_cnt; i++) 
432     {
433       struct variable *const *kv;
434       struct var_renaming *vr;
435
436       /* Get the var_renaming element. */
437       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
438                           &vm->rename_vars[i],
439                           compare_variables_given_ordering,
440                           &forward_positional_ordering);
441       if (kv == NULL)
442         continue;
443       vr = var_renaming + (kv - keep_vars);
444
445       strcpy (vr->new_name, vm->new_names[i]);
446     }
447
448   /* Sort var_renaming array by new names and check for
449      duplicates. */
450   sort (var_renaming, keep_cnt, sizeof *var_renaming,
451         compare_var_renaming_by_new_name, NULL);
452   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
453                                compare_var_renaming_by_new_name, NULL) == NULL;
454
455   /* Clean up. */
456   free (all_vars);
457   free (keep_vars);
458   free (drop_vars);
459   free (var_renaming);
460
461   return valid;
462 }
463
464 /* Reoders, removes, and renames variables in dictionary D
465    according to VM.  Returns nonzero if successful, zero if there
466    would have been duplicate variable names if the modifications
467    had been carried out.  In the latter case, the dictionary is
468    not modified. */
469 static int
470 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
471 {
472   char **rename_old_names;
473
474   struct variable **rename_vars;
475   char **rename_new_names;
476   size_t rename_cnt;
477
478   size_t i;
479
480   /* Check whether the modifications will cause duplicate
481      names. */
482   if (!validate_var_modification (d, vm))
483     return 0;
484
485   /* Record the old names of variables to rename.  After
486      variables are deleted, we can't depend on the variables to
487      still exist, but we can still look them up by name. */
488   rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
489   for (i = 0; i < vm->rename_cnt; i++)
490     rename_old_names[i] = xstrdup (vm->rename_vars[i]->name);
491
492   /* Reorder and delete variables. */
493   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
494   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
495
496   /* Compose lists of variables to rename and their new names. */
497   rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
498   rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
499   rename_cnt = 0;
500   for (i = 0; i < vm->rename_cnt; i++)
501     {
502       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
503       if (var == NULL)
504         continue;
505       
506       rename_vars[rename_cnt] = var;
507       rename_new_names[rename_cnt] = vm->new_names[i];
508       rename_cnt++;
509     }
510
511   /* Do renaming. */
512   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
513                         NULL) == 0)
514     assert (0);
515
516   /* Clean up. */
517   for (i = 0; i < vm->rename_cnt; i++)
518     free (rename_old_names[i]);
519   free (rename_old_names);
520   free (rename_vars);
521   free (rename_new_names);
522
523   return 1;
524 }