| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ |
| #define ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ |
| |
| #include <assert.h> |
| #include <algorithm> |
| #include <functional> |
| #include <memory> |
| #include <numeric> |
| #include <string_view> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "android-base/strings.h" |
| |
| #include "base/indenter.h" |
| #include "cmdline_parse_result.h" |
| #include "cmdline_types.h" |
| #include "token_range.h" |
| #include "unit.h" |
| |
| namespace art { |
| // Implementation details for the parser. Do not look inside if you hate templates. |
| namespace detail { |
| |
| // A non-templated base class for argument parsers. Used by the general parser |
| // to parse arguments, without needing to know the argument type at compile time. |
| // |
| // This is an application of the type erasure idiom. |
| struct CmdlineParseArgumentAny { |
| virtual ~CmdlineParseArgumentAny() {} |
| |
| // Attempt to parse this argument starting at arguments[position]. |
| // If the parsing succeeds, the parsed value will be saved as a side-effect. |
| // |
| // In most situations, the parsing will not match by returning kUnknown. In this case, |
| // no tokens were consumed and the position variable will not be updated. |
| // |
| // At other times, parsing may fail due to validation but the initial token was still matched |
| // (for example an out of range value, or passing in a string where an int was expected). |
| // In this case the tokens are still consumed, and the position variable will get incremented |
| // by all the consumed tokens. |
| // |
| // The # of tokens consumed by the parse attempt will be set as an out-parameter into |
| // consumed_tokens. The parser should skip this many tokens before parsing the next |
| // argument. |
| virtual CmdlineResult ParseArgument(const TokenRange& arguments, size_t* consumed_tokens) = 0; |
| // How many tokens should be taken off argv for parsing this argument. |
| // For example "--help" is just 1, "-compiler-option _" would be 2 (since there's a space). |
| // |
| // A [min,max] range is returned to represent argument definitions with multiple |
| // value tokens. (e.g. {"-h", "-h " } would return [1,2]). |
| virtual std::pair<size_t, size_t> GetNumTokens() const = 0; |
| // Get the run-time typename of the argument type. |
| virtual const char* GetTypeName() const = 0; |
| // Try to do a close match, returning how many tokens were matched against this argument |
| // definition. More tokens is better. |
| // |
| // Do a quick match token-by-token, and see if they match. |
| // Any tokens with a wildcard in them are only matched up until the wildcard. |
| // If this is true, then the wildcard matching later on can still fail, so this is not |
| // a guarantee that the argument is correct, it's more of a strong hint that the |
| // user-provided input *probably* was trying to match this argument. |
| // |
| // Returns how many tokens were either matched (or ignored because there was a |
| // wildcard present). 0 means no match. If the Size() tokens are returned. |
| virtual size_t MaybeMatches(const TokenRange& tokens) = 0; |
| |
| virtual void DumpHelp(VariableIndentationOutputStream& os) = 0; |
| |
| virtual const std::optional<const char*>& GetCategory() = 0; |
| }; |
| |
| template <typename T> |
| using EnableIfNumeric = std::enable_if<std::is_arithmetic<T>::value>; |
| |
| template <typename T> |
| using DisableIfNumeric = std::enable_if<!std::is_arithmetic<T>::value>; |
| |
| // Argument definition information, created by an ArgumentBuilder and an UntypedArgumentBuilder. |
| template <typename TArg> |
| struct CmdlineParserArgumentInfo { |
| // This version will only be used if TArg is arithmetic and thus has the <= operators. |
| template <typename T = TArg> // Necessary to get SFINAE to kick in. |
| bool CheckRange(const TArg& value, typename EnableIfNumeric<T>::type* = nullptr) { |
| if (has_range_) { |
| return min_ <= value && value <= max_; |
| } |
| return true; |
| } |
| |
| // This version will be used at other times when TArg is not arithmetic. |
| template <typename T = TArg> |
| bool CheckRange(const TArg&, typename DisableIfNumeric<T>::type* = nullptr) { |
| assert(!has_range_); |
| return true; |
| } |
| |
| // Do a quick match token-by-token, and see if they match. |
| // Any tokens with a wildcard in them only match the prefix up until the wildcard. |
| // |
| // If this is true, then the wildcard matching later on can still fail, so this is not |
| // a guarantee that the argument is correct, it's more of a strong hint that the |
| // user-provided input *probably* was trying to match this argument. |
| size_t MaybeMatches(const TokenRange& token_list) const { |
| auto best_match = FindClosestMatch(token_list); |
| |
| return best_match.second; |
| } |
| |
| // Attempt to find the closest match (see MaybeMatches). |
| // |
| // Returns the token range that was the closest match and the # of tokens that |
| // this range was matched up until. |
| std::pair<const TokenRange*, size_t> FindClosestMatch(const TokenRange& token_list) const { |
| const TokenRange* best_match_ptr = nullptr; |
| |
| size_t best_match = 0; |
| for (auto&& token_range : tokenized_names_) { |
| size_t this_match = token_range.MaybeMatches(token_list, std::string("_")); |
| |
| if (this_match > best_match) { |
| best_match_ptr = &token_range; |
| best_match = this_match; |
| } |
| } |
| |
| return std::make_pair(best_match_ptr, best_match); |
| } |
| |
| template <typename T = TArg> // Necessary to get SFINAE to kick in. |
| void DumpHelp(VariableIndentationOutputStream& vios) { |
| // Separate arguments |
| vios.Stream() << std::endl; |
| for (auto cname : names_) { |
| std::string_view name = cname; |
| if (using_blanks_) { |
| name = name.substr(0, name.find('_')); |
| } |
| auto& os = vios.Stream(); |
| auto print_once = [&]() { |
| os << name; |
| if (using_blanks_) { |
| if (has_value_map_) { |
| bool first = true; |
| for (auto [val, unused] : value_map_) { |
| os << (first ? "{" : "|") << val; |
| first = false; |
| } |
| os << "}"; |
| } else if (metavar_.has_value()) { |
| os << *metavar_; |
| } else { |
| os << "{" << CmdlineType<T>::DescribeType() << "}"; |
| } |
| } |
| }; |
| print_once(); |
| if (appending_values_) { |
| os << " ["; |
| print_once(); |
| os << "...]"; |
| } |
| os << std::endl; |
| } |
| if (help_.has_value()) { |
| ScopedIndentation si(&vios); |
| vios.Stream() << *help_ << std::endl; |
| } |
| } |
| |
| |
| // Mark the argument definition as completed, do not mutate the object anymore after this |
| // call is done. |
| // |
| // Performs several checks of the validity and token calculations. |
| void CompleteArgument() { |
| assert(names_.size() >= 1); |
| assert(!is_completed_); |
| |
| is_completed_ = true; |
| |
| size_t blank_count = 0; |
| size_t token_count = 0; |
| |
| size_t global_blank_count = 0; |
| size_t global_token_count = 0; |
| for (auto&& name : names_) { |
| std::string s(name); |
| |
| size_t local_blank_count = std::count(s.begin(), s.end(), '_'); |
| size_t local_token_count = std::count(s.begin(), s.end(), ' '); |
| |
| if (global_blank_count != 0) { |
| assert(local_blank_count == global_blank_count |
| && "Every argument descriptor string must have same amount of blanks (_)"); |
| } |
| |
| if (local_blank_count != 0) { |
| global_blank_count = local_blank_count; |
| blank_count++; |
| |
| assert(local_blank_count == 1 && "More than one blank is not supported"); |
| assert(s.back() == '_' && "The blank character must only be at the end of the string"); |
| } |
| |
| if (global_token_count != 0) { |
| assert(local_token_count == global_token_count |
| && "Every argument descriptor string must have same amount of tokens (spaces)"); |
| } |
| |
| if (local_token_count != 0) { |
| global_token_count = local_token_count; |
| token_count++; |
| } |
| |
| // Tokenize every name, turning it from a string to a token list. |
| tokenized_names_.clear(); |
| for (auto&& name1 : names_) { |
| // Split along ' ' only, removing any duplicated spaces. |
| tokenized_names_.push_back( |
| TokenRange::Split(name1, {' '}).RemoveToken(" ")); |
| } |
| |
| // remove the _ character from each of the token ranges |
| // we will often end up with an empty token (i.e. ["-XX", "_"] -> ["-XX", ""] |
| // and this is OK because we still need an empty token to simplify |
| // range comparisons |
| simple_names_.clear(); |
| |
| for (auto&& tokenized_name : tokenized_names_) { |
| simple_names_.push_back(tokenized_name.RemoveCharacter('_')); |
| } |
| } |
| |
| if (token_count != 0) { |
| assert(("Every argument descriptor string must have equal amount of tokens (spaces)" && |
| token_count == names_.size())); |
| } |
| |
| if (blank_count != 0) { |
| assert(("Every argument descriptor string must have an equal amount of blanks (_)" && |
| blank_count == names_.size())); |
| } |
| |
| using_blanks_ = blank_count > 0; |
| { |
| size_t smallest_name_token_range_size = |
| std::accumulate(tokenized_names_.begin(), tokenized_names_.end(), ~(0u), |
| [](size_t min, const TokenRange& cur) { |
| return std::min(min, cur.Size()); |
| }); |
| size_t largest_name_token_range_size = |
| std::accumulate(tokenized_names_.begin(), tokenized_names_.end(), 0u, |
| [](size_t max, const TokenRange& cur) { |
| return std::max(max, cur.Size()); |
| }); |
| |
| token_range_size_ = std::make_pair(smallest_name_token_range_size, |
| largest_name_token_range_size); |
| } |
| |
| if (has_value_list_) { |
| assert(names_.size() == value_list_.size() |
| && "Number of arg descriptors must match number of values"); |
| assert(!has_value_map_); |
| } |
| if (has_value_map_) { |
| if (!using_blanks_) { |
| assert(names_.size() == value_map_.size() && |
| "Since no blanks were specified, each arg is mapped directly into a mapped " |
| "value without parsing; sizes must match"); |
| } |
| |
| assert(!has_value_list_); |
| } |
| |
| if (!using_blanks_ && !CmdlineType<TArg>::kCanParseBlankless) { |
| assert((has_value_map_ || has_value_list_) && |
| "Arguments without a blank (_) must provide either a value map or a value list"); |
| } |
| |
| TypedCheck(); |
| } |
| |
| // List of aliases for a single argument definition, e.g. {"-Xdex2oat", "-Xnodex2oat"}. |
| std::vector<const char*> names_; |
| // Is there at least 1 wildcard '_' in the argument definition? |
| bool using_blanks_ = false; |
| // [min, max] token counts in each arg def |
| std::pair<size_t, size_t> token_range_size_; |
| |
| // contains all the names in a tokenized form, i.e. as a space-delimited list |
| std::vector<TokenRange> tokenized_names_; |
| |
| // contains the tokenized names, but with the _ character stripped |
| std::vector<TokenRange> simple_names_; |
| |
| // For argument definitions created with '.AppendValues()' |
| // Meaning that parsing should mutate the existing value in-place if possible. |
| bool appending_values_ = false; |
| |
| // For argument definitions created with '.WithRange(min, max)' |
| bool has_range_ = false; |
| TArg min_; |
| TArg max_; |
| |
| // For argument definitions created with '.WithValueMap' |
| bool has_value_map_ = false; |
| std::vector<std::pair<const char*, TArg>> value_map_; |
| |
| // For argument definitions created with '.WithValues' |
| bool has_value_list_ = false; |
| std::vector<TArg> value_list_; |
| |
| std::optional<const char*> help_; |
| std::optional<const char*> category_; |
| std::optional<const char*> metavar_; |
| |
| // Make sure there's a default constructor. |
| CmdlineParserArgumentInfo() = default; |
| |
| // Ensure there's a default move constructor. |
| CmdlineParserArgumentInfo(CmdlineParserArgumentInfo&&) noexcept = default; |
| |
| private: |
| // Perform type-specific checks at runtime. |
| template <typename T = TArg> |
| void TypedCheck(typename std::enable_if<std::is_same<Unit, T>::value>::type* = 0) { |
| assert(!using_blanks_ && |
| "Blanks are not supported in Unit arguments; since a Unit has no parse-able value"); |
| } |
| |
| void TypedCheck() {} |
| |
| bool is_completed_ = false; |
| }; |
| |
| // A virtual-implementation of the necessary argument information in order to |
| // be able to parse arguments. |
| template <typename TArg> |
| struct CmdlineParseArgument : CmdlineParseArgumentAny { |
| CmdlineParseArgument(CmdlineParserArgumentInfo<TArg>&& argument_info, |
| std::function<void(TArg&)>&& save_argument, |
| std::function<TArg&(void)>&& load_argument) |
| : argument_info_(std::forward<decltype(argument_info)>(argument_info)), |
| save_argument_(std::forward<decltype(save_argument)>(save_argument)), |
| load_argument_(std::forward<decltype(load_argument)>(load_argument)) { |
| } |
| |
| using UserTypeInfo = CmdlineType<TArg>; |
| |
| virtual CmdlineResult ParseArgument(const TokenRange& arguments, size_t* consumed_tokens) { |
| assert(arguments.Size() > 0); |
| assert(consumed_tokens != nullptr); |
| |
| auto closest_match_res = argument_info_.FindClosestMatch(arguments); |
| size_t best_match_size = closest_match_res.second; |
| const TokenRange* best_match_arg_def = closest_match_res.first; |
| |
| if (best_match_size > arguments.Size()) { |
| // The best match has more tokens than were provided. |
| // Shouldn't happen in practice since the outer parser does this check. |
| return CmdlineResult(CmdlineResult::kUnknown, "Size mismatch"); |
| } |
| |
| assert(best_match_arg_def != nullptr); |
| *consumed_tokens = best_match_arg_def->Size(); |
| |
| if (!argument_info_.using_blanks_) { |
| return ParseArgumentSingle(arguments.Join(' ')); |
| } |
| |
| // Extract out the blank value from arguments |
| // e.g. for a def of "foo:_" and input "foo:bar", blank_value == "bar" |
| std::string blank_value = ""; |
| size_t idx = 0; |
| for (auto&& def_token : *best_match_arg_def) { |
| auto&& arg_token = arguments[idx]; |
| |
| // Does this definition-token have a wildcard in it? |
| if (def_token.find('_') == std::string::npos) { |
| // No, regular token. Match 1:1 against the argument token. |
| bool token_match = def_token == arg_token; |
| |
| if (!token_match) { |
| return CmdlineResult(CmdlineResult::kFailure, |
| std::string("Failed to parse ") + best_match_arg_def->GetToken(0) |
| + " at token " + std::to_string(idx)); |
| } |
| } else { |
| // This is a wild-carded token. |
| TokenRange def_split_wildcards = TokenRange::Split(def_token, {'_'}); |
| |
| // Extract the wildcard contents out of the user-provided arg_token. |
| std::unique_ptr<TokenRange> arg_matches = |
| def_split_wildcards.MatchSubstrings(arg_token, "_"); |
| if (arg_matches == nullptr) { |
| return CmdlineResult(CmdlineResult::kFailure, |
| std::string("Failed to parse ") + best_match_arg_def->GetToken(0) |
| + ", with a wildcard pattern " + def_token |
| + " at token " + std::to_string(idx)); |
| } |
| |
| // Get the corresponding wildcard tokens from arg_matches, |
| // and concatenate it to blank_value. |
| for (size_t sub_idx = 0; |
| sub_idx < def_split_wildcards.Size() && sub_idx < arg_matches->Size(); ++sub_idx) { |
| if (def_split_wildcards[sub_idx] == "_") { |
| blank_value += arg_matches->GetToken(sub_idx); |
| } |
| } |
| } |
| |
| ++idx; |
| } |
| |
| return ParseArgumentSingle(blank_value); |
| } |
| |
| virtual void DumpHelp(VariableIndentationOutputStream& os) { |
| argument_info_.DumpHelp(os); |
| } |
| |
| virtual const std::optional<const char*>& GetCategory() { |
| return argument_info_.category_; |
| } |
| |
| private: |
| virtual CmdlineResult ParseArgumentSingle(const std::string& argument) { |
| // TODO: refactor to use LookupValue for the value lists/maps |
| |
| // Handle the 'WithValueMap(...)' argument definition |
| if (argument_info_.has_value_map_) { |
| for (auto&& value_pair : argument_info_.value_map_) { |
| const char* name = value_pair.first; |
| |
| if (argument == name) { |
| return SaveArgument(value_pair.second); |
| } |
| } |
| |
| // Error case: Fail, telling the user what the allowed values were. |
| std::vector<std::string> allowed_values; |
| for (auto&& value_pair : argument_info_.value_map_) { |
| const char* name = value_pair.first; |
| allowed_values.push_back(name); |
| } |
| |
| std::string allowed_values_flat = android::base::Join(allowed_values, ','); |
| return CmdlineResult(CmdlineResult::kFailure, |
| "Argument value '" + argument + "' does not match any of known valid " |
| "values: {" + allowed_values_flat + "}"); |
| } |
| |
| // Handle the 'WithValues(...)' argument definition |
| if (argument_info_.has_value_list_) { |
| size_t arg_def_idx = 0; |
| for (auto&& value : argument_info_.value_list_) { |
| auto&& arg_def_token = argument_info_.names_[arg_def_idx]; |
| |
| if (arg_def_token == argument) { |
| return SaveArgument(value); |
| } |
| ++arg_def_idx; |
| } |
| |
| assert(arg_def_idx + 1 == argument_info_.value_list_.size() && |
| "Number of named argument definitions must match number of values defined"); |
| |
| // Error case: Fail, telling the user what the allowed values were. |
| std::vector<std::string> allowed_values; |
| allowed_values.reserve(argument_info_.names_.size()); |
| for (auto&& arg_name : argument_info_.names_) { |
| allowed_values.push_back(arg_name); |
| } |
| |
| std::string allowed_values_flat = android::base::Join(allowed_values, ','); |
| return CmdlineResult(CmdlineResult::kFailure, |
| "Argument value '" + argument + "' does not match any of known valid" |
| "values: {" + allowed_values_flat + "}"); |
| } |
| |
| // Handle the regular case where we parsed an unknown value from a blank. |
| UserTypeInfo type_parser; |
| |
| if (argument_info_.appending_values_) { |
| TArg& existing = load_argument_(); |
| CmdlineParseResult<TArg> result = type_parser.ParseAndAppend(argument, existing); |
| |
| assert(!argument_info_.has_range_); |
| |
| return std::move(result); |
| } |
| |
| CmdlineParseResult<TArg> result = type_parser.Parse(argument); |
| |
| if (result.IsSuccess()) { |
| TArg& value = result.GetValue(); |
| |
| // Do a range check for 'WithRange(min,max)' argument definition. |
| if (!argument_info_.CheckRange(value)) { |
| return CmdlineParseResult<TArg>::OutOfRange( |
| value, argument_info_.min_, argument_info_.max_); |
| } |
| |
| return SaveArgument(value); |
| } |
| |
| // Some kind of type-specific parse error. Pass the result as-is. |
| CmdlineResult raw_result = std::move(result); |
| return raw_result; |
| } |
| |
| public: |
| virtual const char* GetTypeName() const { |
| // TODO: Obviate the need for each type specialization to hardcode the type name |
| return UserTypeInfo::Name(); |
| } |
| |
| // How many tokens should be taken off argv for parsing this argument. |
| // For example "--help" is just 1, "-compiler-option _" would be 2 (since there's a space). |
| // |
| // A [min,max] range is returned to represent argument definitions with multiple |
| // value tokens. (e.g. {"-h", "-h " } would return [1,2]). |
| virtual std::pair<size_t, size_t> GetNumTokens() const { |
| return argument_info_.token_range_size_; |
| } |
| |
| // See if this token range might begin the same as the argument definition. |
| virtual size_t MaybeMatches(const TokenRange& tokens) { |
| return argument_info_.MaybeMatches(tokens); |
| } |
| |
| private: |
| CmdlineResult SaveArgument(const TArg& value) { |
| assert(!argument_info_.appending_values_ |
| && "If the values are being appended, then the updated parse value is " |
| "updated by-ref as a side effect and shouldn't be stored directly"); |
| TArg val = value; |
| save_argument_(val); |
| return CmdlineResult(CmdlineResult::kSuccess); |
| } |
| |
| CmdlineParserArgumentInfo<TArg> argument_info_; |
| std::function<void(TArg&)> save_argument_; |
| std::function<TArg&(void)> load_argument_; |
| }; |
| } // namespace detail // NOLINT [readability/namespace] [5] |
| } // namespace art |
| |
| #endif // ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ |