Fix double expansions ($$foo)

Double expansions of variables had the following issues:

* `"$$foo"` threw an error no matter what the value of `$foo` was.
* `set -l foo ''; echo $$foo` threw an error because of the expansion of
  `$foo` to `''`.

With this change, double expansion always works properly. When
double-expanding a multi-valued variable, in a double-quoted string the
first word of the inner expansion is used for the outer expansion, and
outside of a quoted string every word is used for the double-expansion
in each of the arguments.

    > set -l foo bar baz
    > set -l bar one two
    > set -l baz three four
    > echo "$$foo"
    one two baz
    > echo $$foo
    one two three four
This commit is contained in:
Kevin Ballard 2014-08-20 21:19:08 -07:00
parent d0c85471b4
commit 3981b644d6
7 changed files with 153 additions and 13 deletions

View file

@ -1086,10 +1086,15 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
while (1)
{
if (!(in[stop_pos ]))
const wchar_t nc = in[stop_pos];
if (!(nc))
break;
if (!(iswalnum(in[stop_pos]) ||
(wcschr(L"_", in[stop_pos])!= 0)))
if (nc == VARIABLE_EXPAND_EMPTY)
{
stop_pos++;
break;
}
if (!(wcsvarchr(nc)))
break;
stop_pos++;
@ -1108,7 +1113,15 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
}
var_tmp.append(in + start_pos, var_len);
env_var_t var_val = expand_var(var_tmp.c_str());
env_var_t var_val;
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY)
{
var_val = env_var_t::missing_var();
}
else
{
var_val = expand_var(var_tmp.c_str());
}
if (! var_val.missing())
{
@ -1174,7 +1187,18 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
{
in[i]=0;
wcstring res = in;
res.push_back(INTERNAL_SEPARATOR);
if (i > 0)
{
if (in[i-1] != VARIABLE_EXPAND_SINGLE)
{
res.push_back(INTERNAL_SEPARATOR);
}
else if (var_item_list.empty() || var_item_list.front().empty())
{
// first expansion is empty, but we need to recursively expand
res.push_back(VARIABLE_EXPAND_EMPTY);
}
}
for (size_t j=0; j<var_item_list.size(); j++)
{
@ -1204,14 +1228,18 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok)
{
wcstring new_in;
new_in.append(in, i);
if (start_pos > 0)
new_in.append(in, start_pos - 1);
// at this point new_in.size() is start_pos - 1
if (start_pos>1 && new_in[start_pos-2]!=VARIABLE_EXPAND)
if (i > 0)
{
new_in.push_back(INTERNAL_SEPARATOR);
if (in[i-1] != VARIABLE_EXPAND)
{
new_in.push_back(INTERNAL_SEPARATOR);
}
else if (next.empty())
{
new_in.push_back(VARIABLE_EXPAND_EMPTY);
}
}
new_in.append(next);
new_in.append(in + stop_pos);
@ -1243,8 +1271,11 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
Expansion to single argument.
*/
wcstring res;
in[i] = 0;
res.append(in);
res.append(in, i);
if (i > 0 && in[i-1] == VARIABLE_EXPAND_SINGLE)
{
res.push_back(VARIABLE_EXPAND_EMPTY);
}
res.append(in + stop_pos);
is_ok &= expand_variables2(parser, res, out, i, errors);

View file

@ -102,6 +102,11 @@ enum
*/
INTERNAL_SEPARATOR,
/**
Character representing an empty variable expansion.
Only used transitively while expanding variables.
*/
VARIABLE_EXPAND_EMPTY,
}
;

0
tests/expansion.err Normal file
View file

64
tests/expansion.in Normal file
View file

@ -0,0 +1,64 @@
# Test expansion of variables
function show --description 'Prints argument count followed by arguments'
echo (count $argv) $argv
end
set -l foo
show "$foo"
show $foo
show "prefix$foo"
show prefix$foo
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo ''
show "$foo"
show $foo
show "prefix$foo"
show prefix$foo
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo bar
set -l bar
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l bar baz
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l bar baz quux
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo bar fooer fooest
set -l fooer
set -l fooest
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l fooer ''
show $$foo
show prefix$$foo
set -l foo bar '' fooest
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo

38
tests/expansion.out Normal file
View file

@ -0,0 +1,38 @@
1
0
1 prefix
0
1
0
1 prefix
0
1
1
1 prefix
1 prefix
1
0
1 prefix
0
1
0
1 prefix
0
1 baz
1 baz
1 prefixbaz
1 prefixbaz
1 baz quux
2 baz quux
1 prefixbaz quux
2 prefixbaz prefixquux
1 baz quux fooer fooest
2 baz quux
1 prefixbaz quux fooer fooest
2 prefixbaz prefixquux
3 baz quux
3 prefixbaz prefixquux prefix
1 baz quux fooest
2 baz quux
1 prefixbaz quux fooest
2 prefixbaz prefixquux

1
tests/expansion.status Normal file
View file

@ -0,0 +1 @@
0

View file

@ -1,4 +1,5 @@
Testing high level script functionality
File expansion.in tested ok
File printf.in tested ok
File test1.in tested ok
File test2.in tested ok