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) while (1)
{ {
if (!(in[stop_pos ])) const wchar_t nc = in[stop_pos];
if (!(nc))
break; break;
if (!(iswalnum(in[stop_pos]) || if (nc == VARIABLE_EXPAND_EMPTY)
(wcschr(L"_", in[stop_pos])!= 0))) {
stop_pos++;
break;
}
if (!(wcsvarchr(nc)))
break; break;
stop_pos++; 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); 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()) if (! var_val.missing())
{ {
@ -1174,7 +1187,18 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
{ {
in[i]=0; in[i]=0;
wcstring res = in; wcstring res = in;
if (i > 0)
{
if (in[i-1] != VARIABLE_EXPAND_SINGLE)
{
res.push_back(INTERNAL_SEPARATOR); 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++) for (size_t j=0; j<var_item_list.size(); j++)
{ {
@ -1204,15 +1228,19 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (is_ok) if (is_ok)
{ {
wcstring new_in; wcstring new_in;
new_in.append(in, i);
if (start_pos > 0) if (i > 0)
new_in.append(in, start_pos - 1); {
if (in[i-1] != VARIABLE_EXPAND)
// at this point new_in.size() is start_pos - 1
if (start_pos>1 && new_in[start_pos-2]!=VARIABLE_EXPAND)
{ {
new_in.push_back(INTERNAL_SEPARATOR); new_in.push_back(INTERNAL_SEPARATOR);
} }
else if (next.empty())
{
new_in.push_back(VARIABLE_EXPAND_EMPTY);
}
}
new_in.append(next); new_in.append(next);
new_in.append(in + stop_pos); new_in.append(in + stop_pos);
is_ok &= expand_variables2(parser, new_in, out, i, errors); is_ok &= expand_variables2(parser, new_in, out, i, errors);
@ -1243,8 +1271,11 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
Expansion to single argument. Expansion to single argument.
*/ */
wcstring res; wcstring res;
in[i] = 0; res.append(in, i);
res.append(in); if (i > 0 && in[i-1] == VARIABLE_EXPAND_SINGLE)
{
res.push_back(VARIABLE_EXPAND_EMPTY);
}
res.append(in + stop_pos); res.append(in + stop_pos);
is_ok &= expand_variables2(parser, res, out, i, errors); is_ok &= expand_variables2(parser, res, out, i, errors);

View file

@ -102,6 +102,11 @@ enum
*/ */
INTERNAL_SEPARATOR, 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 Testing high level script functionality
File expansion.in tested ok
File printf.in tested ok File printf.in tested ok
File test1.in tested ok File test1.in tested ok
File test2.in tested ok File test2.in tested ok