From 816d35de4368f4d08c29cb720c70b676f6d603e3 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 30 Jan 2018 17:39:25 -0800 Subject: [PATCH] Clean up expand_variables Partially rewrite this function to be shorter and easier to follow. --- src/expand.cpp | 313 +++++++++++++++++++++---------------------------- 1 file changed, 133 insertions(+), 180 deletions(-) diff --git a/src/expand.cpp b/src/expand.cpp index a37015c73..60da42671 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -709,213 +709,166 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector *out, long last_idx, - parse_error_list_t *errors) { +static bool expand_variables(const wcstring &instr, std::vector *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 var_idx_list; - - // Parallel array of source positions of each index in the variable list. - std::vector 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 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 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 var_idx_list; + std::vector 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.