1########################################################################
2##
3## Copyright (C) 2008-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {@var{varname} =} genvarname (@var{str})
28## @deftypefnx {} {@var{varname} =} genvarname (@var{str}, @var{exclusions})
29##
30## This function is obsolete.  Use @code{matlab.lang.makeValidName} or
31## @code{matlab.lang.makeUniqueStrings} instead.
32##
33## Create valid unique variable name(s) from @var{str}.
34##
35## If @var{str} is a cellstr, then a unique variable is created for each cell
36## in @var{str}.
37##
38## @example
39## @group
40## genvarname (@{"foo", "foo"@})
41##   @result{}
42##      @{
43##        [1,1] = foo
44##        [1,2] = foo1
45##      @}
46## @end group
47## @end example
48##
49## If @var{exclusions} is given, then the variable(s) will be unique to each
50## other and to @var{exclusions} (@var{exclusions} may be either a string or a
51## cellstr).
52##
53## @example
54## @group
55## x = 3.141;
56## genvarname ("x", who ())
57##   @result{} x1
58## @end group
59## @end example
60##
61## Note that the result is a char array or cell array of strings, not the
62## variables themselves.  To define a variable, @code{eval()} can be used.
63## The following trivial example sets @code{x} to 42.
64##
65## @example
66## @group
67## name = genvarname ("x");
68## eval ([name " = 42"]);
69##   @result{} x =  42
70## @end group
71## @end example
72##
73## This can be useful for creating unique struct field names.
74##
75## @example
76## @group
77## x = struct ();
78## for i = 1:3
79##   x.(genvarname ("a", fieldnames (x))) = i;
80## endfor
81##   @result{} x =
82##      @{
83##        a =  1
84##        a1 =  2
85##        a2 =  3
86##      @}
87## @end group
88## @end example
89##
90## Since variable names may only contain letters, digits, and underscores,
91## @code{genvarname} will replace any sequence of disallowed characters with
92## an underscore.  Also, variables may not begin with a digit; in this case
93## an @samp{x} is added before the variable name.
94##
95## Variable names beginning and ending with two underscores @qcode{"__"} are
96## valid, but they are used internally by Octave and should generally be
97## avoided; therefore, @code{genvarname} will not generate such names.
98##
99## @code{genvarname} will also ensure that returned names do not clash with
100## keywords such as @qcode{"for"} and @qcode{"if"}.  A number will be
101## appended if necessary.  Note, however, that this does @strong{not} include
102## function names such as @qcode{"sin"}.  Such names should be included in
103## @var{exclusions} if necessary.
104## @seealso{matlab.lang.makeValidName, matlab.lang.makeUniqueStrings,
105## namelengthmax, isvarname, iskeyword, exist, who, tempname, eval}
106## @end deftypefn
107
108function varname = genvarname (str, exclusions = {})
109
110  persistent warned = false;
111  if (! warned)
112    warned = true;
113    warning ("Octave:legacy-function",
114             "genvarname is obsolete; use matlab.lang.makeValidName or matlab.lang.makeUniqueStrings instead\n");
115  endif
116
117  if (nargin < 1 || nargin > 2)
118    print_usage ();
119  endif
120
121  strinput = ischar (str);
122  ## Process the inputs
123  if (strinput)
124    if (rows (str) != 1)
125      error ("genvarname: if more than one STR is given, it must be a cellstr");
126    endif
127    str = {str};
128  elseif (! iscellstr (str))
129    error ("genvarname: STR must be a string or cellstr");
130  endif
131
132  if (ischar (exclusions))
133    if (rows (exclusions) != 1)
134      error ("genvarname: if more than one exclusion is given, it must be a cellstr");
135    endif
136    exclusions = {exclusions};
137  elseif (! iscellstr (exclusions))
138    error ("genvarname: EXCLUSIONS must be a string or cellstr");
139  else
140    exclusions = exclusions(:);
141  endif
142
143  varname = cell (size (str));
144  for i = 1:numel (str)
145    ## Perform any modifications to the varname to make sure that it is
146    ## a valid variable name.
147
148    ## remove invalid characters
149    str{i}(! (isalnum (str{i}) | str{i} == "_")) = "_";
150    ## do not use keywords
151    if (iskeyword (str{i}))
152      firstcharacter = toupper (str{i}(1));
153      str{i} = ["x", firstcharacter, str{i}(2:end)];
154    endif
155    ## The variable cannot be empty
156    if (isempty (str{i}))
157      str{i} = "x";
158    endif
159    ## Leading underscores are not Matlab compatible
160    if (str{i}(1) == "_")
161      str{i} = ["x", str{i}];
162    endif
163    ## it cannot start with a number
164    if (isdigit (str{i}(1)))
165      str{i} = ["x", str{i}];
166    endif
167
168    ## make sure that the variable is unique relative to other variables
169    ## and the exclusions list
170    excluded = any (strcmp (str{i}, exclusions));
171    if (excluded && isdigit (str{i}(end)))
172      ## if it is not unique and ends with a digit, add an underscore to
173      ## make the variable name more readable ("x1_1" instead of "x11")
174      str{i} = [str{i}, "_"];
175    endif
176    varname(i) = str(i);
177    idx = 0;
178    while (excluded)
179      idx += 1;
180      varname{i} = sprintf ("%s%d", str{i}, idx);
181      excluded = any (strcmp (varname{i}, exclusions));
182    endwhile
183    exclusions(end+1) = varname(i);
184  endfor
185
186  if (strinput)
187    varname = varname{1};
188  endif
189
190endfunction
191
192
193## a single argument
194%!assert (genvarname ("a"), "a")
195## a single argument with a non-conflicting exception
196%!assert (genvarname ("a", "b"), "a")
197## a single argument with a conflicting exception
198%!assert (genvarname ("a", "a"), "a1")
199## a single argument as a cell
200%!assert (genvarname ({"a"}), {"a"})
201%!assert (genvarname ({"a"}, "b"), {"a"})
202%!assert (genvarname ({"a"}, {"b"}), {"a"})
203%!assert (genvarname ({"a"}, "a"), {"a1"})
204%!assert (genvarname ({"a"}, {"a"}), {"a1"})
205## Test different arguments
206## orientation
207%!assert (genvarname ({"a" "b"}), {"a" "b"})
208%!assert (genvarname ({"a";"b"}), {"a";"b"})
209%!assert (genvarname ({"a" "a"}), {"a" "a1"})
210%!assert (genvarname ({"a" "b";"c" "d"}), {"a" "b";"c" "d"})
211%!assert (genvarname ({"a" "a" "a";"a" "a" "a"}), {"a" "a2" "a4";"a1" "a3" "a5"})
212## more than one repetition
213%!assert (genvarname ({"a" "a" "a"}), {"a" "a1" "a2"})
214%!assert (genvarname ({"a" "a" "a"}, {"a" "a1" "a2"}), {"a3" "a4" "a5"})
215## more than one repetition not in order
216%!assert (genvarname ({"a" "b" "a" "b" "a"}), {"a" "b" "a1" "b1" "a2"})
217## Variable name munging
218%!assert (genvarname ("__x__"), "x__x__")
219%!assert (genvarname ("123456789"), "x123456789")
220%!assert (genvarname ("_$1__"), "x__1__")
221%!assert (genvarname ("__foo__", "x__foo__"), "x__foo__1")
222%!assert (genvarname ("1million_and1", "x1million_and1"), "x1million_and1_1")
223%!assert (genvarname ({"", "", ""}), {"x", "x1", "x2"})
224%!assert (genvarname ("if"), "xIf")
225%!assert (genvarname ({"if", "if", "if"}), {"xIf", "xIf1", "xIf2"})
226## Exclusions in odd format
227%!assert (genvarname ("x", {"a", "b"; "x", "d"}), "x1")
228
229## Test input validation
230%!error genvarname ()
231%!error genvarname (1,2,3)
232%!error <more than one STR is given, it must be a cellstr> genvarname (char ("a", "b", "c"))
233%!error <STR must be a string or cellstr> genvarname (1)
234%!error <more than one exclusion is given, it must be a cellstr> genvarname ("x", char ("a", "b", "c"))
235%!error <EXCLUSIONS must be a string or cellstr> genvarname ("x", 1)
236