First step in making struct variable opaque: the boring mechanical
[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                                              const 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 (struct lexer *lexer, struct dataset *ds)
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 (ds))
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 (lexer, '/');
109   for (;;)
110     {
111       if (lex_match_id (lexer, "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 (lexer, '=');
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 (lexer, "FORWARD"));
131               else if (lex_match_id (lexer, "BACKWARD"))
132                 ordering.forward = 0;
133               if (lex_match_id (lexer, "POSITIONAL"));
134               else if (lex_match_id (lexer, "ALPHA"))
135                 ordering.positional = 0;
136
137               if (lex_match (lexer, T_ALL) || lex_token (lexer) == '/' || lex_token (lexer) == '.')
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 (dataset_dict (ds), &v, &nv, 1u << DC_SYSTEM);
146                 }
147               else
148                 {
149                   if (!lex_match (lexer, '('))
150                     {
151                       msg (SE, _("`(' expected on REORDER subcommand."));
152                       free (v);
153                       goto done;
154                     }
155                   if (!parse_variables (lexer, dataset_dict (ds), &v, &nv,
156                                         PV_APPEND | PV_NO_DUPLICATE))
157                     {
158                       free (v);
159                       goto done;
160                     }
161                   if (!lex_match (lexer, ')'))
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 (lex_token (lexer) != '/' && lex_token (lexer) != '.');
173
174           vm.reorder_vars = v;
175           vm.reorder_cnt = nv;
176         }
177       else if (lex_match_id (lexer, "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 (lexer, '=');
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 (lexer, '('))
193                 {
194                   msg (SE, _("`(' expected on RENAME subcommand."));
195                   goto done;
196                 }
197               if (!parse_variables (lexer, dataset_dict (ds), 
198                                     &vm.rename_vars, &vm.rename_cnt,
199                                     PV_APPEND | PV_NO_DUPLICATE))
200                 goto done;
201               if (!lex_match (lexer, '='))
202                 {
203                   msg (SE, _("`=' expected between lists of new and old variable "
204                        "names on RENAME subcommand."));
205                   goto done;
206                 }
207               if (!parse_DATA_LIST_vars (lexer, &vm.new_names, 
208                                          &prev_nv_1, PV_APPEND))
209                 goto done;
210               if (prev_nv_1 != vm.rename_cnt)
211                 {
212                   msg (SE, _("Differing number of variables in old name list "
213                        "(%d) and in new name list (%d)."),
214                        vm.rename_cnt - prev_nv_2, prev_nv_1 - prev_nv_2);
215                   for (i = 0; i < prev_nv_1; i++)
216                     free (vm.new_names[i]);
217                   free (vm.new_names);
218                   vm.new_names = NULL;
219                   goto done;
220                 }
221               if (!lex_match (lexer, ')'))
222                 {
223                   msg (SE, _("`)' expected after variable lists on RENAME "
224                        "subcommand."));
225                   goto done;
226                 }
227             }
228           while (lex_token (lexer) != '.' && lex_token (lexer) != '/');
229         }
230       else if (lex_match_id (lexer, "KEEP"))
231         {
232           struct variable **keep_vars, **all_vars, **drop_vars;
233           size_t keep_cnt, all_cnt, drop_cnt;
234
235           if (already_encountered & 4)
236             {
237               msg (SE, _("KEEP subcommand may be given at most once.  It may not"
238                    "be given in conjunction with the DROP subcommand."));
239               goto done;
240             }
241           already_encountered |= 4;
242
243           lex_match (lexer, '=');
244           if (!parse_variables (lexer, dataset_dict (ds), &keep_vars, &keep_cnt, PV_NONE))
245             goto done;
246
247           /* Transform the list of variables to keep into a list of
248              variables to drop.  First sort the keep list, then figure
249              out which variables are missing. */
250           sort (keep_vars, keep_cnt, sizeof *keep_vars,
251                 compare_variables_given_ordering, &forward_positional_ordering);
252
253           dict_get_vars (dataset_dict (ds), &all_vars, &all_cnt, 0);
254           assert (all_cnt >= keep_cnt);
255
256           drop_cnt = all_cnt - keep_cnt;
257           drop_vars = xnmalloc (drop_cnt, sizeof *keep_vars);
258           if (set_difference (all_vars, all_cnt,
259                               keep_vars, keep_cnt,
260                               sizeof *all_vars,
261                               drop_vars,
262                               compare_variables_given_ordering,
263                               &forward_positional_ordering)
264               != drop_cnt)
265             NOT_REACHED ();
266
267           free (keep_vars);
268           free (all_vars);
269
270           vm.drop_vars = drop_vars;
271           vm.drop_cnt = drop_cnt;
272         }
273       else if (lex_match_id (lexer, "DROP"))
274         {
275           struct variable **drop_vars;
276           size_t drop_cnt;
277
278           if (already_encountered & 4)
279             {
280               msg (SE, _("DROP subcommand may be given at most once.  It may "
281                          "not be given in conjunction with the KEEP "
282                          "subcommand."));
283               goto done;
284             }
285           already_encountered |= 4;
286
287           lex_match (lexer, '=');
288           if (!parse_variables (lexer, dataset_dict (ds), &drop_vars, &drop_cnt, PV_NONE))
289             goto done;
290           vm.drop_vars = drop_vars;
291           vm.drop_cnt = drop_cnt;
292         }
293       else if (lex_match_id (lexer, "MAP"))
294         {
295           struct dictionary *temp = dict_clone (dataset_dict (ds));
296           int success = rearrange_dict (temp, &vm);
297           if (success) 
298             {
299               /* FIXME: display new dictionary. */ 
300             }
301           dict_destroy (temp);
302         }
303       else
304         {
305           if (lex_token (lexer) == T_ID)
306             msg (SE, _("Unrecognized subcommand name `%s'."), lex_tokid (lexer));
307           else
308             msg (SE, _("Subcommand name expected."));
309           goto done;
310         }
311
312       if (lex_token (lexer) == '.')
313         break;
314       if (lex_token (lexer) != '/')
315         {
316           msg (SE, _("`/' or `.' expected."));
317           goto done;
318         }
319       lex_get (lexer);
320     }
321
322   if (already_encountered & (1 | 4))
323     {
324       /* Read the data. */
325       if (!procedure (ds,NULL, NULL)) 
326         goto done; 
327     }
328
329   if (!rearrange_dict (dataset_dict (ds), &vm))
330     goto done; 
331
332   ret_code = CMD_SUCCESS;
333
334 done:
335   free (vm.reorder_vars);
336   free (vm.rename_vars);
337   for (i = 0; i < vm.rename_cnt; i++)
338     free (vm.new_names[i]);
339   free (vm.new_names);
340   free (vm.drop_vars);
341   return ret_code;
342 }
343
344 /* Compares A and B according to the settings in
345    ORDERING, returning a strcmp()-type result. */
346 static int
347 compare_variables_given_ordering (const void *a_, const void *b_,
348                                   const void *ordering_)
349 {
350   struct variable *const *pa = a_;
351   struct variable *const *pb = b_;
352   const struct variable *a = *pa;
353   const struct variable *b = *pb;
354   const struct ordering *ordering = ordering_;
355
356   int result;
357   if (ordering->positional)
358     result = a->index < b->index ? -1 : a->index > b->index;
359   else
360     result = strcasecmp (var_get_name (a), var_get_name (b));
361   if (!ordering->forward)
362     result = -result;
363   return result;
364 }
365
366 /* Pairs a variable with a new name. */
367 struct var_renaming
368   {
369     struct variable *var;
370     char new_name[LONG_NAME_LEN + 1];
371   };
372
373 /* A algo_compare_func that compares new_name members in struct
374    var_renaming structures A and B. */
375 static int
376 compare_var_renaming_by_new_name (const void *a_, const void *b_,
377                                   const void *aux UNUSED) 
378 {
379   const struct var_renaming *a = a_;
380   const struct var_renaming *b = b_;
381
382   return strcasecmp (a->new_name, b->new_name);
383 }
384
385 /* Returns true if performing VM on dictionary D would not cause
386    problems such as duplicate variable names.  Returns false
387    otherwise, and issues an error message. */
388 static int
389 validate_var_modification (const struct dictionary *d,
390                            const struct var_modification *vm) 
391 {
392   /* Variable reordering can't be a problem, so we don't simulate
393      it.  Variable renaming can cause duplicate names, but
394      dropping variables can eliminate them, so we simulate both
395      of those. */
396   struct variable **all_vars;
397   struct variable **keep_vars;
398   struct variable **drop_vars;
399   size_t keep_cnt, drop_cnt;
400   size_t all_cnt;
401
402   struct var_renaming *var_renaming;
403   int valid;
404   size_t i;
405
406   /* All variables, in index order. */
407   dict_get_vars (d, &all_vars, &all_cnt, 0);
408
409   /* Drop variables, in index order. */
410   drop_cnt = vm->drop_cnt;
411   drop_vars = xnmalloc (drop_cnt, sizeof *drop_vars);
412   memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
413   sort (drop_vars, drop_cnt, sizeof *drop_vars,
414         compare_variables_given_ordering, &forward_positional_ordering);
415
416   /* Keep variables, in index order. */
417   assert (all_cnt >= drop_cnt);
418   keep_cnt = all_cnt - drop_cnt;
419   keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
420   if (set_difference (all_vars, all_cnt,
421                       drop_vars, drop_cnt,
422                       sizeof *all_vars,
423                       keep_vars,
424                       compare_variables_given_ordering,
425                       &forward_positional_ordering) != keep_cnt)
426     NOT_REACHED ();
427
428   /* Copy variables into var_renaming array. */
429   var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
430   for (i = 0; i < keep_cnt; i++) 
431     {
432       var_renaming[i].var = keep_vars[i];
433       strcpy (var_renaming[i].new_name, var_get_name (keep_vars[i]));
434     }
435   
436   /* Rename variables in var_renaming array. */
437   for (i = 0; i < vm->rename_cnt; i++) 
438     {
439       struct variable *const *kv;
440       struct var_renaming *vr;
441
442       /* Get the var_renaming element. */
443       kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
444                           &vm->rename_vars[i],
445                           compare_variables_given_ordering,
446                           &forward_positional_ordering);
447       if (kv == NULL)
448         continue;
449       vr = var_renaming + (kv - keep_vars);
450
451       strcpy (vr->new_name, vm->new_names[i]);
452     }
453
454   /* Sort var_renaming array by new names and check for
455      duplicates. */
456   sort (var_renaming, keep_cnt, sizeof *var_renaming,
457         compare_var_renaming_by_new_name, NULL);
458   valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
459                                compare_var_renaming_by_new_name, NULL) == NULL;
460
461   /* Clean up. */
462   free (all_vars);
463   free (keep_vars);
464   free (drop_vars);
465   free (var_renaming);
466
467   return valid;
468 }
469
470 /* Reoders, removes, and renames variables in dictionary D
471    according to VM.  Returns true if successful, false if there
472    would have been duplicate variable names if the modifications
473    had been carried out.  In the latter case, the dictionary is
474    not modified. */
475 static bool
476 rearrange_dict (struct dictionary *d, const struct var_modification *vm)
477 {
478   char **rename_old_names;
479
480   struct variable **rename_vars;
481   char **rename_new_names;
482   size_t rename_cnt;
483
484   size_t i;
485
486   /* Check whether the modifications will cause duplicate
487      names. */
488   if (!validate_var_modification (d, vm))
489     return false;
490
491   /* Record the old names of variables to rename.  After
492      variables are deleted, we can't depend on the variables to
493      still exist, but we can still look them up by name. */
494   rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
495   for (i = 0; i < vm->rename_cnt; i++)
496     rename_old_names[i] = xstrdup (var_get_name (vm->rename_vars[i]));
497
498   /* Reorder and delete variables. */
499   dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
500   dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
501
502   /* Compose lists of variables to rename and their new names. */
503   rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
504   rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
505   rename_cnt = 0;
506   for (i = 0; i < vm->rename_cnt; i++)
507     {
508       struct variable *var = dict_lookup_var (d, rename_old_names[i]);
509       if (var == NULL)
510         continue;
511       
512       rename_vars[rename_cnt] = var;
513       rename_new_names[rename_cnt] = vm->new_names[i];
514       rename_cnt++;
515     }
516
517   /* Do renaming. */
518   if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
519                         NULL) == 0)
520     NOT_REACHED ();
521
522   /* Clean up. */
523   for (i = 0; i < vm->rename_cnt; i++)
524     free (rename_old_names[i]);
525   free (rename_old_names);
526   free (rename_vars);
527   free (rename_new_names);
528
529   return true;
530 }