/* PSPP - a program for statistical analysis.
Copyright (C) 1997-9, 2000, 2006, 2007, 2008, 2010, 2011,
- 2019 Free Software Foundation, Inc.
+ 2019, 2020 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "language/lexer/lexer.h"
#include "language/lexer/variable-parser.h"
#include "libpspp/message.h"
+#include "libpspp/misc.h"
#include "gl/xalloc.h"
}
}
+/* Check that OLD_NAME can be renamed to NEW_NAME in DICT. */
+static bool
+check_rename (const struct dictionary *dict, const char *old_name, const char *new_name)
+{
+ if (dict_lookup_var (dict, new_name) != NULL)
+ {
+ msg (SE, _("Cannot rename %s as %s because there already exists "
+ "a variable named %s. To rename variables with "
+ "overlapping names, use a single RENAME subcommand "
+ "such as `/RENAME (A=B)(B=C)(C=A)', or equivalently, "
+ "`/RENAME (A B C=B C A)'."),
+ old_name, new_name, new_name);
+ return false;
+ }
+ return true;
+}
+
+/* Parse a "VarX TO VarY" sequence where X and Y are integers
+ such that X >= Y.
+ If successfull, returns a string to the prefix Var and sets FIRST
+ to X and LAST to Y. Returns NULL on failure.
+ The caller must free the return value. */
+static char *
+try_to_sequence (struct lexer *lexer, const struct dictionary *dict,
+ int *first, int *last)
+{
+ /* Check that the next 3 tokens are of the correct type. */
+ if (lex_token (lexer) != T_ID
+ || lex_next_token (lexer, 1) != T_TO
+ || lex_next_token (lexer, 2) != T_ID)
+ return NULL;
+
+ /* Check that the first and last tokens are suitable as
+ variable names. */
+ const char *s0 = lex_tokcstr (lexer);
+ if (!id_is_valid (s0, dict_get_encoding (dict), true))
+ return NULL;
+
+ const char *s1 = lex_next_tokcstr (lexer, 2);
+ if (!id_is_valid (s1, dict_get_encoding (dict), true))
+ return NULL;
+
+ int x0 = strcspn (s0, "0123456789");
+ int x1 = strcspn (s1, "0123456789");
+
+ /* The non-digit parts of s0 and s1 must be the same length. */
+ if (x0 != x1)
+ return NULL;
+
+ /* Both s0 and s1 must have some digits. */
+ if (strlen (s0) <= x0)
+ return NULL;
+
+ if (strlen (s1) <= x1)
+ return NULL;
+
+ /* The non-digit parts of s0 and s1 must be identical. */
+ if (0 != strncmp (s0, s1, x0))
+ return NULL;
+
+ /* Both names must end with digits. */
+ int len_s0_pfx = strspn (s0 + x0, "0123456789");
+ if (len_s0_pfx + x0 != strlen (s0))
+ return NULL;
+
+ int len_s1_pfx = strspn (s1 + x1, "0123456789");
+ if (len_s1_pfx + x1 != strlen (s1))
+ return NULL;
+
+ const char *n_start = s0 + x0;
+ const char *n_stop = s1 + x1;
+
+ /* The first may not be greater than the last. */
+ if (atoi (n_start) > atoi (n_stop))
+ return NULL;
+
+ char *prefix = xstrndup (s0, x1);
+
+ *first = atoi (n_start);
+ *last = atoi (n_stop);
+
+ return prefix;
+}
+
+
/* Parses and performs the RENAME subcommand of GET, SAVE, and
related commands. If RELAX is true, then the new variable
names need not conform to the normal dictionary rules.
goto fail;
newnames = xmalloc (sizeof *newnames * n_oldvars);
+
+ char *prefix = NULL;
+ int first, last;
+ /* First attempt to parse v1 TO v10 format. */
+ if ((prefix = try_to_sequence (lexer, dict, &first, &last)))
+ {
+ /* These 3 tokens have already been checked in the
+ try_to_sequence function. */
+ lex_get (lexer);
+ lex_get (lexer);
+ lex_get (lexer);
+
+ /* Make sure the new names are suitable. */
+ for (int i = first; i <= last; ++i)
+ {
+ int sz = strlen (prefix) + intlog10 (last) + 1;
+ char *vn = malloc (sz);
+ snprintf (vn, sz, "%s%d", prefix, i);
+
+ if (!check_rename (dict, var_get_name (oldvars[n_newvars]), vn))
+ {
+ free (prefix);
+ goto fail;
+ }
+
+ newnames[i - first] = vn;
+ n_newvars++;
+ }
+ }
+ else
while (lex_token (lexer) == T_ID || lex_token (lexer) == T_STRING)
- {
- if (n_newvars >= n_oldvars)
- break;
- const char *new_name = lex_tokcstr (lexer);
- if (!relax && ! id_is_plausible (new_name, true))
- goto fail;
-
- if (dict_lookup_var (dict, new_name) != NULL)
- {
- msg (SE, _("Cannot rename %s as %s because there already exists "
- "a variable named %s. To rename variables with "
- "overlapping names, use a single RENAME subcommand "
- "such as `/RENAME (A=B)(B=C)(C=A)', or equivalently, "
- "`/RENAME (A B C=B C A)'."),
- var_get_name (oldvars[n_newvars]), new_name, new_name);
- goto fail;
- }
- newnames[n_newvars] = strdup (new_name);
- lex_get (lexer);
- n_newvars++;
- }
+ {
+ if (n_newvars >= n_oldvars)
+ break;
+ const char *new_name = lex_tokcstr (lexer);
+ if (!relax && ! id_is_plausible (new_name, true))
+ goto fail;
+
+ if (!check_rename (dict, var_get_name (oldvars[n_newvars]), new_name))
+ goto fail;
+ newnames[n_newvars] = strdup (new_name);
+ lex_get (lexer);
+ n_newvars++;
+ }
+ free (prefix);
+
if (n_newvars != n_oldvars)
{
msg (SE, _("Number of variables on left side of `=' (%zu) does not "