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.
788 lines
28 KiB
788 lines
28 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_CMDLINE_PARSER_H_
|
|
#define ART_CMDLINE_CMDLINE_PARSER_H_
|
|
|
|
#define CMDLINE_NDEBUG 1 // Do not output any debugging information for parsing.
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "base/indenter.h"
|
|
#include "base/variant_map.h"
|
|
#include "cmdline_parse_result.h"
|
|
#include "cmdline_result.h"
|
|
#include "cmdline_type_parser.h"
|
|
#include "cmdline_types.h"
|
|
#include "detail/cmdline_debug_detail.h"
|
|
#include "detail/cmdline_parse_argument_detail.h"
|
|
#include "detail/cmdline_parser_detail.h"
|
|
#include "token_range.h"
|
|
|
|
namespace art {
|
|
// Build a parser for command line arguments with a small domain specific language.
|
|
// Each parsed type must have a specialized CmdlineType<T> in order to do the string->T parsing.
|
|
// Each argument must also have a VariantMap::Key<T> in order to do the T storage.
|
|
template <typename TVariantMap,
|
|
template <typename TKeyValue> class TVariantMapKey>
|
|
struct CmdlineParser {
|
|
template <typename TArg>
|
|
struct ArgumentBuilder;
|
|
|
|
struct Builder; // Build the parser.
|
|
struct UntypedArgumentBuilder; // Build arguments which weren't yet given a type.
|
|
|
|
private:
|
|
// Forward declare some functions that we need to use before fully-defining structs.
|
|
template <typename TArg>
|
|
static ArgumentBuilder<TArg> CreateArgumentBuilder(Builder& parent);
|
|
static void AppendCompletedArgument(Builder& builder, detail::CmdlineParseArgumentAny* arg);
|
|
|
|
// Allow argument definitions to save their values when they are parsed,
|
|
// without having a dependency on CmdlineParser or any of the builders.
|
|
//
|
|
// A shared pointer to the save destination is saved into the load/save argument callbacks.
|
|
//
|
|
// This also allows the underlying storage (i.e. a variant map) to be released
|
|
// to the user, without having to recreate all of the callbacks.
|
|
struct SaveDestination {
|
|
SaveDestination() : variant_map_(new TVariantMap()) {}
|
|
|
|
// Save value to the variant map.
|
|
template <typename TArg>
|
|
void SaveToMap(const TVariantMapKey<TArg>& key, TArg& value) {
|
|
variant_map_->Set(key, value);
|
|
}
|
|
|
|
// Get the existing value from a map, creating the value if it did not already exist.
|
|
template <typename TArg>
|
|
TArg& GetOrCreateFromMap(const TVariantMapKey<TArg>& key) {
|
|
auto* ptr = variant_map_->Get(key);
|
|
if (ptr == nullptr) {
|
|
variant_map_->Set(key, TArg());
|
|
ptr = variant_map_->Get(key);
|
|
assert(ptr != nullptr);
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
protected:
|
|
// Release the map, clearing it as a side-effect.
|
|
// Future saves will be distinct from previous saves.
|
|
TVariantMap&& ReleaseMap() {
|
|
return std::move(*variant_map_);
|
|
}
|
|
|
|
// Get a read-only reference to the variant map.
|
|
const TVariantMap& GetMap() {
|
|
return *variant_map_;
|
|
}
|
|
|
|
// Clear all potential save targets.
|
|
void Clear() {
|
|
variant_map_->Clear();
|
|
}
|
|
|
|
private:
|
|
// Don't try to copy or move this. Just don't.
|
|
SaveDestination(const SaveDestination&) = delete;
|
|
SaveDestination(SaveDestination&&) = delete;
|
|
SaveDestination& operator=(const SaveDestination&) = delete;
|
|
SaveDestination& operator=(SaveDestination&&) = delete;
|
|
|
|
std::shared_ptr<TVariantMap> variant_map_;
|
|
|
|
// Allow the parser to change the underlying pointers when we release the underlying storage.
|
|
friend struct CmdlineParser;
|
|
};
|
|
|
|
public:
|
|
// Builder for the argument definition of type TArg. Do not use this type directly,
|
|
// it is only a separate type to provide compile-time enforcement against doing
|
|
// illegal builds.
|
|
template <typename TArg>
|
|
struct ArgumentBuilder {
|
|
// Add a range check to this argument.
|
|
ArgumentBuilder<TArg>& WithRange(const TArg& min, const TArg& max) {
|
|
argument_info_.has_range_ = true;
|
|
argument_info_.min_ = min;
|
|
argument_info_.max_ = max;
|
|
|
|
return *this;
|
|
}
|
|
|
|
// Map the list of names into the list of values. List of names must not have
|
|
// any wildcards '_' in it.
|
|
//
|
|
// Do not use if a value map has already been set.
|
|
ArgumentBuilder<TArg>& WithValues(std::initializer_list<TArg> value_list) {
|
|
SetValuesInternal(value_list);
|
|
return *this;
|
|
}
|
|
|
|
// When used with a single alias, map the alias into this value.
|
|
// Same as 'WithValues({value})' , but allows the omission of the curly braces {}.
|
|
ArgumentBuilder<TArg> WithValue(const TArg& value) {
|
|
return WithValues({ value });
|
|
}
|
|
|
|
// Map the parsed string values (from _) onto a concrete value. If no wildcard
|
|
// has been specified, then map the value directly from the arg name (i.e.
|
|
// if there are multiple aliases, then use the alias to do the mapping).
|
|
//
|
|
// Do not use if a values list has already been set.
|
|
ArgumentBuilder<TArg>& WithValueMap(
|
|
std::initializer_list<std::pair<const char*, TArg>> key_value_list) {
|
|
assert(!argument_info_.has_value_list_);
|
|
|
|
argument_info_.has_value_map_ = true;
|
|
argument_info_.value_map_ = key_value_list;
|
|
|
|
return *this;
|
|
}
|
|
|
|
// If this argument is seen multiple times, successive arguments mutate the same value
|
|
// instead of replacing it with a new value.
|
|
ArgumentBuilder<TArg>& AppendValues() {
|
|
argument_info_.appending_values_ = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
ArgumentBuilder<TArg>& WithMetavar(const char* sv) {
|
|
argument_info_.metavar_ = sv;
|
|
return *this;
|
|
}
|
|
|
|
ArgumentBuilder<TArg>& WithHelp(const char* sv) {
|
|
argument_info_.help_ = sv;
|
|
return *this;
|
|
}
|
|
|
|
// Convenience type alias for the variant map key type definition.
|
|
using MapKey = TVariantMapKey<TArg>;
|
|
|
|
// Write the results of this argument into the key.
|
|
// To look up the parsed arguments, get the map and then use this key with VariantMap::Get
|
|
CmdlineParser::Builder& IntoKey(const MapKey& key) {
|
|
// Only capture save destination as a pointer.
|
|
// This allows the parser to later on change the specific save targets.
|
|
auto save_destination = save_destination_;
|
|
save_value_ = [save_destination, &key](TArg& value) {
|
|
save_destination->SaveToMap(key, value);
|
|
CMDLINE_DEBUG_LOG << "Saved value into map '"
|
|
<< detail::ToStringAny(value) << "'" << std::endl;
|
|
};
|
|
|
|
load_value_ = [save_destination, &key]() -> TArg& {
|
|
TArg& value = save_destination->GetOrCreateFromMap(key);
|
|
CMDLINE_DEBUG_LOG << "Loaded value from map '" << detail::ToStringAny(value) << "'"
|
|
<< std::endl;
|
|
|
|
return value;
|
|
};
|
|
|
|
save_value_specified_ = true;
|
|
load_value_specified_ = true;
|
|
|
|
CompleteArgument();
|
|
return parent_;
|
|
}
|
|
|
|
// Write the results of this argument into a variable pointed to by destination.
|
|
// An optional is used to tell whether the command line argument was present.
|
|
CmdlineParser::Builder& IntoLocation(std::optional<TArg>* destination) {
|
|
save_value_ = [destination](TArg& value) {
|
|
*destination = value;
|
|
};
|
|
|
|
load_value_ = [destination]() -> TArg& {
|
|
return destination->value();
|
|
};
|
|
|
|
save_value_specified_ = true;
|
|
load_value_specified_ = true;
|
|
|
|
CompleteArgument();
|
|
return parent_;
|
|
}
|
|
|
|
// Ensure we always move this when returning a new builder.
|
|
ArgumentBuilder(ArgumentBuilder&&) = default;
|
|
|
|
protected:
|
|
// Used by builder to internally ignore arguments by dropping them on the floor after parsing.
|
|
CmdlineParser::Builder& IntoIgnore() {
|
|
save_value_ = [](TArg& value) {
|
|
CMDLINE_DEBUG_LOG << "Ignored value '" << detail::ToStringAny(value) << "'" << std::endl;
|
|
};
|
|
load_value_ = []() -> TArg& {
|
|
assert(false && "Should not be appending values to ignored arguments");
|
|
__builtin_trap(); // Blow up.
|
|
};
|
|
|
|
save_value_specified_ = true;
|
|
load_value_specified_ = true;
|
|
|
|
CompleteArgument();
|
|
return parent_;
|
|
}
|
|
|
|
void SetValuesInternal(const std::vector<TArg>&& value_list) {
|
|
assert(!argument_info_.has_value_map_);
|
|
|
|
argument_info_.has_value_list_ = true;
|
|
argument_info_.value_list_ = value_list;
|
|
}
|
|
|
|
void SetNames(std::vector<const char*>&& names) {
|
|
argument_info_.names_ = names;
|
|
}
|
|
|
|
void SetNames(std::initializer_list<const char*> names) {
|
|
argument_info_.names_ = names;
|
|
}
|
|
|
|
void SetHelp(std::optional<const char*>&& val) {
|
|
argument_info_.help_ = val;
|
|
}
|
|
|
|
void SetCategory(std::optional<const char*>&& val) {
|
|
argument_info_.category_ = val;
|
|
}
|
|
void SetMetavar(std::optional<const char*>&& val) {
|
|
argument_info_.metavar_ = val;
|
|
}
|
|
|
|
private:
|
|
// Copying is bad. Move only.
|
|
ArgumentBuilder(const ArgumentBuilder&) = delete;
|
|
|
|
// Called by any function that doesn't chain back into this builder.
|
|
// Completes the argument builder and save the information into the main builder.
|
|
void CompleteArgument() {
|
|
assert(save_value_specified_ &&
|
|
"No Into... function called, nowhere to save parsed values to");
|
|
assert(load_value_specified_ &&
|
|
"No Into... function called, nowhere to load parsed values from");
|
|
|
|
argument_info_.CompleteArgument();
|
|
|
|
// Appending the completed argument is destructive. The object is no longer
|
|
// usable since all the useful information got moved out of it.
|
|
AppendCompletedArgument(parent_,
|
|
new detail::CmdlineParseArgument<TArg>(
|
|
std::move(argument_info_),
|
|
std::move(save_value_),
|
|
std::move(load_value_)));
|
|
}
|
|
|
|
friend struct CmdlineParser;
|
|
friend struct CmdlineParser::Builder;
|
|
friend struct CmdlineParser::UntypedArgumentBuilder;
|
|
|
|
ArgumentBuilder(CmdlineParser::Builder& parser,
|
|
std::shared_ptr<SaveDestination> save_destination)
|
|
: parent_(parser),
|
|
save_value_specified_(false),
|
|
load_value_specified_(false),
|
|
save_destination_(save_destination) {
|
|
save_value_ = [](TArg&) {
|
|
assert(false && "No save value function defined");
|
|
};
|
|
|
|
load_value_ = []() -> TArg& {
|
|
assert(false && "No load value function defined");
|
|
__builtin_trap(); // Blow up.
|
|
};
|
|
}
|
|
|
|
CmdlineParser::Builder& parent_;
|
|
std::function<void(TArg&)> save_value_;
|
|
std::function<TArg&(void)> load_value_;
|
|
bool save_value_specified_;
|
|
bool load_value_specified_;
|
|
detail::CmdlineParserArgumentInfo<TArg> argument_info_;
|
|
|
|
std::shared_ptr<SaveDestination> save_destination_;
|
|
};
|
|
|
|
struct UntypedArgumentBuilder {
|
|
// Set a type for this argument. The specific subcommand parser is looked up by the type.
|
|
template <typename TArg>
|
|
ArgumentBuilder<TArg> WithType() {
|
|
return CreateTypedBuilder<TArg>();
|
|
}
|
|
|
|
UntypedArgumentBuilder& WithHelp(const char* sv) {
|
|
SetHelp(sv);
|
|
return *this;
|
|
}
|
|
|
|
UntypedArgumentBuilder& WithCategory(const char* sv) {
|
|
SetCategory(sv);
|
|
return *this;
|
|
}
|
|
|
|
UntypedArgumentBuilder& WithMetavar(const char* sv) {
|
|
SetMetavar(sv);
|
|
return *this;
|
|
}
|
|
|
|
// When used with multiple aliases, map the position of the alias to the value position.
|
|
template <typename TArg>
|
|
ArgumentBuilder<TArg> WithValues(std::initializer_list<TArg> values) {
|
|
auto&& a = CreateTypedBuilder<TArg>();
|
|
a.WithValues(values);
|
|
return std::move(a);
|
|
}
|
|
|
|
// When used with a single alias, map the alias into this value.
|
|
// Same as 'WithValues({value})' , but allows the omission of the curly braces {}.
|
|
template <typename TArg>
|
|
ArgumentBuilder<TArg> WithValue(const TArg& value) {
|
|
return WithValues({ value });
|
|
}
|
|
|
|
// Set the current building argument to target this key.
|
|
// When this command line argument is parsed, it can be fetched with this key.
|
|
Builder& IntoKey(const TVariantMapKey<Unit>& key) {
|
|
return CreateTypedBuilder<Unit>().IntoKey(key);
|
|
}
|
|
|
|
// Ensure we always move this when returning a new builder.
|
|
UntypedArgumentBuilder(UntypedArgumentBuilder&&) = default;
|
|
|
|
protected:
|
|
void SetNames(std::vector<const char*>&& names) {
|
|
names_ = std::move(names);
|
|
}
|
|
|
|
void SetNames(std::initializer_list<const char*> names) {
|
|
names_ = names;
|
|
}
|
|
|
|
void SetHelp(std::optional<const char*> sv) {
|
|
help_.swap(sv);
|
|
}
|
|
|
|
void SetMetavar(std::optional<const char*> sv) {
|
|
metavar_.swap(sv);
|
|
}
|
|
|
|
void SetCategory(std::optional<const char*> sv) {
|
|
category_.swap(sv);
|
|
}
|
|
|
|
private:
|
|
// No copying. Move instead.
|
|
UntypedArgumentBuilder(const UntypedArgumentBuilder&) = delete;
|
|
|
|
template <typename TArg>
|
|
ArgumentBuilder<TArg> CreateTypedBuilder() {
|
|
auto&& b = CreateArgumentBuilder<TArg>(parent_);
|
|
InitializeTypedBuilder(&b); // Type-specific initialization
|
|
b.SetNames(std::move(names_));
|
|
b.SetHelp(std::move(help_));
|
|
b.SetCategory(std::move(category_));
|
|
b.SetMetavar(std::move(metavar_));
|
|
return std::move(b);
|
|
}
|
|
|
|
template <typename TArg = Unit>
|
|
typename std::enable_if<std::is_same<TArg, Unit>::value>::type
|
|
InitializeTypedBuilder(ArgumentBuilder<TArg>* arg_builder) {
|
|
// Every Unit argument implicitly maps to a runtime value of Unit{}
|
|
std::vector<Unit> values(names_.size(), Unit{});
|
|
arg_builder->SetValuesInternal(std::move(values));
|
|
}
|
|
|
|
// No extra work for all other types
|
|
void InitializeTypedBuilder(void*) {}
|
|
|
|
template <typename TArg>
|
|
friend struct ArgumentBuilder;
|
|
friend struct Builder;
|
|
|
|
explicit UntypedArgumentBuilder(CmdlineParser::Builder& parent) : parent_(parent) {}
|
|
// UntypedArgumentBuilder(UntypedArgumentBuilder&& other) = default;
|
|
|
|
CmdlineParser::Builder& parent_;
|
|
std::vector<const char*> names_;
|
|
std::optional<const char*> category_;
|
|
std::optional<const char*> help_;
|
|
std::optional<const char*> metavar_;
|
|
};
|
|
|
|
// Build a new parser given a chain of calls to define arguments.
|
|
struct Builder {
|
|
Builder() : save_destination_(new SaveDestination()) {}
|
|
|
|
// Define a single argument. The default type is Unit.
|
|
UntypedArgumentBuilder Define(const char* name) {
|
|
return Define({name});
|
|
}
|
|
|
|
Builder& ClearCategory() {
|
|
default_category_.reset();
|
|
return *this;
|
|
}
|
|
|
|
Builder& SetCategory(const char* sv) {
|
|
default_category_ = sv;
|
|
return *this;
|
|
}
|
|
|
|
Builder& OrderCategories(std::vector<const char*> categories) {
|
|
category_order_.swap(categories);
|
|
return *this;
|
|
}
|
|
|
|
// Define a single argument with multiple aliases.
|
|
UntypedArgumentBuilder Define(std::initializer_list<const char*> names) {
|
|
auto&& b = UntypedArgumentBuilder(*this);
|
|
b.SetNames(names);
|
|
b.SetCategory(default_category_);
|
|
return std::move(b);
|
|
}
|
|
|
|
// Whether the parser should give up on unrecognized arguments. Not recommended.
|
|
Builder& IgnoreUnrecognized(bool ignore_unrecognized) {
|
|
ignore_unrecognized_ = ignore_unrecognized;
|
|
return *this;
|
|
}
|
|
|
|
// Provide a list of arguments to ignore for backwards compatibility.
|
|
Builder& Ignore(std::initializer_list<const char*> ignore_list) {
|
|
auto current_cat = default_category_;
|
|
default_category_ = "Ignored";
|
|
for (auto&& ignore_name : ignore_list) {
|
|
std::string ign = ignore_name;
|
|
|
|
// Ignored arguments are just like a regular definition which have very
|
|
// liberal parsing requirements (no range checks, no value checks).
|
|
// Unlike regular argument definitions, when a value gets parsed into its
|
|
// stronger type, we just throw it away.
|
|
|
|
if (ign.find('_') != std::string::npos) { // Does the arg-def have a wildcard?
|
|
// pretend this is a string, e.g. -Xjitconfig:<anythinggoeshere>
|
|
auto&& builder = Define(ignore_name).template WithType<std::string>().IntoIgnore();
|
|
assert(&builder == this);
|
|
(void)builder; // Ignore pointless unused warning, it's used in the assert.
|
|
} else {
|
|
// pretend this is a unit, e.g. -Xjitblocking
|
|
auto&& builder = Define(ignore_name).template WithType<Unit>().IntoIgnore();
|
|
assert(&builder == this);
|
|
(void)builder; // Ignore pointless unused warning, it's used in the assert.
|
|
}
|
|
}
|
|
ignore_list_ = ignore_list;
|
|
default_category_ = current_cat;
|
|
return *this;
|
|
}
|
|
|
|
// Finish building the parser; performs a check of the validity. Return value is moved, not
|
|
// copied. Do not call this more than once.
|
|
CmdlineParser Build() {
|
|
assert(!built_);
|
|
built_ = true;
|
|
|
|
auto&& p = CmdlineParser(ignore_unrecognized_,
|
|
std::move(ignore_list_),
|
|
save_destination_,
|
|
std::move(completed_arguments_),
|
|
std::move(category_order_));
|
|
|
|
return std::move(p);
|
|
}
|
|
|
|
protected:
|
|
void AppendCompletedArgument(detail::CmdlineParseArgumentAny* arg) {
|
|
auto smart_ptr = std::unique_ptr<detail::CmdlineParseArgumentAny>(arg);
|
|
completed_arguments_.push_back(std::move(smart_ptr));
|
|
}
|
|
|
|
private:
|
|
// No copying now!
|
|
Builder(const Builder& other) = delete;
|
|
|
|
template <typename TArg>
|
|
friend struct ArgumentBuilder;
|
|
friend struct UntypedArgumentBuilder;
|
|
friend struct CmdlineParser;
|
|
|
|
bool built_ = false;
|
|
bool ignore_unrecognized_ = false;
|
|
std::vector<const char*> ignore_list_;
|
|
std::shared_ptr<SaveDestination> save_destination_;
|
|
std::optional<const char*> default_category_;
|
|
std::vector<const char*> category_order_;
|
|
|
|
std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>> completed_arguments_;
|
|
};
|
|
|
|
void DumpHelp(VariableIndentationOutputStream& vios);
|
|
|
|
CmdlineResult Parse(const std::string& argv) {
|
|
std::vector<std::string> tokenized;
|
|
Split(argv, ' ', &tokenized);
|
|
|
|
return Parse(TokenRange(std::move(tokenized)));
|
|
}
|
|
|
|
// Parse the arguments; storing results into the arguments map. Returns success value.
|
|
CmdlineResult Parse(const char* argv) {
|
|
return Parse(std::string(argv));
|
|
}
|
|
|
|
// Parse the arguments; storing the results into the arguments map. Returns success value.
|
|
// Assumes that argv[0] is a valid argument (i.e. not the program name).
|
|
CmdlineResult Parse(const std::vector<const char*>& argv) {
|
|
return Parse(TokenRange(argv.begin(), argv.end()));
|
|
}
|
|
|
|
// Parse the arguments; storing the results into the arguments map. Returns success value.
|
|
// Assumes that argv[0] is a valid argument (i.e. not the program name).
|
|
CmdlineResult Parse(const std::vector<std::string>& argv) {
|
|
return Parse(TokenRange(argv.begin(), argv.end()));
|
|
}
|
|
|
|
// Parse the arguments (directly from an int main(argv,argc)). Returns success value.
|
|
// Assumes that argv[0] is the program name, and ignores it.
|
|
CmdlineResult Parse(const char* argv[], int argc) {
|
|
return Parse(TokenRange(&argv[1], argc - 1)); // ignore argv[0] because it's the program name
|
|
}
|
|
|
|
// Look up the arguments that have been parsed; use the target keys to lookup individual args.
|
|
const TVariantMap& GetArgumentsMap() const {
|
|
return save_destination_->GetMap();
|
|
}
|
|
|
|
// Release the arguments map that has been parsed; useful for move semantics.
|
|
TVariantMap&& ReleaseArgumentsMap() {
|
|
return save_destination_->ReleaseMap();
|
|
}
|
|
|
|
// How many arguments were defined?
|
|
size_t CountDefinedArguments() const {
|
|
return completed_arguments_.size();
|
|
}
|
|
|
|
// Ensure we have a default move constructor.
|
|
CmdlineParser(CmdlineParser&&) = default;
|
|
// Ensure we have a default move assignment operator.
|
|
CmdlineParser& operator=(CmdlineParser&&) = default;
|
|
|
|
private:
|
|
friend struct Builder;
|
|
|
|
// Construct a new parser from the builder. Move all the arguments.
|
|
CmdlineParser(bool ignore_unrecognized,
|
|
std::vector<const char*>&& ignore_list,
|
|
std::shared_ptr<SaveDestination> save_destination,
|
|
std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>>&& completed_arguments,
|
|
std::vector<const char*>&& category_order)
|
|
: ignore_unrecognized_(ignore_unrecognized),
|
|
ignore_list_(std::move(ignore_list)),
|
|
save_destination_(save_destination),
|
|
completed_arguments_(std::move(completed_arguments)),
|
|
category_order_(category_order) {
|
|
assert(save_destination != nullptr);
|
|
}
|
|
|
|
// Parse the arguments; storing results into the arguments map. Returns success value.
|
|
// The parsing will fail on the first non-success parse result and return that error.
|
|
//
|
|
// All previously-parsed arguments are cleared out.
|
|
// Otherwise, all parsed arguments will be stored into SaveDestination as a side-effect.
|
|
// A partial parse will result only in a partial save of the arguments.
|
|
CmdlineResult Parse(TokenRange&& arguments_list) {
|
|
save_destination_->Clear();
|
|
|
|
for (size_t i = 0; i < arguments_list.Size(); ) {
|
|
TokenRange possible_name = arguments_list.Slice(i);
|
|
|
|
size_t best_match_size = 0; // How many tokens were matched in the best case.
|
|
size_t best_match_arg_idx = 0;
|
|
bool matched = false; // At least one argument definition has been matched?
|
|
|
|
// Find the closest argument definition for the remaining token range.
|
|
size_t arg_idx = 0;
|
|
for (auto&& arg : completed_arguments_) {
|
|
size_t local_match = arg->MaybeMatches(possible_name);
|
|
|
|
if (local_match > best_match_size) {
|
|
best_match_size = local_match;
|
|
best_match_arg_idx = arg_idx;
|
|
matched = true;
|
|
}
|
|
arg_idx++;
|
|
}
|
|
|
|
// Saw some kind of unknown argument
|
|
if (matched == false) {
|
|
if (UNLIKELY(ignore_unrecognized_)) { // This is usually off, we only need it for JNI.
|
|
// Consume 1 token and keep going, hopefully the next token is a good one.
|
|
++i;
|
|
continue;
|
|
}
|
|
// Common case:
|
|
// Bail out on the first unknown argument with an error.
|
|
return CmdlineResult(CmdlineResult::kUnknown,
|
|
std::string("Unknown argument: ") + possible_name[0]);
|
|
}
|
|
|
|
// Look at the best-matched argument definition and try to parse against that.
|
|
auto&& arg = completed_arguments_[best_match_arg_idx];
|
|
|
|
assert(arg->MaybeMatches(possible_name) == best_match_size);
|
|
|
|
// Try to parse the argument now, if we have enough tokens.
|
|
std::pair<size_t, size_t> num_tokens = arg->GetNumTokens();
|
|
size_t min_tokens;
|
|
size_t max_tokens;
|
|
|
|
std::tie(min_tokens, max_tokens) = num_tokens;
|
|
|
|
if ((i + min_tokens) > arguments_list.Size()) {
|
|
// expected longer command line but it was too short
|
|
// e.g. if the argv was only "-Xms" without specifying a memory option
|
|
CMDLINE_DEBUG_LOG << "Parse failure, i = " << i << ", arg list " << arguments_list.Size() <<
|
|
" num tokens in arg_def: " << min_tokens << "," << max_tokens << std::endl;
|
|
return CmdlineResult(CmdlineResult::kFailure,
|
|
std::string("Argument ") +
|
|
possible_name[0] + ": incomplete command line arguments, expected "
|
|
+ std::to_string(size_t(i + min_tokens) - arguments_list.Size()) +
|
|
" more tokens");
|
|
}
|
|
|
|
if (best_match_size > max_tokens || best_match_size < min_tokens) {
|
|
// Even our best match was out of range, so parsing would fail instantly.
|
|
return CmdlineResult(CmdlineResult::kFailure,
|
|
std::string("Argument ") + possible_name[0] + ": too few tokens "
|
|
"matched " + std::to_string(best_match_size)
|
|
+ " but wanted " + std::to_string(num_tokens.first));
|
|
}
|
|
|
|
// We have enough tokens to begin exact parsing.
|
|
TokenRange exact_range = possible_name.Slice(0, max_tokens);
|
|
|
|
size_t consumed_tokens = 1; // At least 1 if we ever want to try to resume parsing on error
|
|
CmdlineResult parse_attempt = arg->ParseArgument(exact_range, &consumed_tokens);
|
|
|
|
if (parse_attempt.IsError()) {
|
|
// We may also want to continue parsing the other tokens to gather more errors.
|
|
return parse_attempt;
|
|
} // else the value has been successfully stored into the map
|
|
|
|
assert(consumed_tokens > 0); // Don't hang in an infinite loop trying to parse
|
|
i += consumed_tokens;
|
|
|
|
// TODO: also handle ignoring arguments for backwards compatibility
|
|
} // for
|
|
|
|
return CmdlineResult(CmdlineResult::kSuccess);
|
|
}
|
|
|
|
bool ignore_unrecognized_ = false;
|
|
std::vector<const char*> ignore_list_;
|
|
std::shared_ptr<SaveDestination> save_destination_;
|
|
std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>> completed_arguments_;
|
|
std::vector<const char*> category_order_;
|
|
};
|
|
|
|
// This has to be defined after everything else, since we want the builders to call this.
|
|
template <typename TVariantMap,
|
|
template <typename TKeyValue> class TVariantMapKey>
|
|
template <typename TArg>
|
|
typename CmdlineParser<TVariantMap, TVariantMapKey>::template ArgumentBuilder<TArg>
|
|
CmdlineParser<TVariantMap, TVariantMapKey>::CreateArgumentBuilder(
|
|
CmdlineParser<TVariantMap, TVariantMapKey>::Builder& parent) {
|
|
return CmdlineParser<TVariantMap, TVariantMapKey>::ArgumentBuilder<TArg>(
|
|
parent, parent.save_destination_);
|
|
}
|
|
|
|
// This has to be defined after everything else, since we want the builders to call this.
|
|
template <typename TVariantMap,
|
|
template <typename TKeyValue> class TVariantMapKey>
|
|
void CmdlineParser<TVariantMap, TVariantMapKey>::AppendCompletedArgument(
|
|
CmdlineParser<TVariantMap, TVariantMapKey>::Builder& builder,
|
|
detail::CmdlineParseArgumentAny* arg) {
|
|
builder.AppendCompletedArgument(arg);
|
|
}
|
|
|
|
template <typename TVariantMap,
|
|
template <typename TKeyValue> class TVariantMapKey>
|
|
void CmdlineParser<TVariantMap, TVariantMapKey>::DumpHelp(VariableIndentationOutputStream& vios) {
|
|
std::vector<detail::CmdlineParseArgumentAny*> uncat;
|
|
std::unordered_map<std::string, std::vector<detail::CmdlineParseArgumentAny*>> args;
|
|
for (const std::unique_ptr<detail::CmdlineParseArgumentAny>& it : completed_arguments_) {
|
|
auto cat = it->GetCategory();
|
|
if (cat) {
|
|
if (args.find(cat.value()) == args.end()) {
|
|
args[cat.value()] = {};
|
|
}
|
|
args.at(cat.value()).push_back(it.get());
|
|
} else {
|
|
uncat.push_back(it.get());
|
|
}
|
|
}
|
|
args.erase("Ignored");
|
|
for (auto arg : uncat) {
|
|
arg->DumpHelp(vios);
|
|
vios.Stream();
|
|
}
|
|
for (auto it : category_order_) {
|
|
auto cur = args.find(it);
|
|
if (cur != args.end() && !cur->second.empty()) {
|
|
vios.Stream() << "The following " << it << " arguments are supported:" << std::endl;
|
|
ScopedIndentation si(&vios);
|
|
for (detail::CmdlineParseArgumentAny* arg : cur->second) {
|
|
arg->DumpHelp(vios);
|
|
vios.Stream();
|
|
}
|
|
args.erase(cur->first);
|
|
}
|
|
}
|
|
for (auto [cat, lst] : args) {
|
|
vios.Stream() << "The following " << cat << " arguments are supported:" << std::endl;
|
|
ScopedIndentation si(&vios);
|
|
for (auto& arg : completed_arguments_) {
|
|
arg->DumpHelp(vios);
|
|
vios.Stream();
|
|
}
|
|
}
|
|
if (!ignore_list_.empty()) {
|
|
vios.Stream() << "The following arguments are ignored for compatibility:" << std::endl;
|
|
ScopedIndentation si(&vios);
|
|
for (auto ign : ignore_list_) {
|
|
vios.Stream() << ign << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace art
|
|
|
|
#endif // ART_CMDLINE_CMDLINE_PARSER_H_
|