checkin of 0.3.0
[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 /* AIX requires this to be the first thing in the file.  */
21 #include <config.h>
22 #if __GNUC__
23 #define alloca __builtin_alloca
24 #else
25 #if HAVE_ALLOCA_H
26 #include <alloca.h>
27 #else
28 #ifdef _AIX
29 #pragma alloca
30 #else
31 #ifndef alloca                  /* predefined by HP cc +Olibcalls */
32 char *alloca ();
33 #endif
34 #endif
35 #endif
36 #endif
37
38 #include <stdlib.h>
39 #include <assert.h>
40 #include "alloc.h"
41 #include "avl.h"
42 #include "bitvector.h"
43 #include "command.h"
44 #include "error.h"
45 #include "lexer.h"
46 #include "misc.h"
47 #include "str.h"
48 #include "var.h"
49 #include "vfm.h"
50
51 /* FIXME: should change weighting variable, etc. */
52 /* These control the way that compare_variables() does its work. */
53 static int forward;             /* 1=FORWARD, 0=BACKWARD. */
54 static int positional;          /* 1=POSITIONAL, 0=ALPHA. */
55
56 static int compare_variables (const void *pa, const void *pb);
57
58 /* Explains how to modify the variables in a dictionary in conjunction
59    with the p.mfv field of `variable'. */
60 struct var_modification
61   {
62     /* REORDER information. */
63     struct variable **reorder_list;
64
65     /* RENAME information. */
66     struct variable **old_names;
67     char **new_names;
68     int n_rename;
69
70     /* DROP/KEEP information. */
71     int n_drop;                 /* Number of variables being dropped. */
72   };
73
74 static struct dictionary *rearrange_dict (struct dictionary *d,
75                                           struct var_modification *vm,
76                                           int permanent);
77
78 /* Performs MODIFY VARS command. */
79 int
80 cmd_modify_vars (void)
81 {
82   /* Bits indicated whether we've already encountered a subcommand of
83      this type. */
84   unsigned already_encountered = 0;
85
86   /* What we're gonna do to the active file. */
87   struct var_modification vm;
88
89   lex_match_id ("MODIFY");
90   lex_match_id ("VARS");
91
92   vm.reorder_list = NULL;
93   vm.old_names = NULL;
94   vm.new_names = NULL;
95   vm.n_rename = 0;
96   vm.n_drop = 0;
97
98   /* Parse each subcommand. */
99   lex_match ('/');
100   for (;;)
101     {
102       if (lex_match_id ("REORDER"))
103         {
104           struct variable **v = NULL;
105           int nv = 0;
106
107           if (already_encountered & 1)
108             {
109               msg (SE, _("REORDER subcommand may be given at most once."));
110               goto lossage;
111             }
112           already_encountered |= 1;
113
114           lex_match ('=');
115           do
116             {
117               int prev_nv = nv;
118
119               forward = positional = 1;
120               if (lex_match_id ("FORWARD"));
121               else if (lex_match_id ("BACKWARD"))
122                 forward = 0;
123               if (lex_match_id ("POSITIONAL"));
124               else if (lex_match_id ("ALPHA"))
125                 positional = 0;
126
127               if (lex_match (T_ALL) || token == '/' || token == '.')
128                 {
129                   if (prev_nv != 0)
130                     {
131                       msg (SE, _("Cannot specify ALL after specifying a set "
132                            "of variables."));
133                       goto lossage;
134                     }
135                   fill_all_vars (&v, &nv, FV_NO_SYSTEM);
136                 }
137               else
138                 {
139                   if (!lex_match ('('))
140                     {
141                       msg (SE, _("`(' expected on REORDER subcommand."));
142                       free (v);
143                       goto lossage;
144                     }
145                   if (!parse_variables (&default_dict, &v, &nv,
146                                         PV_APPEND | PV_NO_DUPLICATE))
147                     {
148                       free (v);
149                       goto lossage;
150                     }
151                   if (!lex_match (')'))
152                     {
153                       msg (SE, _("`)' expected following variable names on "
154                            "REORDER subcommand."));
155                       free (v);
156                       goto lossage;
157                     }
158                 }
159               qsort (&v[prev_nv], nv - prev_nv, sizeof *v, compare_variables);
160             }
161           while (token != '/' && token != '.');
162
163           if (nv != default_dict.nvar)
164             {
165               size_t nbytes = DIV_RND_UP (default_dict.nvar, 8);
166               unsigned char *bits = local_alloc (nbytes);
167               int i;
168
169               memset (bits, 0, nbytes);
170               for (i = 0; i < nv; i++)
171                 SET_BIT (bits, v[i]->index);
172               v = xrealloc (v, sizeof *v * default_dict.nvar);
173               for (i = 0; i < default_dict.nvar; i++)
174                 if (!TEST_BIT (bits, i))
175                   v[nv++] = default_dict.var[i];
176               local_free (bits);
177             }
178
179           vm.reorder_list = v;
180         }
181       else if (lex_match_id ("RENAME"))
182         {
183           if (already_encountered & 2)
184             {
185               msg (SE, _("RENAME subcommand may be given at most once."));
186               goto lossage;
187             }
188           already_encountered |= 2;
189
190           lex_match ('=');
191           do
192             {
193               int prev_nv_1 = vm.n_rename;
194               int prev_nv_2 = vm.n_rename;
195
196               if (!lex_match ('('))
197                 {
198                   msg (SE, _("`(' expected on RENAME subcommand."));
199                   goto lossage;
200                 }
201               if (!parse_variables (&default_dict, &vm.old_names, &vm.n_rename,
202                                     PV_APPEND | PV_NO_DUPLICATE))
203                 goto lossage;
204               if (!lex_match ('='))
205                 {
206                   msg (SE, _("`=' expected between lists of new and old variable "
207                        "names on RENAME subcommand."));
208                   goto lossage;
209                 }
210               if (!parse_DATA_LIST_vars (&vm.new_names, &prev_nv_1, PV_APPEND))
211                 goto lossage;
212               if (prev_nv_1 != vm.n_rename)
213                 {
214                   int i;
215
216                   msg (SE, _("Differing number of variables in old name list "
217                        "(%d) and in new name list (%d)."),
218                        vm.n_rename - prev_nv_2, prev_nv_1 - prev_nv_2);
219                   for (i = 0; i < prev_nv_1; i++)
220                     free (&vm.new_names[i]);
221                   free (&vm.new_names);
222                   vm.new_names = NULL;
223                   goto lossage;
224                 }
225               if (!lex_match (')'))
226                 {
227                   msg (SE, _("`)' expected after variable lists on RENAME "
228                        "subcommand."));
229                   goto lossage;
230                 }
231             }
232           while (token != '.' && token != '/');
233         }
234       else if (lex_match_id ("KEEP"))
235         {
236           struct variable **keep_vars;
237           int nv;
238           int counter;
239           int i;
240
241           if (already_encountered & 4)
242             {
243               msg (SE, _("KEEP subcommand may be given at most once.  It may not"
244                    "be given in conjunction with the DROP subcommand."));
245               goto lossage;
246             }
247           already_encountered |= 4;
248
249           lex_match ('=');
250           if (!parse_variables (&default_dict, &keep_vars, &nv, PV_NONE))
251             goto lossage;
252
253           /* Transform the list of variables to keep into a list of
254              variables to drop.  First sort the keep list, then figure
255              out which variables are missing. */
256           forward = positional = 1;
257           qsort (keep_vars, nv, sizeof *keep_vars, compare_variables);
258
259           vm.n_drop = default_dict.nvar - nv;
260
261           counter = 0;
262           for (i = 0; i < nv; i++)
263             {
264               while (counter < keep_vars[i]->index)
265                 default_dict.var[counter++]->p.mfv.drop_this_var = 1;
266               default_dict.var[counter++]->p.mfv.drop_this_var = 0;
267             }
268           while (counter < nv)
269             default_dict.var[counter++]->p.mfv.drop_this_var = 1;
270
271           free (keep_vars);
272         }
273       else if (lex_match_id ("DROP"))
274         {
275           struct variable **drop_vars;
276           int nv;
277           int i;
278
279           if (already_encountered & 4)
280             {
281               msg (SE, _("DROP subcommand may be given at most once.  It may not"
282                    "be given in conjunction with the KEEP subcommand."));
283               goto lossage;
284             }
285           already_encountered |= 4;
286
287           lex_match ('=');
288           if (!parse_variables (&default_dict, &drop_vars, &nv, PV_NONE))
289             goto lossage;
290           for (i = 0; i < default_dict.nvar; i++)
291             default_dict.var[i]->p.mfv.drop_this_var = 0;
292           for (i = 0; i < nv; i++)
293             drop_vars[i]->p.mfv.drop_this_var = 1;
294           vm.n_drop = nv;
295           free (drop_vars);
296         }
297       else if (lex_match_id ("MAP"))
298         {
299           struct dictionary *new_dict = rearrange_dict (&default_dict, &vm, 0);
300           if (!new_dict)
301             goto lossage;
302           /* FIXME: display new dictionary. */
303         }
304       else
305         {
306           if (token == T_ID)
307             msg (SE, _("Unrecognized subcommand name `%s'."), tokid);
308           else
309             msg (SE, _("Subcommand name expected."));
310           goto lossage;
311         }
312
313       if (token == '.')
314         break;
315       if (token != '/')
316         {
317           msg (SE, _("`/' or `.' expected."));
318           goto lossage;
319         }
320       lex_get ();
321     }
322
323   {
324     int i;
325
326     if (already_encountered & (1 | 4))
327       {
328         /* Read the data. */
329         procedure (NULL, NULL, NULL);
330       }
331
332     if (NULL == rearrange_dict (&default_dict, &vm, 1))
333       goto lossage;
334
335     free (vm.reorder_list);
336     free (vm.old_names);
337     for (i = 0; i < vm.n_rename; i++)
338       free (vm.new_names[i]);
339     free (vm.new_names);
340
341     return CMD_SUCCESS;
342   }
343
344 lossage:
345   {
346     int i;
347
348     free (vm.reorder_list);
349     free (vm.old_names);
350     for (i = 0; i < vm.n_rename; i++)
351       free (vm.new_names[i]);
352     free (vm.new_names);
353     return CMD_FAILURE;
354   }
355 }
356
357 /* Compares a pair of variables according to the settings in `forward'
358    and `positional', returning a strcmp()-type result. */
359 static int
360 compare_variables (const void *pa, const void *pb)
361 {
362   const struct variable *a = *(const struct variable **) pa;
363   const struct variable *b = *(const struct variable **) pb;
364
365   int result = positional ? a->index - b->index : strcmp (a->name, b->name);
366   return forward ? result : -result;
367 }
368
369 /* (Possibly) rearranges variables and (possibly) removes some
370    variables and (possibly) renames some more variables in dictionary
371    D.  There are two modes of operation, distinguished by the value of
372    PERMANENT:
373
374    If PERMANENT is nonzero, then the dictionary is modified in place.
375    Returns the new dictionary on success or NULL if there would have
376    been duplicate variable names in the resultant dictionary (in this
377    case the dictionary has not been modified).
378
379    If PERMANENT is zero, then the dictionary is copied to a new
380    dictionary structure that retains most of the same deep structure
381    as D.  The p.mfv.new_name field of each variable is set to what
382    would become the variable's new name if PERMANENT were nonzero.
383    Returns the new dictionary. */
384 static struct dictionary *
385 rearrange_dict (struct dictionary * d, struct var_modification * vm, int permanent)
386 {
387   struct dictionary *n;
388
389   struct variable **save_var;
390
391   /* Linked list of variables for deletion. */
392   struct variable *head, *tail;
393
394   int i;
395
396   /* First decide what dictionary to modify. */
397   if (permanent == 0)
398     {
399       n = xmalloc (sizeof *n);
400       *n = *d;
401     }
402   else
403     n = d;
404   save_var = n->var;
405
406   /* Perform first half of renaming. */
407   if (permanent)
408     {
409       for (i = 0; i < d->nvar; i++)
410         d->var[i]->p.mfv.new_name[0] = 0;
411       d->var = xmalloc (sizeof *d->var * d->nvar);
412     }
413   else
414     for (i = 0; i < d->nvar; i++)
415       strcpy (d->var[i]->p.mfv.new_name, d->var[i]->name);
416   for (i = 0; i < vm->n_rename; i++)
417     strcpy (vm->old_names[i]->p.mfv.new_name, vm->new_names[i]);
418
419   /* Copy the variable list, reordering if appropriate. */
420   if (vm->reorder_list)
421     memcpy (n->var, vm->reorder_list, sizeof *n->var * d->nvar);
422   else if (!permanent)
423     for (i = 0; i < d->nvar; i++)
424       n->var[i] = d->var[i];
425
426   /* Drop all the unwanted variables. */
427   head = NULL;
428   if (vm->n_drop)
429     {
430       int j;
431
432       n->nvar = d->nvar - vm->n_drop;
433       for (i = j = 0; i < n->nvar; i++)
434         {
435           while (n->var[j]->p.mfv.drop_this_var != 0)
436             {
437               if (permanent)
438                 {
439                   /* If this is permanent, then we have to keep a list
440                      of all the dropped variables because they must be
441                      free()'d, but can't be until we know that there
442                      aren't any duplicate variable names. */
443                   if (head)
444                     tail = tail->p.mfv.next = n->var[j];
445                   else
446                     head = tail = n->var[j];
447                 }
448               j++;
449             }
450           n->var[i] = n->var[j++];
451         }
452       if (permanent)
453         tail->p.mfv.next = NULL;
454     }
455
456   /* Check for duplicate variable names if appropriate. */
457   if (permanent && vm->n_rename)
458     {
459       struct variable **v;
460
461       if (vm->reorder_list)
462         v = vm->reorder_list;   /* Reuse old buffer if possible. */
463       else
464         v = xmalloc (sizeof *v * n->nvar);
465       memcpy (v, n->var, sizeof *v * n->nvar);
466       forward = 1, positional = 0;
467       qsort (v, n->nvar, sizeof *v, compare_variables);
468       for (i = 1; i < n->nvar; i++)
469         if (!strcmp (n->var[i]->name, n->var[i - 1]->name))
470           {
471             msg (SE, _("Duplicate variable name `%s' after renaming."),
472                  n->var[i]->name);
473             if (vm->reorder_list == NULL)
474               free (v);
475             n->var = save_var;
476             return NULL;
477           }
478       if (vm->reorder_list == NULL)
479         free (v);
480     }
481
482   /* Delete unwanted variables and finalize renaming if
483      appropriate. */
484   if (permanent)
485     {
486       /* Delete dropped variables for good. */
487       for (; head; head = tail)
488         {
489           tail = head->p.mfv.next;
490           clear_variable (n, head);
491           free (head);
492         }
493
494       /* Remove names from all renamed variables. */
495       head = NULL;
496       for (i = 0; i < n->nvar; i++)
497         if (n->var[i]->p.mfv.new_name[0])
498           {
499             avl_force_delete (n->var_by_name, n->var[i]);
500             if (head)
501               tail = tail->p.mfv.next = n->var[i];
502             else
503               head = tail = n->var[i];
504           }
505       if (head)
506         tail->p.mfv.next = NULL;
507
508       /* Put names onto renamed variables. */
509       for (; head; head = head->p.mfv.next)
510         {
511           strcpy (head->name, head->p.mfv.new_name);
512           avl_force_insert (n->var_by_name, head);
513         }
514       free (save_var);
515
516       /* As a final step the index fields must be redone. */
517       for (i = 0; i < n->nvar; i++)
518         n->var[i]->index = i;
519     }
520
521   return n;
522 }