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