From c4d0612e5c0fbc0dd118ade84c9be055e0f2ea89 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 5 Feb 2016 01:22:46 -0800 Subject: Factor expand_string into multiple stages Breaks up a big ugly function into separable stages with a common interface. --- src/expand.cpp | 334 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 180 insertions(+), 154 deletions(-) (limited to 'src/expand.cpp') diff --git a/src/expand.cpp b/src/expand.cpp index bd2433da..dde7ddac 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -98,7 +98,7 @@ parameter expansion. */ #define UNCLEAN L"$*?\\\"'({})" -static void remove_internal_separator(wcstring &s, bool conv); +static void remove_internal_separator(wcstring *s, bool conv); /** Test if the specified argument is clean, i.e. it does not contain @@ -799,7 +799,7 @@ static bool expand_pid(const wcstring &instr_with_sep, expand_flags_t flags, std /* expand_string calls us with internal separators in instr...sigh */ wcstring instr = instr_with_sep; - remove_internal_separator(instr, false); + remove_internal_separator(&instr, false); if (instr.empty() || instr.at(0) != PROCESS_EXPAND) { @@ -1255,7 +1255,7 @@ static int expand_variables(const wcstring &instr, std::vector *ou /** Perform bracket expansion */ -static int expand_brackets(const wcstring &instr, int flags, std::vector *out, parse_error_list_t *errors) +static expand_error_t expand_brackets(const wcstring &instr, expand_flags_t flags, std::vector *out, parse_error_list_t *errors) { bool syntax_error = false; int bracket_count=0; @@ -1325,6 +1325,7 @@ static int expand_brackets(const wcstring &instr, int flags, std::vectorerase(std::remove(str->begin(), str->end(), (wchar_t)INTERNAL_SEPARATOR), str->end()); /* If conv is true, replace all instances of ANY_CHAR with '?', ANY_STRING with '*', ANY_STRING_RECURSIVE with '*' */ if (conv) { - for (size_t idx = 0; idx < str.size(); idx++) + for (size_t idx = 0; idx < str->size(); idx++) { - switch (str.at(idx)) + switch (str->at(idx)) { case ANY_CHAR: - str.at(idx) = L'?'; + str->at(idx) = L'?'; break; case ANY_STRING: case ANY_STRING_RECURSIVE: - str.at(idx) = L'*'; + str->at(idx) = L'*'; break; } } } } -expand_error_t expand_string(const wcstring &input, std::vector *out_completions, expand_flags_t flags, parse_error_list_t *errors) -{ - expand_error_t res = EXPAND_OK; - - /* Early out. If we're not completing, and there's no magic in the input, we're done. */ - if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(input)) - { - append_completion(out_completions, input); - return EXPAND_OK; - } - - std::vector incoming, outgoing; +/** + * A stage in string expansion is represented as a function that takes an input and returns a list + * of output (by reference). We get flags and errors. It may return an error; if so expansion halts. + */ +typedef expand_error_t (*expand_stage_t)(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors); - /* Handle command substitutions */ +static expand_error_t expand_stage_cmdsubst(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) +{ + expand_error_t result = EXPAND_OK; if (EXPAND_SKIP_CMDSUBST & flags) { wchar_t *begin, *end; - if (parse_util_locate_cmdsubst(input.c_str(), &begin, &end, true) != 0) + if (parse_util_locate_cmdsubst(input.c_str(), &begin, &end, true) == 0) + { + append_completion(out, input); + } + else { append_cmdsub_error(errors, SOURCE_LOCATION_UNKNOWN, L"Command substitutions not allowed"); - return EXPAND_ERROR; + result = EXPAND_ERROR; } - append_completion(&incoming, input); } else { - int cmdsubst_ok = expand_cmdsubst(input, &incoming, errors); + int cmdsubst_ok = expand_cmdsubst(input, out, errors); if (! cmdsubst_ok) { - return EXPAND_ERROR; + result = EXPAND_ERROR; } } + return result; +} - /* Expand variables */ - for (size_t i=0; i < incoming.size(); i++) +static expand_error_t expand_stage_variables(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) +{ + /* + We accept incomplete strings here, since complete uses + expand_string to expand incomplete strings from the + commandline. + */ + wcstring next; + unescape_string(input, &next, UNESCAPE_SPECIAL | UNESCAPE_INCOMPLETE); + + if (EXPAND_SKIP_VARIABLES & flags) { - /* - We accept incomplete strings here, since complete uses - expand_string to expand incomplete strings from the - commandline. - */ - wcstring next; - unescape_string(incoming.at(i).completion, &next, UNESCAPE_SPECIAL | UNESCAPE_INCOMPLETE); - - if (EXPAND_SKIP_VARIABLES & flags) - { - for (size_t i=0; i < next.size(); i++) - { - if (next.at(i) == VARIABLE_EXPAND) - { - next[i] = L'$'; - } - } - append_completion(&outgoing, next); - } - else + for (size_t i=0; i < next.size(); i++) { - if (!expand_variables(next, &outgoing, next.size(), errors)) + if (next.at(i) == VARIABLE_EXPAND) { - return EXPAND_ERROR; + next[i] = L'$'; } } + append_completion(out, next); } - - /* Outgoing is new incoming for next round */ - incoming.clear(); - incoming.swap(outgoing); - - /* Expand brackets */ - for (size_t i=0; i < incoming.size(); i++) + else { - const wcstring &next = incoming.at(i).completion; - if (!expand_brackets(next, flags, &outgoing, errors)) + if (!expand_variables(next, out, next.size(), errors)) { return EXPAND_ERROR; } } - incoming.clear(); - incoming.swap(outgoing); - - /* Expand home directories and process expansion */ - for (size_t i=0; i < incoming.size(); i++) - { - wcstring next = incoming.at(i).completion; + return EXPAND_OK; +} - if (!(EXPAND_SKIP_HOME_DIRECTORIES & flags)) - expand_home_directory(next); +static expand_error_t expand_stage_brackets(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) +{ + return expand_brackets(input, flags, out, errors); +} - if (flags & EXPAND_FOR_COMPLETIONS) +static expand_error_t expand_stage_home_and_pid(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) +{ + wcstring next = input; + + if (!(EXPAND_SKIP_HOME_DIRECTORIES & flags)) + { + expand_home_directory(next); + } + + if (flags & EXPAND_FOR_COMPLETIONS) + { + if (! next.empty() && next.at(0) == PROCESS_EXPAND) { - if (! next.empty() && next.at(0) == PROCESS_EXPAND) - { - /* - If process expansion matches, we are not - interested in other completions, so we - short-circuit and return - */ - expand_pid(next, flags, out_completions, NULL); - return EXPAND_OK; - } - else - { - append_completion(&outgoing, next); - } + expand_pid(next, flags, out, NULL); + return EXPAND_OK; } - else if (! expand_pid(next, flags, &outgoing, errors)) + else { - return EXPAND_ERROR; + append_completion(out, next); } } - incoming.clear(); - incoming.swap(outgoing); - - /* Expand wildcards */ - for (size_t i=0; i < incoming.size(); i++) + else if (! expand_pid(next, flags, out, errors)) { - wcstring next = incoming.at(i).completion; - int wc_res; - - remove_internal_separator(next, (EXPAND_SKIP_WILDCARDS & flags) ? true : false); - const bool has_wildcard = wildcard_has(next, true /* internal, i.e. ANY_CHAR */); + return EXPAND_ERROR; + } + return EXPAND_OK; +} - if (has_wildcard && (flags & EXECUTABLES_ONLY)) +static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector *out, expand_flags_t flags, parse_error_list_t *errors) +{ + expand_error_t result = EXPAND_OK; + wcstring next = input; + + remove_internal_separator(&next, (EXPAND_SKIP_WILDCARDS & flags) ? true : false); + const bool has_wildcard = wildcard_has(next, true /* internal, i.e. ANY_CHAR */); + + if (has_wildcard && (flags & EXECUTABLES_ONLY)) + { + /* Don't do wildcard expansion for executables. See #785. Make them expand to nothing here. */ + } + else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) || + has_wildcard) + { + /* We either have a wildcard, or we don't have a wildcard but we're doing completion expansion (so we want to get the completion of a file path) */ + wcstring start, rest; + + if (next[0] == L'/') { - // Don't do wildcard expansion for executables. See #785. So do nothing here. + start = L"/"; + rest = next.substr(1); } - else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) || - has_wildcard) + else { - wcstring start, rest; - - if (next[0] == L'/') - { - start = L"/"; - rest = next.substr(1); - } - else - { - start = L""; - rest = next; - } - - std::vector expanded; - wc_res = wildcard_expand_string(rest, start, flags, &expanded); - if (flags & EXPAND_FOR_COMPLETIONS) - { - outgoing.insert(outgoing.end(), expanded.begin(), expanded.end()); - } - else + start = L""; + rest = next; + } + + std::vector expanded; + int wc_res = wildcard_expand_string(rest, start, flags, &expanded); + if (flags & EXPAND_FOR_COMPLETIONS) + { + out->insert(out->end(), expanded.begin(), expanded.end()); + } + else + { + switch (wc_res) { - switch (wc_res) + case 0: { - case 0: - { - if (res == EXPAND_OK) - res = EXPAND_WILDCARD_NO_MATCH; - break; - } - - case 1: - { - res = EXPAND_WILDCARD_MATCH; - std::sort(expanded.begin(), expanded.end(), completion_t::is_naturally_less_than); - outgoing.insert(outgoing.end(), expanded.begin(), expanded.end()); - break; - } - - case -1: - { - return EXPAND_ERROR; - } + result = EXPAND_WILDCARD_NO_MATCH; + break; + } + case 1: + { + result = EXPAND_WILDCARD_MATCH; + std::sort(expanded.begin(), expanded.end(), completion_t::is_naturally_less_than); + out->insert(out->end(), expanded.begin(), expanded.end()); + break; + } + + case -1: + { + result = EXPAND_ERROR; + break; } } } - else + } + else + { + /* Can't yet justify this check */ + if (!(flags & EXPAND_FOR_COMPLETIONS)) { - if (!(flags & EXPAND_FOR_COMPLETIONS)) + append_completion(out, next); + } + } + return result; +} + +expand_error_t expand_string(const wcstring &input, std::vector *out_completions, expand_flags_t flags, parse_error_list_t *errors) +{ + /* Early out. If we're not completing, and there's no magic in the input, we're done. */ + if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(input)) + { + append_completion(out_completions, input); + return EXPAND_OK; + } + + /* Our expansion stages */ + const expand_stage_t stages[] = + { + expand_stage_cmdsubst, + expand_stage_variables, + expand_stage_brackets, + expand_stage_home_and_pid, + expand_stage_wildcards + }; + + /* Load up our single initial completion */ + std::vector completions, output_storage; + append_completion(&completions, input); + + expand_error_t total_result = EXPAND_OK; + for (size_t stage_idx=0; total_result != EXPAND_ERROR && stage_idx < sizeof stages / sizeof *stages; stage_idx++) + { + for (size_t i=0; total_result != EXPAND_ERROR && i < completions.size(); i++) + { + const wcstring &next = completions.at(i).completion; + expand_error_t this_result = stages[stage_idx](next, &output_storage, flags, errors); + /* If this_result was no match, but total_result is that we have a match, then don't change it */ + if (! (this_result == EXPAND_WILDCARD_NO_MATCH && total_result == EXPAND_WILDCARD_MATCH)) { - append_completion(&outgoing, next); + total_result = this_result; } } + + /* Output becomes our next stage's input */ + completions.swap(output_storage); + output_storage.clear(); } - - // Hack to un-expand tildes (see #647) + + /* Hack to un-expand tildes (see #647) */ if (!(flags & EXPAND_SKIP_HOME_DIRECTORIES)) { - unexpand_tildes(input, &outgoing); + unexpand_tildes(input, &completions); } - - // Return our output - out_completions->insert(out_completions->end(), outgoing.begin(), outgoing.end()); - - return res; + + out_completions->insert(out_completions->end(), completions.begin(), completions.end()); + return total_result; } bool expand_one(wcstring &string, expand_flags_t flags, parse_error_list_t *errors) -- cgit v1.2.3