Actually implement the new procedure code and adapt all of its clients
[pspp] / src / language / dictionary / modify-variables.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17    02110-1301, USA. */
18
19 #include <config.h>
20
21 #include <stdlib.h>
22
23 #include <data/dictionary.h>
24 #include <data/procedure.h>
25 #include <data/variable.h>
26 #include <language/command.h>
27 #include <language/lexer/lexer.h>
28 #include <language/lexer/variable-parser.h>
29 #include <libpspp/alloc.h>
30 #include <libpspp/assertion.h>
31 #include <libpspp/array.h>
32 #include <libpspp/bit-vector.h>
33 #include <libpspp/compiler.h>
34 #include <libpspp/hash.h>
35 #include <libpspp/message.h>
36 #include <libpspp/message.h>
37 #include <libpspp/misc.h>
38 #include <libpspp/str.h>
39
40 #include "gettext.h"
41 #define _(msgid) gettext (msgid)
42
43 /* These control the ordering produced by
44    compare_variables_given_ordering(). */
45 struct ordering
46   {
47     int forward;                /* 1=FORWARD, 0=BACKWARD. */
48     int positional;             /* 1=POSITIONAL, 0=ALPHA. */
49   };
50
51 /* Increasing order of variable index. */
52 static struct ordering forward_positional_ordering = {1, 1};
53
54 static int compare_variables_given_ordering (const void *, const void *,
55                                              const void *ordering);
56
57 /* Explains how to modify the variables in a dictionary. */
58 struct var_modification
59   {
60     /* New variable ordering. */
61     struct variable **reorder_vars;
62     size_t reorder_cnt;
63
64     /* DROP/KEEP information. */
65     struct variable **drop_vars;
66     size_t drop_cnt;
67
68     /* New variable names. */
69     struct variable **rename_vars;
70     char **new_names;
71     size_t rename_cnt;
72   };
73
74 static bool rearrange_dict (struct dictionary *d,
75                            const struct var_modification *vm);
76
77 /* Performs MODIFY VARS command. */
78 int
79 cmd_modify_vars (struct lexer *lexer, struct dataset *ds)
80 {
81   /* Bits indicated whether we've already encountered a subcommand of
82      this type. */
83   unsigned already_encountered = 0;
84
85   /* What we're gonna do to the active file. */
86   struct var_modification vm;
87
88   /* Return code. */
89   int ret_code = CMD_CASCADING_FAILURE;
90
91   size_t i;
92
93   if (proc_make_temporary_transformations_permanent (ds))
94     msg (SE, _("MODIFY VARS may not be used after TEMPORARY.  "
95                "Temporary transformations will be made permanent."));
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 (lexer, '/');
107   for (;;)
108     {
109       if (lex_match_id (lexer, "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 (lexer, '=');
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 (lexer, "FORWARD"));
129               else if (lex_match_id (lexer, "BACKWARD"))
130                 ordering.forward = 0;
131               if (lex_match_id (lexer, "POSITIONAL"));
132               else if (lex_match_id (lexer, "ALPHA"))
133                 ordering.positional = 0;
134
135               if (lex_match (lexer, T_ALL) || lex_token (lexer) == '/' || lex_token (lexer) == '.')
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_mutable (dataset_dict (ds), &v, &nv, 1u << DC_SYSTEM);
144                 }
145               else
146                 {
147                   if (!lex_match (lexer, '('))
148                     {
149                       msg (SE, _("`(' expected on REORDER subcommand."));
150                       free (v);
151                       goto done;
152                     }
153                   if (!parse_variables (lexer, dataset_dict (ds), &v, &nv,
154                                         PV_APPEND | PV_NO_DUPLICATE))
155                     {
156                       free (v);
157                       goto done;
158                     }
159                   if (!lex_match (lexer, ')'))
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 (lex_token (lexer) != '/' && lex_token (lexer) != '.');
171
172           vm.reorder_vars = v;
173           vm.reorder_cnt = nv;
174         }
175       else if (lex_match_id (lexer, "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 (lexer, '=');
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 (lexer, '('))
191                 {
192                   msg (SE, _("`(' expected on RENAME subcommand."));
193                   goto done;
194                 }
195               if (!parse_variables (lexer, dataset_dict (ds), 
196                                     &vm.rename_vars, &vm.rename_cnt,
197                                     PV_APPEND | PV_NO_DUPLICATE))
198                 goto done;
199               if (!lex_match (lexer, '='))
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 (lexer, &vm.new_names, 
206                                          &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                        (int) (vm.rename_cnt - prev_nv_2),
213                        (int) (prev_nv_1 - prev_nv_2));
214                   for (i = 0; i < prev_nv_1; i++)
215                     free (vm.new_names[i]);
216                   free (vm.new_names);
217                   vm.new_names = NULL;
218                   goto done;
219                 }
220               if (!lex_match (lexer, ')'))
221                 {
222                   msg (SE, _("`)' expected after variable lists on RENAME "
223                        "subcommand."));
224                   goto done;
225                 }
226             }
227           while (lex_token (lexer) != '.' && lex_token (lexer) != '/');
228         }
229       else if (lex_match_id (lexer, "KEEP"))
230         {
231           struct variable **keep_vars, **all_vars, **drop_vars;
232           size_t keep_cnt, all_cnt, drop_cnt;
233
234           if (already_encountered & 4)
235             {
236               msg (SE, _("KEEP subcommand may be given at most once.  It may not"
237                    "be given in conjunction with the DROP subcommand."));
238               goto done;
239             }
240           already_encountered |= 4;
241
242           lex_match (lexer, '=');
243           if (!parse_variables (lexer, dataset_dict (ds), &keep_vars, &keep_cnt, PV_NONE))
244             goto done;
245
246           /* Transform the list of variables to keep into a list of
247              variables to drop.  First sort the keep list, then figure
248              out which variables are missing. */
249           sort (keep_vars, keep_cnt, sizeof *keep_vars,
250                 compare_variables_given_ordering, &forward_positional_ordering);
251
252           dict_get_vars_mutable (dataset_dict (ds), &all_vars, &all_cnt, 0);
253           assert (all_cnt >= keep_cnt);
254
255           drop_cnt = all_cnt - keep_cnt;
256           drop_vars = xnmalloc (drop_cnt, sizeof *keep_vars);
257           if (set_difference (all_vars, all_cnt,
258                               keep_vars, keep_cnt,
259                               sizeof *all_vars,
260                               drop_vars,
261                               compare_variables_given_ordering,
262                               &forward_positional_ordering)
263               != drop_cnt)
264             NOT_REACHED ();
265
266           free (keep_vars);
267           free (all_vars);
268
269           vm.drop_vars = drop_vars;
270           vm.drop_cnt = drop_cnt;
271         }
272       else if (lex_match_id (lexer, "DROP"))
273         {
274           struct variable **drop_vars;
275           size_t drop_cnt;
276
277           if (already_encountered & 4)
278             {
279               msg (SE, _("DROP subcommand may be given at most once.  It may "
280                          "not be given in conjunction with the KEEP "
281                          "subcommand."));
282               goto done;
283             }
284           already_encountered |= 4;
285
286           lex_match (lexer, '=');
287           if (!parse_variables (lexer, dataset_dict (ds), &drop_vars, &drop_cnt, PV_NONE))
288             goto done;
289           vm.drop_vars = drop_vars;
290           vm.drop_cnt = drop_cnt;
291         }
292       else if (lex_match_id (lexer, "MAP"))
293         {
294           struct dictionary *temp = dict_clone (dataset_dict (ds));
295           int success = rearrange_dict (temp, &vm);
296           if (success) 
297             {
298               /* FIXME: display new dictionary. */ 
299             }
300           dict_destroy (temp);
301         }
302       else
303         {
304           if (lex_token (lexer) == T_ID)
305             msg (SE, _("Unrecognized subcommand name `%s'."), lex_tokid (lexer));
306           else
307             msg (SE, _("Subcommand name expected."));
308           goto done;
309         }
310
311       if (lex_token (lexer) == '.')
312         break;
313       if (lex_token (lexer) != '/')
314         {
315           msg (SE, _("`/' or `.' expected."));
316           goto done;
317         }
318       lex_get (lexer);
319     }
320
321   if (already_encountered & (1 | 4))
322     {
323       /* Read the data. */
324       if (!proc_execute (ds)) 
325         goto done; 
326     }
327
328   if (!rearrange_dict (dataset_dict (ds), &vm))
329     goto done; 
330
331   ret_code = CMD_SUCCESS;
332
333 done:
334   free (vm.reorder_vars);
335   free (vm.rename_vars);
336   for (i = 0; i < vm.rename_cnt; i++)
337     free (vm.new_names[i]);
338   free (vm.new_names);
339   free (vm.drop_vars);
340   return ret_code;
341 }
342
343 /* Compares A and B according to the settings in
344    ORDERING, returning a strcmp()-type result. */
345 static int
346 compare_variables_given_ordering (const void *a_, const void *b_,
347                                   const void *ordering_)
348 {
349   struct variable *const *pa = a_;
350   struct variable *const *pb = b_;
351   const struct variable *a = *pa;
352   const struct variable *b = *pb;
353   const struct ordering *ordering = ordering_;
354
355   int result;
356   if (ordering->positional) 
357     {
358       size_t a_index = var_get_dict_index (a);
359       size_t b_index = var_get_dict_index (b);
360       result = a_index < b_index ? -1 : a_index > b_index; 
361     }
362   else
363     result = strcasecmp (var_get_name (a), var_get_name (b));
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[LONG_NAME_LEN + 1];
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                                   const void *aux UNUSED) 
381 {
382   const struct var_renaming *a = a_;
383   const struct var_renaming *b = b_;
384
385   return strcasecmp (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 keep_cnt, drop_cnt;
403   size_t all_cnt;
404
405   struct var_renaming *var_renaming;
406   int valid;
407   size_t i;
408
409   /* All variables, in index order. */
410   dict_get_vars_mutable (d, &all_vars, &all_cnt, 0);
411
412   /* Drop variables, in index order. */
413   drop_cnt = vm->drop_cnt;
414   drop_vars = xnmalloc (drop_cnt, sizeof *drop_vars);
415   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
416   sort (drop_vars, drop_cnt, sizeof *drop_vars,
417         compare_variables_given_ordering, &forward_positional_ordering);
418
419   /* Keep variables, in index order. */
420   assert (all_cnt >= drop_cnt);
421   keep_cnt = all_cnt - drop_cnt;
422   keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
423   if (set_difference (all_vars, all_cnt,
424                       drop_vars, drop_cnt,
425                       sizeof *all_vars,
426                       keep_vars,
427                       compare_variables_given_ordering,
428                       &forward_positional_ordering) != keep_cnt)
429     NOT_REACHED ();
430
431   /* Copy variables into var_renaming array. */
432   var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
433   for (i = 0; i < keep_cnt; i++) 
434     {
435       var_renaming[i].var = keep_vars[i];
436       strcpy (var_renaming[i].new_name, var_get_name (keep_vars[i]));
437     }
438   
439   /* Rename variables in var_renaming array. */
440   for (i = 0; i < vm->rename_cnt; i++) 
441     {
442       struct variable *const *kv;
443       struct var_renaming *vr;
444
445       /* Get the var_renaming element. */
446       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
447                           &vm->rename_vars[i],
448                           compare_variables_given_ordering,
449                           &forward_positional_ordering);
450       if (kv == NULL)
451         continue;
452       vr = var_renaming + (kv - keep_vars);
453
454       strcpy (vr->new_name, vm->new_names[i]);
455     }
456
457   /* Sort var_renaming array by new names and check for
458      duplicates. */
459   sort (var_renaming, keep_cnt, sizeof *var_renaming,
460         compare_var_renaming_by_new_name, NULL);
461   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
462                                compare_var_renaming_by_new_name, NULL) == NULL;
463
464   /* Clean up. */
465   free (all_vars);
466   free (keep_vars);
467   free (drop_vars);
468   free (var_renaming);
469
470   return valid;
471 }
472
473 /* Reoders, removes, and renames variables in dictionary D
474    according to VM.  Returns true if successful, false if there
475    would have been duplicate variable names if the modifications
476    had been carried out.  In the latter case, the dictionary is
477    not modified. */
478 static bool
479 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
480 {
481   char **rename_old_names;
482
483   struct variable **rename_vars;
484   char **rename_new_names;
485   size_t rename_cnt;
486
487   size_t i;
488
489   /* Check whether the modifications will cause duplicate
490      names. */
491   if (!validate_var_modification (d, vm))
492     return false;
493
494   /* Record the old names of variables to rename.  After
495      variables are deleted, we can't depend on the variables to
496      still exist, but we can still look them up by name. */
497   rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
498   for (i = 0; i < vm->rename_cnt; i++)
499     rename_old_names[i] = xstrdup (var_get_name (vm->rename_vars[i]));
500
501   /* Reorder and delete variables. */
502   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
503   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
504
505   /* Compose lists of variables to rename and their new names. */
506   rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
507   rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
508   rename_cnt = 0;
509   for (i = 0; i < vm->rename_cnt; i++)
510     {
511       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
512       if (var == NULL)
513         continue;
514       
515       rename_vars[rename_cnt] = var;
516       rename_new_names[rename_cnt] = vm->new_names[i];
517       rename_cnt++;
518     }
519
520   /* Do renaming. */
521   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
522                         NULL) == 0)
523     NOT_REACHED ();
524
525   /* Clean up. */
526   for (i = 0; i < vm->rename_cnt; i++)
527     free (rename_old_names[i]);
528   free (rename_old_names);
529   free (rename_vars);
530   free (rename_new_names);
531
532   return true;
533 }