aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--expand.cpp119
-rw-r--r--expand.h5
-rw-r--r--highlight.cpp32
-rw-r--r--tests/expansion.err30
-rw-r--r--tests/expansion.in82
-rw-r--r--tests/expansion.out42
-rw-r--r--tests/expansion.status1
-rw-r--r--tests/top.out1
8 files changed, 283 insertions, 29 deletions
diff --git a/expand.cpp b/expand.cpp
index d8cf4a1a..5f96fb2f 100644
--- a/expand.cpp
+++ b/expand.cpp
@@ -953,8 +953,11 @@ void expand_variable_error(parser_t &parser, const wcstring &token, size_t token
/**
Parse an array slicing specification
+ Returns 0 on success.
+ If a parse error occurs, returns the index of the bad token.
+ Note that 0 can never be a bad index because the string always starts with [.
*/
-static int parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx, std::vector<size_t> &source_positions, size_t array_size)
+static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx, std::vector<size_t> &source_positions, size_t array_size)
{
wchar_t *end;
@@ -981,7 +984,7 @@ static int parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &
tmp = wcstol(&in[pos], &end, 10);
if ((errno) || (end == &in[pos]))
{
- return 1;
+ return pos;
}
// debug( 0, L"Push idx %d", tmp );
@@ -999,7 +1002,7 @@ static int parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &
long tmp1 = wcstol(&in[pos], &end, 10);
if ((errno) || (end == &in[pos]))
{
- return 1;
+ return pos;
}
pos = end-in;
@@ -1086,10 +1089,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 +1116,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())
{
@@ -1123,12 +1139,14 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
if (in[slice_start] == L'[')
{
wchar_t *slice_end;
+ size_t bad_pos;
all_vars=0;
- if (parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, var_item_list.size()))
+ bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, var_item_list.size());
+ if (bad_pos != 0)
{
append_syntax_error(errors,
- stop_pos,
+ stop_pos + bad_pos,
L"Invalid index value");
is_ok = 0;
break;
@@ -1142,10 +1160,10 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
for (size_t j=0; j<var_idx_list.size(); j++)
{
long tmp = var_idx_list.at(j);
- size_t var_src_pos = var_pos_list.at(j);
/* Check that we are within array bounds. If not, truncate the list to exit. */
if (tmp < 1 || (size_t)tmp > var_item_list.size())
{
+ size_t var_src_pos = var_pos_list.at(j);
/* The slice was parsed starting at stop_pos, so we have to add that to the error position */
append_syntax_error(errors,
slice_start + var_src_pos,
@@ -1174,7 +1192,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 +1233,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);
@@ -1227,6 +1260,41 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
}
else
{
+ // even with no value, we still need to parse out slice syntax
+ // Behave as though we had 1 value, so $foo[1] always works.
+ const size_t slice_start = stop_pos;
+ if (in[slice_start] == L'[')
+ {
+ wchar_t *slice_end;
+ size_t bad_pos;
+
+ bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, 1);
+ if (bad_pos != 0)
+ {
+ append_syntax_error(errors,
+ stop_pos + bad_pos,
+ L"Invalid index value");
+ is_ok = 0;
+ return is_ok;
+ }
+ stop_pos = (slice_end-in);
+
+ // validate that the parsed indexes are valid
+ for (size_t j=0; j<var_idx_list.size(); j++)
+ {
+ long tmp = var_idx_list.at(j);
+ if (tmp != 1)
+ {
+ size_t var_src_pos = var_pos_list.at(j);
+ append_syntax_error(errors,
+ slice_start + var_src_pos,
+ ARRAY_BOUNDS_ERR);
+ is_ok = 0;
+ return is_ok;
+ }
+ }
+ }
+
/*
Expand a non-existing variable
*/
@@ -1243,8 +1311,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);
@@ -1435,11 +1506,14 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
{
std::vector<long> slice_idx;
std::vector<size_t> slice_source_positions;
+ const wchar_t * const slice_begin = tail_begin;
wchar_t *slice_end;
+ size_t bad_pos;
- if (parse_slice(tail_begin, &slice_end, slice_idx, slice_source_positions, sub_res.size()))
+ bad_pos = parse_slice(slice_begin, &slice_end, slice_idx, slice_source_positions, sub_res.size());
+ if (bad_pos != 0)
{
- append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Invalid index value");
+ append_syntax_error(errors, slice_begin - in + bad_pos, L"Invalid index value");
return 0;
}
else
@@ -1451,8 +1525,9 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
long idx = slice_idx.at(i);
if (idx < 1 || (size_t)idx > sub_res.size())
{
+ size_t pos = slice_source_positions.at(i);
append_syntax_error(errors,
- SOURCE_LOCATION_UNKNOWN,
+ slice_begin - in + pos,
ARRAY_BOUNDS_ERR);
return 0;
}
diff --git a/expand.h b/expand.h
index 3956b1f1..14a4f477 100644
--- a/expand.h
+++ b/expand.h
@@ -102,6 +102,11 @@ enum
*/
INTERNAL_SEPARATOR,
+ /**
+ Character representing an empty variable expansion.
+ Only used transitively while expanding variables.
+ */
+ VARIABLE_EXPAND_EMPTY,
}
;
diff --git a/highlight.cpp b/highlight.cpp
index 287f1a3b..3fdb6c8f 100644
--- a/highlight.cpp
+++ b/highlight.cpp
@@ -617,12 +617,26 @@ static size_t color_variable(const wchar_t *in, size_t in_len, std::vector<highl
if (in[idx] == L'[')
{
wchar_t *slice_begin = NULL, *slice_end = NULL;
- if (1 == parse_util_locate_slice(in, &slice_begin, &slice_end, false))
+ switch (parse_util_locate_slice(in, &slice_begin, &slice_end, false))
{
- size_t slice_begin_idx = slice_begin - in, slice_end_idx = slice_end - in;
- assert(slice_end_idx > slice_begin_idx);
- colors[slice_begin_idx] = highlight_spec_operator;
- colors[slice_end_idx] = highlight_spec_operator;
+ case 1:
+ {
+ size_t slice_begin_idx = slice_begin - in, slice_end_idx = slice_end - in;
+ assert(slice_end_idx > slice_begin_idx);
+ colors[slice_begin_idx] = highlight_spec_operator;
+ colors[slice_end_idx] = highlight_spec_operator;
+ break;
+ }
+ case -1:
+ {
+ // syntax error
+ // Normally the entire token is colored red for us, but inside a double-quoted string
+ // that doesn't happen. As such, color the variable + the slice start red. Coloring any
+ // more than that looks bad, unless we're willing to try and detect where the double-quoted
+ // string ends, and I'd rather not do that.
+ std::fill(colors, colors + idx + 1, (highlight_spec_t)highlight_spec_error);
+ break;
+ }
}
}
return idx;
@@ -861,7 +875,11 @@ static void color_argument_internal(const wcstring &buffstr, std::vector<highlig
*/
case e_double_quoted:
{
- colors[in_pos] = highlight_spec_quote;
+ // slices are colored in advance, past `in_pos`, and we don't want to overwrite that
+ if (colors[in_pos] == highlight_spec_param)
+ {
+ colors[in_pos] = highlight_spec_quote;
+ }
switch (c)
{
case L'"':
@@ -876,7 +894,7 @@ static void color_argument_internal(const wcstring &buffstr, std::vector<highlig
if (in_pos + 1 < buff_len)
{
const wchar_t escaped_char = buffstr.at(in_pos + 1);
- if (escaped_char == L'\\' || escaped_char == L'\'' || escaped_char == L'$')
+ if (wcschr(L"\\\"\n$", escaped_char))
{
colors[in_pos] = highlight_spec_escape; //backslash
colors[in_pos + 1] = highlight_spec_escape; //escaped char
diff --git a/tests/expansion.err b/tests/expansion.err
new file mode 100644
index 00000000..e60c203d
--- /dev/null
+++ b/tests/expansion.err
@@ -0,0 +1,30 @@
+Array index out of bounds
+fish: show "$foo[2]"
+ ^
+Array index out of bounds
+fish: show $foo[2]
+ ^
+Array index out of bounds
+fish: show "$foo[1 2]"
+ ^
+Array index out of bounds
+fish: show $foo[1 2]
+ ^
+Array index out of bounds
+fish: show "$foo[2 1]"
+ ^
+Array index out of bounds
+fish: show $foo[2 1]
+ ^
+Invalid index value
+fish: echo "$foo[d]"
+ ^
+Invalid index value
+fish: echo $foo[d]
+ ^
+Array index out of bounds
+fish: echo ()[1]
+ ^
+Invalid index value
+fish: echo ()[d]
+ ^
diff --git a/tests/expansion.in b/tests/expansion.in
new file mode 100644
index 00000000..a2225127
--- /dev/null
+++ b/tests/expansion.in
@@ -0,0 +1,82 @@
+# 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
+
+set -l foo
+show "$foo[1]"
+show $foo[1]
+show "$foo[-1]"
+show $foo[-1]
+show "$foo[2]"
+show $foo[2]
+show "$foo[1 2]"
+show $foo[1 2]
+show "$foo[2 1]"
+show $foo[2 1]
+
+echo "$foo[d]"
+echo $foo[d]
+
+echo ()[1]
+echo ()[d]
diff --git a/tests/expansion.out b/tests/expansion.out
new file mode 100644
index 00000000..fe594539
--- /dev/null
+++ b/tests/expansion.out
@@ -0,0 +1,42 @@
+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
+0
+1
+0
diff --git a/tests/expansion.status b/tests/expansion.status
new file mode 100644
index 00000000..573541ac
--- /dev/null
+++ b/tests/expansion.status
@@ -0,0 +1 @@
+0
diff --git a/tests/top.out b/tests/top.out
index f2873b15..a62ec047 100644
--- a/tests/top.out
+++ b/tests/top.out
@@ -1,4 +1,5 @@
Testing high level script functionality
+File expansion.in tested ok
File printf.in tested ok
File read.in tested ok
File test1.in tested ok