You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
20 KiB
570 lines
20 KiB
/*
|
|
* 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;
|
|
auto& os = vios.Stream();
|
|
std::function<void()> print_once;
|
|
if (using_blanks_) {
|
|
std::string_view nblank = name.substr(0, name.find("_"));
|
|
print_once = [&]() {
|
|
os << nblank;
|
|
if (has_value_map_) {
|
|
bool first = true;
|
|
for (auto [val, unused] : value_map_) {
|
|
os << (first ? "{" : "|") << val;
|
|
first = false;
|
|
}
|
|
os << "}";
|
|
} else if (metavar_) {
|
|
os << metavar_.value();
|
|
} else {
|
|
os << "{" << CmdlineType<T>::DescribeType() << "}";
|
|
}
|
|
};
|
|
} else {
|
|
print_once = [&]() {
|
|
os << name;
|
|
};
|
|
}
|
|
print_once();
|
|
if (appending_values_) {
|
|
os << " [";
|
|
print_once();
|
|
os << "...]";
|
|
}
|
|
os << std::endl;
|
|
}
|
|
if (help_) {
|
|
ScopedIndentation si(&vios);
|
|
vios.Stream() << help_.value() << 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&&) = 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;
|
|
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_
|