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