Further refactoring of builtin_set

This rearranges some error handling to centralize it, and performs
additional cleanup.
This commit is contained in:
ridiculousfish 2021-02-13 18:23:22 -08:00
parent 6c46ea0ed2
commit 0b06a0ee07

View file

@ -436,26 +436,6 @@ maybe_t<split_var_t> split_var_and_indexes(const wchar_t *arg, env_mode_flags_t
return res; return res;
} }
static int update_values(wcstring_list_t &list, std::vector<long> &indexes,
const wcstring_list_t &values) {
// Replace values where needed.
for (size_t i = 0; i < indexes.size(); i++) {
// The '- 1' below is because the indices in fish are one-based, but the vector uses
// zero-based indices.
long ind = indexes[i] - 1;
const wcstring newv = values[i];
if (ind < 0) {
return 1;
}
if (static_cast<size_t>(ind) >= list.size()) {
list.resize(ind + 1);
}
list[ind] = newv;
}
return 0;
}
/// Given a list of values and 1-based indexes, return a new list, with those elements removed. /// Given a list of values and 1-based indexes, return a new list, with those elements removed.
/// Note this deliberately accepts both args by value, as it modifies them both. /// Note this deliberately accepts both args by value, as it modifies them both.
@ -706,67 +686,62 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc,
return ret; return ret;
} }
/// This handles the common case of setting the entire var to a set of values. /// Return a list of new values for the variable \p varname, respecting the \p opts.
static int set_var_array(const wchar_t *cmd, const set_cmd_opts_t &opts, const wcstring &varname, /// The arguments are given as the argc, argv pair.
wcstring_list_t &new_values, int argc, wchar_t **argv, parser_t &parser, /// This handles the simple case where there are no indexes.
const io_streams_t &streams) { static wcstring_list_t new_var_values(const wcstring &varname, const set_cmd_opts_t &opts, int argc,
UNUSED(cmd); const wchar_t *const *argv, const environment_t &vars) {
UNUSED(streams); wcstring_list_t result;
if (!opts.prepend && !opts.append) {
if (opts.prepend || opts.append) { // Not prepending or appending.
if (opts.prepend) { result.assign(argv, argv + argc);
for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } else {
// Note: when prepending or appending, we always use default scoping when fetching existing
// values. For example:
// set --global var 1 2
// set --local --append var 3 4
// This starts with the existing global variable, appends to it, and sets it locally.
// So do not use the given variable: we must re-fetch it.
// TODO: this races under concurrent execution.
if (auto existing = vars.get(varname, ENV_DEFAULT)) {
result = existing->as_list();
} }
auto var_str = parser.vars().get(varname, ENV_DEFAULT); if (opts.prepend) {
if (var_str) { result.insert(result.begin(), argv, argv + argc);
const auto &var_array = var_str->as_list();
new_values.insert(new_values.end(), var_array.begin(), var_array.end());
} }
if (opts.append) { if (opts.append) {
for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); result.insert(result.end(), argv, argv + argc);
} }
} else {
for (int i = 0; i < argc; i++) new_values.push_back(argv[i]);
} }
return result;
return STATUS_CMD_OK;
} }
/// This handles the more difficult case of setting individual slices of a var. /// This handles the more difficult case of setting individual slices of a var.
static int set_var_slices(const wchar_t *cmd, set_cmd_opts_t &opts, const wcstring &varname, static wcstring_list_t new_var_values_by_index(const split_var_t &split, int argc,
wcstring_list_t &new_values, std::vector<long> &indexes, int argc, const wchar_t *const *argv) {
wchar_t **argv, parser_t &parser, io_streams_t &streams) { assert(static_cast<size_t>(argc) == split.indexes.size() &&
UNUSED(parser); "Must have the same number of indexes as arguments");
if (opts.append || opts.prepend) { // Inherit any existing values.
streams.err.append_format( // Note unlike the append/prepend case, we start with a variable in the same scope as we are
L"%ls: Cannot use --append or --prepend when assigning to a slice", cmd); // setting.
builtin_print_error_trailer(parser, streams.err, cmd);
return STATUS_INVALID_ARGS;
}
if (indexes.size() != static_cast<size_t>(argc)) {
streams.err.append_format(BUILTIN_SET_MISMATCHED_ARGS, cmd, indexes.size(), argc);
return STATUS_INVALID_ARGS;
}
int scope = compute_scope(opts); // calculate the variable scope based on the provided options
const auto var_str = parser.vars().get(varname, scope);
if (var_str) var_str->to_list(new_values);
// Slice indexes have been calculated, do the actual work.
wcstring_list_t result; wcstring_list_t result;
for (int i = 0; i < argc; i++) result.push_back(argv[i]); if (split.var) result = split.var->as_list();
int retval = update_values(new_values, indexes, result); // For each (index, argument) pair, set the element in our \p result to the replacement string.
if (retval != STATUS_CMD_OK) { // Extend the list with empty strings as needed. The indexes are 1-based.
streams.err.append_format(BUILTIN_SET_ARRAY_BOUNDS_ERR, cmd); for (int i = 0; i < argc; i++) {
return retval; long lidx = split.indexes.at(i);
assert(lidx >= 1 && "idx should have been verified in range already");
// Convert from 1-based to 0-based.
size_t idx = static_cast<size_t>(lidx - 1);
// Extend as needed with empty strings.
if (idx >= result.size()) result.resize(idx + 1);
result.at(idx) = argv[i];
} }
return result;
return STATUS_CMD_OK;
} }
/// Set a variable. /// Set a variable.
@ -789,28 +764,52 @@ static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, w
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
} }
// Is the variable valid?
if (!valid_var_name(split->varname)) { if (!valid_var_name(split->varname)) {
streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, split->varname.c_str()); streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, split->varname.c_str());
builtin_print_error_trailer(parser, streams.err, cmd); builtin_print_error_trailer(parser, streams.err, cmd);
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
} }
int retval; // Setting with explicit indexes like `set foo[3] ...` has additional error handling.
if (!split->indexes.empty()) {
// Indexes must be > 0. (Note split_var_and_indexes negates negative values).
for (long v : split->indexes) {
if (v <= 0) {
streams.err.append_format(BUILTIN_SET_ARRAY_BOUNDS_ERR, cmd);
return STATUS_INVALID_ARGS;
}
}
// Append and prepend are disallowed.
if (opts.append || opts.prepend) {
streams.err.append_format(
L"%ls: Cannot use --append or --prepend when assigning to a slice", cmd);
builtin_print_error_trailer(parser, streams.err, cmd);
return STATUS_INVALID_ARGS;
}
// Argument count and index count must agree.
if (split->indexes.size() != static_cast<size_t>(argc)) {
streams.err.append_format(BUILTIN_SET_MISMATCHED_ARGS, cmd, split->indexes.size(),
argc);
return STATUS_INVALID_ARGS;
}
}
wcstring_list_t new_values; wcstring_list_t new_values;
if (split->indexes.empty()) { if (split->indexes.empty()) {
// Handle the simple, common, case. Set the var to the specified values. // Handle the simple, common, case. Set the var to the specified values.
retval = set_var_array(cmd, opts, split->varname, new_values, argc, argv, parser, streams); new_values = new_var_values(split->varname, opts, argc, argv, parser.vars());
} else { } else {
// Handle the uncommon case of setting specific slices of a var. // Handle the uncommon case of setting specific slices of a var.
retval = set_var_slices(cmd, opts, split->varname, new_values, split->indexes, argc, argv, new_values = new_var_values_by_index(*split, argc, argv);
parser, streams);
} }
if (retval != STATUS_CMD_OK) return retval;
// Set the value back in the variable stack and fire any events.
std::vector<event_t> evts; std::vector<event_t> evts;
retval = env_set_reporting_errors(cmd, split->varname, scope, std::move(new_values), streams, int retval = env_set_reporting_errors(cmd, split->varname, scope, std::move(new_values),
parser.vars(), &evts); streams, parser.vars(), &evts);
// Fire any events.
for (const auto &evt : evts) { for (const auto &evt : evts) {
event_fire(parser, evt); event_fire(parser, evt);
} }