diff options
Diffstat (limited to 'cmdline/cmdline_parser.h')
| -rw-r--r-- | cmdline/cmdline_parser.h | 635 | 
1 files changed, 635 insertions, 0 deletions
diff --git a/cmdline/cmdline_parser.h b/cmdline/cmdline_parser.h new file mode 100644 index 0000000000..a555356396 --- /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_  |