cf493391e4d9ccdf5729a6d974ce1056ce8bb774
[pspp] / src / language / data-io / trim.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2007, 2008, 2010, 2011,
3    2019, 2020 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU 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, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 #include "language/data-io/trim.h"
21
22 #include <stdlib.h>
23
24 #include "data/dictionary.h"
25 #include "data/variable.h"
26 #include "language/lexer/lexer.h"
27 #include "language/lexer/variable-parser.h"
28 #include "libpspp/message.h"
29 #include "libpspp/misc.h"
30
31 #include "gl/xalloc.h"
32
33 #include "gettext.h"
34 #define _(msgid) gettext (msgid)
35
36 /* Commands that read and write system files share a great deal
37    of common syntactic structure for rearranging and dropping
38    variables.  This function parses this syntax and modifies DICT
39    appropriately.  If RELAX is true, then the modified dictionary
40    need not conform to the usual variable name rules.  Returns
41    true on success, false on failure. */
42 bool
43 parse_dict_trim (struct lexer *lexer, struct dictionary *dict, bool relax)
44 {
45   if (lex_match_id (lexer, "MAP"))
46     {
47       /* FIXME. */
48       return true;
49     }
50   else if (lex_match_id (lexer, "DROP"))
51     return parse_dict_drop (lexer, dict);
52   else if (lex_match_id (lexer, "KEEP"))
53     return parse_dict_keep (lexer, dict);
54   else if (lex_match_id (lexer, "RENAME"))
55     return parse_dict_rename (lexer, dict, relax);
56   else
57     {
58       lex_error_expecting (lexer, "MAP", "DROP", "KEEP", "RENAME");
59       return false;
60     }
61 }
62
63 /* Check that OLD_NAME can be renamed to NEW_NAME in DICT.  */
64 static bool
65 check_rename (const struct dictionary *dict, const char *old_name, const char *new_name)
66 {
67   if (dict_lookup_var (dict, new_name) != NULL)
68     {
69       msg (SE, _("Cannot rename %s as %s because there already exists "
70                  "a variable named %s.  To rename variables with "
71                  "overlapping names, use a single RENAME subcommand "
72                  "such as `/RENAME (A=B)(B=C)(C=A)', or equivalently, "
73                  "`/RENAME (A B C=B C A)'."),
74            old_name, new_name, new_name);
75       return false;
76     }
77   return true;
78 }
79
80 /* Parse a  "VarX TO VarY" sequence where X and Y are integers
81    such that X >= Y.
82    If successfull, returns a string to the prefix Var and sets FIRST
83    to X and LAST to Y.  Returns NULL on failure.
84    The caller must free the return value.  */
85 static char *
86 try_to_sequence (struct lexer *lexer, const struct dictionary *dict,
87                  int *first, int *last)
88 {
89   /* Check that the next 3 tokens are of the correct type.  */
90   if (lex_token (lexer) != T_ID
91       || lex_next_token (lexer, 1) != T_TO
92       || lex_next_token (lexer, 2) != T_ID)
93     return NULL;
94
95   /* Check that the first and last tokens are suitable as
96      variable names.  */
97   const char *s0 = lex_tokcstr (lexer);
98   char *error = id_is_valid__ (s0, dict_get_encoding (dict));
99   if (error)
100     {
101       lex_error (lexer, "%s", error);
102       free (error);
103       return NULL;
104     }
105
106   const char *s1 = lex_next_tokcstr (lexer, 2);
107   error = id_is_valid__ (s1, dict_get_encoding (dict));
108   if (error)
109     {
110       lex_next_error (lexer, 2, 2, "%s", error);
111       free (error);
112       return NULL;
113     }
114
115   int x0 = strcspn (s0, "0123456789");
116   int x1 = strcspn (s1, "0123456789");
117
118   /* The non-digit parts of s0 and s1 must be the same length.  */
119   if (x0 != x1)
120     return NULL;
121
122   /* Both s0 and s1 must have some digits.  */
123   if (strlen (s0) <= x0)
124     return NULL;
125
126   if (strlen (s1) <= x1)
127     return NULL;
128
129   /* The non-digit parts of s0 and s1 must be identical.  */
130   if (0 != strncmp (s0, s1, x0))
131     return NULL;
132
133   /* Both names must end with digits.  */
134   int len_s0_pfx = strspn (s0 + x0, "0123456789");
135   if (len_s0_pfx + x0 != strlen (s0))
136     return NULL;
137
138   int len_s1_pfx = strspn (s1 + x1, "0123456789");
139   if (len_s1_pfx + x1 != strlen (s1))
140     return NULL;
141
142   const char *n_start = s0 + x0;
143   const char *n_stop = s1 + x1;
144
145   /* The first may not be greater than the last.  */
146   if (atoi (n_start) > atoi (n_stop))
147     return NULL;
148
149   char *prefix = xstrndup (s0, x1);
150
151   *first = atoi (n_start);
152   *last = atoi (n_stop);
153
154   return prefix;
155 }
156
157
158 /* Parses and performs the RENAME subcommand of GET, SAVE, and
159    related commands.  If RELAX is true, then the new variable
160    names need  not conform to the normal dictionary rules.
161 */
162 bool
163 parse_dict_rename (struct lexer *lexer, struct dictionary *dict,
164                    bool relax)
165 {
166   struct variable **oldvars = NULL;
167   size_t n_newvars = 0;
168   int group = 0;
169   char **newnames = NULL;
170   lex_match (lexer, T_EQUALS);
171
172   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
173     {
174       size_t n_oldvars = 0;
175       oldvars = NULL;
176       n_newvars = 0;
177       n_oldvars = 0;
178       oldvars = NULL;
179
180       bool paren = lex_match (lexer, T_LPAREN);
181       group++;
182       if (!parse_variables (lexer, dict, &oldvars, &n_oldvars, PV_NO_DUPLICATE))
183         goto fail;
184
185       if (!lex_force_match (lexer, T_EQUALS))
186         goto fail;
187
188       newnames = xmalloc (sizeof *newnames * n_oldvars);
189
190       char *prefix = NULL;
191       int first, last;
192       /* First attempt to parse v1 TO v10 format.  */
193       if ((prefix = try_to_sequence (lexer, dict, &first, &last)))
194         {
195           /* These 3 tokens have already been checked in the
196              try_to_sequence function.  */
197           lex_get (lexer);
198           lex_get (lexer);
199           lex_get (lexer);
200
201           /* Make sure the new names are suitable.  */
202           for (int i = first; i <= last; ++i)
203             {
204               int sz = strlen (prefix) + intlog10 (last) + 1;
205               char *vn = malloc (sz);
206               snprintf (vn, sz, "%s%d", prefix, i);
207
208               if (!check_rename (dict, var_get_name (oldvars[n_newvars]), vn))
209                 {
210                   free (prefix);
211                   goto fail;
212                 }
213
214               newnames[i - first] = vn;
215               n_newvars++;
216             }
217         }
218       else
219       while (lex_token (lexer) == T_ID || lex_token (lexer) == T_STRING)
220         {
221           if (n_newvars >= n_oldvars)
222             break;
223           const char *new_name = lex_tokcstr (lexer);
224           if (!relax)
225             {
226               char *error = id_is_plausible__ (new_name);
227               if (error)
228                 {
229                   lex_error (lexer, "%s", error);
230                   free (error);
231                   goto fail;
232                 }
233             }
234
235           if (!check_rename (dict, var_get_name (oldvars[n_newvars]), new_name))
236             goto fail;
237           newnames[n_newvars] = strdup (new_name);
238           lex_get (lexer);
239           n_newvars++;
240         }
241       free (prefix);
242
243       if (n_newvars != n_oldvars)
244         {
245           msg (SE, _("Number of variables on left side of `=' (%zu) does not "
246                      "match number of variables on right side (%zu), in "
247                      "parenthesized group %d of RENAME subcommand."),
248                n_oldvars, n_newvars, group);
249           goto fail;
250         }
251
252       if (paren)
253         if (!lex_force_match (lexer, T_RPAREN))
254           goto fail;
255
256       char *errname = 0;
257       if (!dict_rename_vars (dict, oldvars, newnames, n_newvars, &errname))
258         {
259           msg (SE,
260                _("Requested renaming duplicates variable name %s."),
261                errname);
262           goto fail;
263         }
264       free (oldvars);
265       for (int i = 0; i < n_newvars; ++i)
266         free (newnames[i]);
267       free (newnames);
268       newnames = NULL;
269     }
270
271   return true;
272
273  fail:
274   free (oldvars);
275   for (int i = 0; i < n_newvars; ++i)
276     free (newnames[i]);
277   free (newnames);
278   newnames = NULL;
279   return false;
280 }
281
282 /* Parses and performs the DROP subcommand of GET, SAVE, and
283    related commands.
284    Returns true if successful, false on failure.*/
285 bool
286 parse_dict_drop (struct lexer *lexer, struct dictionary *dict)
287 {
288   struct variable **v;
289   size_t nv;
290
291   lex_match (lexer, T_EQUALS);
292   if (!parse_variables (lexer, dict, &v, &nv, PV_NONE))
293     return false;
294   dict_delete_vars (dict, v, nv);
295   free (v);
296
297   if (dict_get_n_vars (dict) == 0)
298     {
299       msg (SE, _("Cannot DROP all variables from dictionary."));
300       return false;
301     }
302   return true;
303 }
304
305 /* Parses and performs the KEEP subcommand of GET, SAVE, and
306    related commands.
307    Returns true if successful, false on failure.*/
308 bool
309 parse_dict_keep (struct lexer *lexer, struct dictionary *dict)
310 {
311   struct variable **v;
312   size_t nv;
313   size_t i;
314
315   lex_match (lexer, T_EQUALS);
316   if (!parse_variables (lexer, dict, &v, &nv, PV_NONE))
317     return false;
318
319   /* Move the specified variables to the beginning. */
320   dict_reorder_vars (dict, v, nv);
321
322   /* Delete the remaining variables. */
323   if (dict_get_n_vars (dict) == nv)
324     {
325       free (v);
326       return true;
327     }
328
329   v = xnrealloc (v, dict_get_n_vars (dict) - nv, sizeof *v);
330   for (i = nv; i < dict_get_n_vars (dict); i++)
331     v[i - nv] = dict_get_var (dict, i);
332   dict_delete_vars (dict, v, dict_get_n_vars (dict) - nv);
333   free (v);
334
335   return true;
336 }