art: Refactor RuntimeOptions/ParsedOptions
Refactor the RuntimeOptions to be a
type-safe map (VariantMap, see runtime_options.h) and the ParsedOptions
to delegate the parsing to CmdlineParser (see cmdline/cmdline_parser.h).
This is the start of a command line parsing refactor, and may include
more in the future (dex2oat, patchoat, etc).
For more details of the command line parsing generator usage see cmdline/README.md
Change-Id: Ic67c6bca5e1f33bf2ec60e2e3ff8c366bab91563
diff --git a/cmdline/cmdline_parser.h b/cmdline/cmdline_parser.h
new file mode 100644
index 0000000..a555356
--- /dev/null
+++ b/cmdline/cmdline_parser.h
@@ -0,0 +1,635 @@
+/*
+ * 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 "cmdline/detail/cmdline_parser_detail.h"
+#include "cmdline/detail/cmdline_parse_argument_detail.h"
+#include "cmdline/detail/cmdline_debug_detail.h"
+
+#include "cmdline_type_parser.h"
+#include "token_range.h"
+#include "cmdline_types.h"
+#include "cmdline_result.h"
+#include "cmdline_parse_result.h"
+
+#include "runtime/base/variant_map.h"
+#include "utils.h"
+
+#include <vector>
+#include <memory>
+
+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;
+ }
+
+ // 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_;
+ }
+
+ // 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");
+ return *reinterpret_cast<TArg*>(0); // 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;
+ }
+
+ 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");
+ return *reinterpret_cast<TArg*>(0); // 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>();
+ }
+
+ // 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;
+ }
+
+ 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_));
+ 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{}); // NOLINT [whitespace/braces] [5]
+ 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_;
+ };
+
+ // 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});
+ }
+
+ // Define a single argument with multiple aliases.
+ UntypedArgumentBuilder Define(std::initializer_list<const char*> names) {
+ auto&& b = UntypedArgumentBuilder(*this);
+ b.SetNames(names);
+ 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) {
+ 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;
+ return *this;
+ }
+
+ // Finish building the parser; performs sanity checks. 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_));
+
+ 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::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>> completed_arguments_;
+ };
+
+ 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&& other) = default;
+ // Ensure we have a default move assignment operator.
+ CmdlineParser& operator=(CmdlineParser&& other) = default;
+
+ private:
+ friend struct Builder;
+
+ // Construct a new parser from the builder. Move all the arguments.
+ explicit 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)
+ : ignore_unrecognized_(ignore_unrecognized),
+ ignore_list_(std::move(ignore_list)),
+ save_destination_(save_destination),
+ completed_arguments_(std::move(completed_arguments)) {
+ 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_;
+};
+
+// 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>
+CmdlineParser<TVariantMap, TVariantMapKey>::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);
+}
+
+} // namespace art
+
+#endif // ART_CMDLINE_CMDLINE_PARSER_H_