mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-27 11:43:37 +08:00
Clean up expand_variables
Partially rewrite this function to be shorter and easier to follow.
This commit is contained in:
parent
3d1975c6a6
commit
816d35de43
313
src/expand.cpp
313
src/expand.cpp
|
@ -709,213 +709,166 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
|
|||
/// Note: last_idx is considered to be where it previously finished procesisng. This means it
|
||||
/// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
|
||||
/// as last_idx instead of string.size()-1.
|
||||
static int expand_variables(const wcstring &instr, std::vector<completion_t> *out, long last_idx,
|
||||
parse_error_list_t *errors) {
|
||||
static bool expand_variables(const wcstring &instr, std::vector<completion_t> *out, size_t last_idx,
|
||||
parse_error_list_t *errors) {
|
||||
const size_t insize = instr.size();
|
||||
|
||||
// last_idx may be 1 past the end of the string, but no further.
|
||||
assert(last_idx >= 0 && (size_t)last_idx <= insize);
|
||||
|
||||
assert(last_idx <= insize && "Invalid last_idx");
|
||||
if (last_idx == 0) {
|
||||
append_completion(out, instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_ok = true;
|
||||
bool empty = false;
|
||||
|
||||
wcstring var_tmp;
|
||||
|
||||
// List of indexes.
|
||||
std::vector<long> var_idx_list;
|
||||
|
||||
// Parallel array of source positions of each index in the variable list.
|
||||
std::vector<size_t> var_pos_list;
|
||||
|
||||
// CHECK( out, 0 );
|
||||
|
||||
for (long i = last_idx - 1; (i >= 0) && is_ok && !empty; i--) {
|
||||
const wchar_t c = instr.at(i);
|
||||
if (c != VARIABLE_EXPAND && c != VARIABLE_EXPAND_SINGLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long var_len;
|
||||
int is_single = (c == VARIABLE_EXPAND_SINGLE);
|
||||
size_t start_pos = i + 1;
|
||||
size_t stop_pos = start_pos;
|
||||
|
||||
while (stop_pos < insize) {
|
||||
const wchar_t nc = instr.at(stop_pos);
|
||||
if (nc == VARIABLE_EXPAND_EMPTY) {
|
||||
stop_pos++;
|
||||
break;
|
||||
}
|
||||
if (!valid_var_name_char(nc)) break;
|
||||
|
||||
stop_pos++;
|
||||
}
|
||||
|
||||
// fwprintf(stdout, L"Stop for '%c'\n", in[stop_pos]);
|
||||
var_len = stop_pos - start_pos;
|
||||
|
||||
if (var_len == 0) {
|
||||
if (errors) {
|
||||
parse_util_expand_variable_error(instr, 0 /* global_token_pos */, i, errors);
|
||||
}
|
||||
|
||||
is_ok = false;
|
||||
// Locate the last VARIABLE_EXPAND or VARIABLE_EXPAND_SINGLE
|
||||
bool is_single = false;
|
||||
size_t varexp_char_idx = last_idx;
|
||||
while (varexp_char_idx--) {
|
||||
const wchar_t c = instr.at(varexp_char_idx);
|
||||
if (c == VARIABLE_EXPAND || c == VARIABLE_EXPAND_SINGLE) {
|
||||
is_single = (c == VARIABLE_EXPAND_SINGLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (varexp_char_idx >= instr.size()) {
|
||||
// No variable expand char, we're done.
|
||||
append_completion(out, instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
var_tmp.append(instr, start_pos, var_len);
|
||||
maybe_t<env_var_t> var;
|
||||
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY) {
|
||||
var = none();
|
||||
} else {
|
||||
var = env_get(var_tmp);
|
||||
// Get the variable name.
|
||||
const size_t var_name_start = varexp_char_idx + 1;
|
||||
size_t var_name_stop = var_name_start;
|
||||
while (var_name_stop < insize) {
|
||||
const wchar_t nc = instr.at(var_name_stop);
|
||||
if (nc == VARIABLE_EXPAND_EMPTY) {
|
||||
var_name_stop++;
|
||||
break;
|
||||
}
|
||||
if (!valid_var_name_char(nc)) break;
|
||||
var_name_stop++;
|
||||
}
|
||||
assert(var_name_stop >= var_name_start && "Bogus variable name indexes");
|
||||
const size_t var_name_len = var_name_stop - var_name_start;
|
||||
|
||||
if (var) {
|
||||
int all_vars = 1;
|
||||
wcstring_list_t var_item_list;
|
||||
|
||||
if (is_ok) {
|
||||
var->to_list(var_item_list);
|
||||
|
||||
const size_t slice_start = stop_pos;
|
||||
if (slice_start < insize && instr.at(slice_start) == L'[') {
|
||||
wchar_t *slice_end;
|
||||
size_t bad_pos;
|
||||
all_vars = 0;
|
||||
const wchar_t *in = instr.c_str();
|
||||
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 + bad_pos, L"Invalid index value");
|
||||
is_ok = false;
|
||||
break;
|
||||
}
|
||||
stop_pos = (slice_end - in);
|
||||
}
|
||||
|
||||
if (!all_vars) {
|
||||
wcstring_list_t string_values(var_idx_list.size());
|
||||
size_t k = 0;
|
||||
for (size_t j = 0; j < var_idx_list.size(); j++) {
|
||||
long tmp = var_idx_list.at(j);
|
||||
// Check that we are within array bounds. If not, skip the element. Note:
|
||||
// Negative indices (`echo $foo[-1]`) are already converted to positive ones
|
||||
// here, So tmp < 1 means it's definitely not in.
|
||||
if ((size_t)tmp > var_item_list.size() || tmp < 1) {
|
||||
continue;
|
||||
}
|
||||
// Replace each index in var_idx_list inplace with the string value
|
||||
// at the specified index.
|
||||
string_values.at(k++) = var_item_list.at(tmp - 1);
|
||||
}
|
||||
|
||||
// string_values is the new var_item_list. Resize to remove invalid elements.
|
||||
string_values.resize(k);
|
||||
var_item_list = std::move(string_values);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_ok) {
|
||||
return is_ok;
|
||||
}
|
||||
|
||||
if (is_single) {
|
||||
wcstring res(instr, 0, i);
|
||||
if (i > 0) {
|
||||
if (instr.at(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++) {
|
||||
const wcstring &next = var_item_list.at(j);
|
||||
if (is_ok) {
|
||||
if (j != 0) res.append(L" ");
|
||||
res.append(next);
|
||||
}
|
||||
}
|
||||
assert(stop_pos <= insize);
|
||||
res.append(instr, stop_pos, insize - stop_pos);
|
||||
is_ok &= expand_variables(res, out, i, errors);
|
||||
} else {
|
||||
for (size_t j = 0; j < var_item_list.size(); j++) {
|
||||
const wcstring &next = var_item_list.at(j);
|
||||
if (is_ok && i == 0 && stop_pos == insize) {
|
||||
append_completion(out, next);
|
||||
} else {
|
||||
if (is_ok) {
|
||||
wcstring new_in;
|
||||
new_in.append(instr, 0, i);
|
||||
|
||||
if (i > 0) {
|
||||
if (instr.at(i - 1) != VARIABLE_EXPAND) {
|
||||
new_in.push_back(INTERNAL_SEPARATOR);
|
||||
} else if (next.empty()) {
|
||||
new_in.push_back(VARIABLE_EXPAND_EMPTY);
|
||||
}
|
||||
}
|
||||
assert(stop_pos <= insize);
|
||||
new_in.append(next);
|
||||
new_in.append(instr, stop_pos, insize - stop_pos);
|
||||
is_ok &= expand_variables(new_in, out, i, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is_ok;
|
||||
// It's an error if the name is empty.
|
||||
if (var_name_len == 0) {
|
||||
if (errors) {
|
||||
parse_util_expand_variable_error(instr, 0 /* global_token_pos */, varexp_char_idx,
|
||||
errors);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 (slice_start < insize && instr.at(slice_start) == L'[') {
|
||||
const wchar_t *in = instr.c_str();
|
||||
wchar_t *slice_end;
|
||||
size_t bad_pos;
|
||||
// Get the variable name as a string, then try to get the variable from env.
|
||||
const wcstring var_name(instr, var_name_start, var_name_len);
|
||||
const maybe_t<env_var_t> var =
|
||||
(var_name == wcstring{VARIABLE_EXPAND_EMPTY} ? none() : env_get(var_name));
|
||||
|
||||
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);
|
||||
// Parse out any following slice.
|
||||
// Record the end of the variable name and any following slice.
|
||||
size_t var_name_and_slice_stop = var_name_stop;
|
||||
bool all_vars = true;
|
||||
const size_t slice_start = var_name_stop;
|
||||
// List of indexes, and parallel array of source positions of each index in the variable list.
|
||||
std::vector<long> var_idx_list;
|
||||
std::vector<size_t> var_pos_list;
|
||||
if (slice_start < insize && instr.at(slice_start) == L'[') {
|
||||
all_vars = false;
|
||||
const wchar_t *in = instr.c_str();
|
||||
wchar_t *slice_end;
|
||||
// If a variable is missing, behave as though we have one value, so that $var[1] always
|
||||
// works.
|
||||
size_t effective_val_count = var ? var->as_list().size() : 1;
|
||||
size_t bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list,
|
||||
effective_val_count);
|
||||
if (bad_pos != 0) {
|
||||
append_syntax_error(errors, slice_start + bad_pos, L"Invalid index value");
|
||||
return false;
|
||||
}
|
||||
var_name_and_slice_stop = (slice_end - in);
|
||||
}
|
||||
|
||||
// Expand a non-existing variable.
|
||||
if (c == VARIABLE_EXPAND) {
|
||||
// Regular expansion, i.e. expand this argument to nothing.
|
||||
empty = true;
|
||||
if (!var) {
|
||||
// Expanding a non-existent variable.
|
||||
if (!is_single) {
|
||||
// Normal expansions of missing variables successfully expand to nothing.
|
||||
return true;
|
||||
} else {
|
||||
// Expansion to single argument.
|
||||
wcstring res;
|
||||
res.append(instr, 0, i);
|
||||
if (i > 0 && instr.at(i - 1) == VARIABLE_EXPAND_SINGLE) {
|
||||
// Replace the variable name and slice with VARIABLE_EXPAND_EMPTY.
|
||||
wcstring res(instr, 0, varexp_char_idx);
|
||||
if (!res.empty() && res.back() == VARIABLE_EXPAND_SINGLE) {
|
||||
res.push_back(VARIABLE_EXPAND_EMPTY);
|
||||
}
|
||||
assert(stop_pos <= insize);
|
||||
res.append(instr, stop_pos, insize - stop_pos);
|
||||
|
||||
is_ok &= expand_variables(res, out, i, errors);
|
||||
return is_ok;
|
||||
res.append(instr, var_name_and_slice_stop, wcstring::npos);
|
||||
return expand_variables(res, out, varexp_char_idx, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty) {
|
||||
append_completion(out, instr);
|
||||
// Ok, we have a variable. Let's expand it.
|
||||
// Start by respecting the sliced elements.
|
||||
assert(var && "Should have variable here");
|
||||
wcstring_list_t var_item_list = var->as_list();
|
||||
if (!all_vars) {
|
||||
wcstring_list_t sliced_items;
|
||||
for (long item_index : var_idx_list) {
|
||||
// Check that we are within array bounds. If not, skip the element. Note:
|
||||
// Negative indices (`echo $foo[-1]`) are already converted to positive ones
|
||||
// here, So tmp < 1 means it's definitely not in.
|
||||
// Note we are 1-based.
|
||||
if (item_index >= 1 && size_t(item_index) <= var_item_list.size()) {
|
||||
sliced_items.push_back(var_item_list.at(item_index - 1));
|
||||
}
|
||||
}
|
||||
var_item_list = std::move(sliced_items);
|
||||
}
|
||||
|
||||
return is_ok;
|
||||
if (is_single) {
|
||||
wcstring res(instr, 0, varexp_char_idx);
|
||||
if (!res.empty()) {
|
||||
if (res.back() != 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Append all entries in var_item_list, separated by spaces.
|
||||
// Remove the last space.
|
||||
if (!var_item_list.empty()) {
|
||||
for (const wcstring &item : var_item_list) {
|
||||
res.append(item);
|
||||
res.push_back(L' ');
|
||||
}
|
||||
res.pop_back();
|
||||
}
|
||||
res.append(instr, var_name_and_slice_stop, wcstring::npos);
|
||||
return expand_variables(res, out, varexp_char_idx, errors);
|
||||
} else {
|
||||
// Normal cartesian-product expansion.
|
||||
for (const wcstring &item : var_item_list) {
|
||||
if (varexp_char_idx == 0 && var_name_and_slice_stop == insize) {
|
||||
append_completion(out, item);
|
||||
} else {
|
||||
wcstring new_in(instr, 0, varexp_char_idx);
|
||||
if (!new_in.empty()) {
|
||||
if (new_in.back() != VARIABLE_EXPAND) {
|
||||
new_in.push_back(INTERNAL_SEPARATOR);
|
||||
} else if (item.empty()) {
|
||||
new_in.push_back(VARIABLE_EXPAND_EMPTY);
|
||||
}
|
||||
}
|
||||
new_in.append(item);
|
||||
new_in.append(instr, var_name_and_slice_stop, wcstring::npos);
|
||||
if (!expand_variables(new_in, out, varexp_char_idx, errors)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Perform bracket expansion.
|
||||
|
|
Loading…
Reference in New Issue
Block a user