treewide: Replace <name>_cnt by n_<name>s and <name>_cap by allocated_<name>.
[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 (lexer, _("expecting a valid subcommand"));
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   if (!id_is_valid (s0, dict_get_encoding (dict), true))
99     return NULL;
100
101   const char *s1 = lex_next_tokcstr (lexer, 2);
102   if (!id_is_valid (s1, dict_get_encoding (dict), true))
103     return NULL;
104
105   int x0 = strcspn (s0, "0123456789");
106   int x1 = strcspn (s1, "0123456789");
107
108   /* The non-digit parts of s0 and s1 must be the same length.  */
109   if (x0 != x1)
110     return NULL;
111
112   /* Both s0 and s1 must have some digits.  */
113   if (strlen (s0) <= x0)
114     return NULL;
115
116   if (strlen (s1) <= x1)
117     return NULL;
118
119   /* The non-digit parts of s0 and s1 must be identical.  */
120   if (0 != strncmp (s0, s1, x0))
121     return NULL;
122
123   /* Both names must end with digits.  */
124   int len_s0_pfx = strspn (s0 + x0, "0123456789");
125   if (len_s0_pfx + x0 != strlen (s0))
126     return NULL;
127
128   int len_s1_pfx = strspn (s1 + x1, "0123456789");
129   if (len_s1_pfx + x1 != strlen (s1))
130     return NULL;
131
132   const char *n_start = s0 + x0;
133   const char *n_stop = s1 + x1;
134
135   /* The first may not be greater than the last.  */
136   if (atoi (n_start) > atoi (n_stop))
137     return NULL;
138
139   char *prefix = xstrndup (s0, x1);
140
141   *first = atoi (n_start);
142   *last = atoi (n_stop);
143
144   return prefix;
145 }
146
147
148 /* Parses and performs the RENAME subcommand of GET, SAVE, and
149    related commands.  If RELAX is true, then the new variable
150    names need  not conform to the normal dictionary rules.
151 */
152 bool
153 parse_dict_rename (struct lexer *lexer, struct dictionary *dict,
154                    bool relax)
155 {
156   struct variable **oldvars = NULL;
157   size_t n_newvars = 0;
158   int group = 0;
159   char **newnames = NULL;
160   lex_match (lexer, T_EQUALS);
161
162   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
163     {
164       size_t n_oldvars = 0;
165       oldvars = NULL;
166       n_newvars = 0;
167       n_oldvars = 0;
168       oldvars = NULL;
169
170       bool paren = lex_match (lexer, T_LPAREN);
171       group++;
172       if (!parse_variables (lexer, dict, &oldvars, &n_oldvars, PV_NO_DUPLICATE))
173         goto fail;
174
175       if (!lex_force_match (lexer, T_EQUALS))
176         goto fail;
177
178       newnames = xmalloc (sizeof *newnames * n_oldvars);
179
180       char *prefix = NULL;
181       int first, last;
182       /* First attempt to parse v1 TO v10 format.  */
183       if ((prefix = try_to_sequence (lexer, dict, &first, &last)))
184         {
185           /* These 3 tokens have already been checked in the
186              try_to_sequence function.  */
187           lex_get (lexer);
188           lex_get (lexer);
189           lex_get (lexer);
190
191           /* Make sure the new names are suitable.  */
192           for (int i = first; i <= last; ++i)
193             {
194               int sz = strlen (prefix) + intlog10 (last) + 1;
195               char *vn = malloc (sz);
196               snprintf (vn, sz, "%s%d", prefix, i);
197
198               if (!check_rename (dict, var_get_name (oldvars[n_newvars]), vn))
199                 {
200                   free (prefix);
201                   goto fail;
202                 }
203
204               newnames[i - first] = vn;
205               n_newvars++;
206             }
207         }
208       else
209       while (lex_token (lexer) == T_ID || lex_token (lexer) == T_STRING)
210         {
211           if (n_newvars >= n_oldvars)
212             break;
213           const char *new_name = lex_tokcstr (lexer);
214           if (!relax && ! id_is_plausible (new_name, true))
215             goto fail;
216
217           if (!check_rename (dict, var_get_name (oldvars[n_newvars]), new_name))
218             goto fail;
219           newnames[n_newvars] = strdup (new_name);
220           lex_get (lexer);
221           n_newvars++;
222         }
223       free (prefix);
224
225       if (n_newvars != n_oldvars)
226         {
227           msg (SE, _("Number of variables on left side of `=' (%zu) does not "
228                      "match number of variables on right side (%zu), in "
229                      "parenthesized group %d of RENAME subcommand."),
230                n_oldvars, n_newvars, group);
231           goto fail;
232         }
233
234       if (paren)
235         if (!lex_force_match (lexer, T_RPAREN))
236           goto fail;
237
238       char *errname = 0;
239       if (!dict_rename_vars (dict, oldvars, newnames, n_newvars, &errname))
240         {
241           msg (SE,
242                _("Requested renaming duplicates variable name %s."),
243                errname);
244           goto fail;
245         }
246       free (oldvars);
247       for (int i = 0; i < n_newvars; ++i)
248         free (newnames[i]);
249       free (newnames);
250       newnames = NULL;
251     }
252
253   return true;
254
255  fail:
256   free (oldvars);
257   for (int i = 0; i < n_newvars; ++i)
258     free (newnames[i]);
259   free (newnames);
260   newnames = NULL;
261   return false;
262 }
263
264 /* Parses and performs the DROP subcommand of GET, SAVE, and
265    related commands.
266    Returns true if successful, false on failure.*/
267 bool
268 parse_dict_drop (struct lexer *lexer, struct dictionary *dict)
269 {
270   struct variable **v;
271   size_t nv;
272
273   lex_match (lexer, T_EQUALS);
274   if (!parse_variables (lexer, dict, &v, &nv, PV_NONE))
275     return false;
276   dict_delete_vars (dict, v, nv);
277   free (v);
278
279   if (dict_get_n_vars (dict) == 0)
280     {
281       msg (SE, _("Cannot DROP all variables from dictionary."));
282       return false;
283     }
284   return true;
285 }
286
287 /* Parses and performs the KEEP subcommand of GET, SAVE, and
288    related commands.
289    Returns true if successful, false on failure.*/
290 bool
291 parse_dict_keep (struct lexer *lexer, struct dictionary *dict)
292 {
293   struct variable **v;
294   size_t nv;
295   size_t i;
296
297   lex_match (lexer, T_EQUALS);
298   if (!parse_variables (lexer, dict, &v, &nv, PV_NONE))
299     return false;
300
301   /* Move the specified variables to the beginning. */
302   dict_reorder_vars (dict, v, nv);
303
304   /* Delete the remaining variables. */
305   if (dict_get_n_vars (dict) == nv)
306     {
307       free (v);
308       return true;
309     }
310
311   v = xnrealloc (v, dict_get_n_vars (dict) - nv, sizeof *v);
312   for (i = nv; i < dict_get_n_vars (dict); i++)
313     v[i - nv] = dict_get_var (dict, i);
314   dict_delete_vars (dict, v, dict_get_n_vars (dict) - nv);
315   free (v);
316
317   return true;
318 }