| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #pragma once |
| |
| #include "cmConfigure.h" // IWYU pragma: keep |
| |
| #include <cassert> |
| #include <cstddef> |
| #include <functional> |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <cm/optional> |
| #include <cm/string_view> |
| #include <cm/type_traits> |
| #include <cmext/string_view> |
| |
| #include "cmArgumentParserTypes.h" // IWYU pragma: keep |
| |
| template <typename Result> |
| class cmArgumentParser; // IWYU pragma: keep |
| |
| class cmMakefile; |
| |
| namespace ArgumentParser { |
| |
| class ParseResult |
| { |
| std::map<cm::string_view, std::string> KeywordErrors; |
| |
| public: |
| explicit operator bool() const { return this->KeywordErrors.empty(); } |
| |
| void AddKeywordError(cm::string_view key, cm::string_view text) |
| |
| { |
| this->KeywordErrors[key] += text; |
| } |
| |
| std::map<cm::string_view, std::string> const& GetKeywordErrors() const |
| { |
| return this->KeywordErrors; |
| } |
| |
| bool MaybeReportError(cmMakefile& mf) const; |
| }; |
| |
| template <typename Result> |
| typename std::enable_if<std::is_base_of<ParseResult, Result>::value, |
| ParseResult*>::type |
| AsParseResultPtr(Result& result) |
| { |
| return &result; |
| } |
| |
| template <typename Result> |
| typename std::enable_if<!std::is_base_of<ParseResult, Result>::value, |
| ParseResult*>::type |
| AsParseResultPtr(Result&) |
| { |
| return nullptr; |
| } |
| |
| enum class Continue |
| { |
| No, |
| Yes, |
| }; |
| |
| struct ExpectAtLeast |
| { |
| std::size_t Count = 0; |
| |
| ExpectAtLeast(std::size_t count) |
| : Count(count) |
| { |
| } |
| }; |
| |
| class Instance; |
| using KeywordAction = std::function<void(Instance&)>; |
| using KeywordNameAction = std::function<void(Instance&, cm::string_view)>; |
| using PositionAction = |
| std::function<void(Instance&, std::size_t, cm::string_view)>; |
| |
| // using KeywordActionMap = cm::flat_map<cm::string_view, KeywordAction>; |
| class KeywordActionMap |
| : public std::vector<std::pair<cm::string_view, KeywordAction>> |
| { |
| public: |
| std::pair<iterator, bool> Emplace(cm::string_view name, |
| KeywordAction action); |
| const_iterator Find(cm::string_view name) const; |
| }; |
| |
| // using PositionActionMap = cm::flat_map<cm::string_view, PositionAction>; |
| class PositionActionMap |
| : public std::vector<std::pair<std::size_t, PositionAction>> |
| { |
| public: |
| std::pair<iterator, bool> Emplace(std::size_t pos, PositionAction action); |
| const_iterator Find(std::size_t pos) const; |
| }; |
| |
| class ActionMap |
| { |
| public: |
| KeywordActionMap Keywords; |
| KeywordNameAction KeywordMissingValue; |
| KeywordNameAction ParsedKeyword; |
| PositionActionMap Positions; |
| }; |
| |
| class Base |
| { |
| public: |
| using ExpectAtLeast = ArgumentParser::ExpectAtLeast; |
| using Continue = ArgumentParser::Continue; |
| using Instance = ArgumentParser::Instance; |
| using ParseResult = ArgumentParser::ParseResult; |
| |
| ArgumentParser::ActionMap Bindings; |
| |
| bool MaybeBind(cm::string_view name, KeywordAction action) |
| { |
| return this->Bindings.Keywords.Emplace(name, std::move(action)).second; |
| } |
| |
| void Bind(cm::string_view name, KeywordAction action) |
| { |
| bool const inserted = this->MaybeBind(name, std::move(action)); |
| assert(inserted); |
| static_cast<void>(inserted); |
| } |
| |
| void BindParsedKeyword(KeywordNameAction action) |
| { |
| assert(!this->Bindings.ParsedKeyword); |
| this->Bindings.ParsedKeyword = std::move(action); |
| } |
| |
| void BindKeywordMissingValue(KeywordNameAction action) |
| { |
| assert(!this->Bindings.KeywordMissingValue); |
| this->Bindings.KeywordMissingValue = std::move(action); |
| } |
| |
| void Bind(std::size_t pos, PositionAction action) |
| { |
| bool const inserted = |
| this->Bindings.Positions.Emplace(pos, std::move(action)).second; |
| assert(inserted); |
| static_cast<void>(inserted); |
| } |
| }; |
| |
| class Instance |
| { |
| public: |
| Instance(ActionMap const& bindings, ParseResult* parseResult, |
| std::vector<std::string>* unparsedArguments, void* result = nullptr) |
| : Bindings(bindings) |
| , ParseResults(parseResult) |
| , UnparsedArguments(unparsedArguments) |
| , Result(result) |
| { |
| } |
| |
| void Bind(std::function<Continue(cm::string_view)> f, ExpectAtLeast expect); |
| void Bind(bool& val); |
| void Bind(std::string& val); |
| void Bind(NonEmpty<std::string>& val); |
| void Bind(Maybe<std::string>& val); |
| void Bind(MaybeEmpty<std::vector<std::string>>& val); |
| void Bind(NonEmpty<std::vector<std::string>>& val); |
| void Bind(std::vector<std::vector<std::string>>& val); |
| |
| // cm::optional<> records the presence the keyword to which it binds. |
| template <typename T> |
| void Bind(cm::optional<T>& optVal) |
| { |
| if (!optVal) { |
| optVal.emplace(); |
| } |
| this->Bind(*optVal); |
| } |
| |
| template <typename Range> |
| void Parse(Range const& args, std::size_t pos = 0) |
| { |
| for (cm::string_view arg : args) { |
| this->Consume(pos++, arg); |
| } |
| this->FinishKeyword(); |
| } |
| |
| private: |
| ActionMap const& Bindings; |
| ParseResult* ParseResults = nullptr; |
| std::vector<std::string>* UnparsedArguments = nullptr; |
| void* Result = nullptr; |
| |
| cm::string_view Keyword; |
| std::size_t KeywordValuesSeen = 0; |
| std::size_t KeywordValuesExpected = 0; |
| std::function<Continue(cm::string_view)> KeywordValueFunc; |
| |
| void Consume(std::size_t pos, cm::string_view arg); |
| void FinishKeyword(); |
| |
| template <typename Result> |
| friend class ::cmArgumentParser; |
| }; |
| |
| } // namespace ArgumentParser |
| |
| template <typename Result> |
| class cmArgumentParser : private ArgumentParser::Base |
| { |
| public: |
| // I *think* this function could be made `constexpr` when the code is |
| // compiled as C++20. This would allow building a parser at compile time. |
| template <typename T> |
| cmArgumentParser& Bind(cm::static_string_view name, T Result::*member) |
| { |
| this->Base::Bind(name, [member](Instance& instance) { |
| instance.Bind(static_cast<Result*>(instance.Result)->*member); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind(cm::static_string_view name, |
| Continue (Result::*member)(cm::string_view), |
| ExpectAtLeast expect = { 1 }) |
| { |
| this->Base::Bind(name, [member, expect](Instance& instance) { |
| Result* result = static_cast<Result*>(instance.Result); |
| instance.Bind( |
| [result, member](cm::string_view arg) -> Continue { |
| return (result->*member)(arg); |
| }, |
| expect); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind(cm::static_string_view name, |
| Continue (Result::*member)(cm::string_view, |
| cm::string_view), |
| ExpectAtLeast expect = { 1 }) |
| { |
| this->Base::Bind(name, [member, expect](Instance& instance) { |
| Result* result = static_cast<Result*>(instance.Result); |
| cm::string_view keyword = instance.Keyword; |
| instance.Bind( |
| [result, member, keyword](cm::string_view arg) -> Continue { |
| return (result->*member)(keyword, arg); |
| }, |
| expect); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind(cm::static_string_view name, |
| std::function<Continue(Result&, cm::string_view)> f, |
| ExpectAtLeast expect = { 1 }) |
| { |
| this->Base::Bind(name, [f, expect](Instance& instance) { |
| Result* result = static_cast<Result*>(instance.Result); |
| instance.Bind( |
| [result, &f](cm::string_view arg) -> Continue { |
| return f(*result, arg); |
| }, |
| expect); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind( |
| cm::static_string_view name, |
| std::function<Continue(Result&, cm::string_view, cm::string_view)> f, |
| ExpectAtLeast expect = { 1 }) |
| { |
| this->Base::Bind(name, [f, expect](Instance& instance) { |
| Result* result = static_cast<Result*>(instance.Result); |
| cm::string_view keyword = instance.Keyword; |
| instance.Bind( |
| [result, keyword, &f](cm::string_view arg) -> Continue { |
| return f(*result, keyword, arg); |
| }, |
| expect); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind(std::size_t position, |
| cm::optional<std::string> Result::*member) |
| { |
| this->Base::Bind( |
| position, |
| [member](Instance& instance, std::size_t, cm::string_view arg) { |
| Result* result = static_cast<Result*>(instance.Result); |
| result->*member = arg; |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& BindParsedKeywords( |
| std::vector<cm::string_view> Result::*member) |
| { |
| this->Base::BindParsedKeyword( |
| [member](Instance& instance, cm::string_view arg) { |
| (static_cast<Result*>(instance.Result)->*member).emplace_back(arg); |
| }); |
| return *this; |
| } |
| |
| template <typename Range> |
| bool Parse(Result& result, Range const& args, |
| std::vector<std::string>* unparsedArguments, |
| std::size_t pos = 0) const |
| { |
| using ArgumentParser::AsParseResultPtr; |
| ParseResult* parseResultPtr = AsParseResultPtr(result); |
| Instance instance(this->Bindings, parseResultPtr, unparsedArguments, |
| &result); |
| instance.Parse(args, pos); |
| return parseResultPtr ? static_cast<bool>(*parseResultPtr) : true; |
| } |
| |
| template <typename Range> |
| Result Parse(Range const& args, std::vector<std::string>* unparsedArguments, |
| std::size_t pos = 0) const |
| { |
| Result result; |
| this->Parse(result, args, unparsedArguments, pos); |
| return result; |
| } |
| }; |
| |
| template <> |
| class cmArgumentParser<void> : private ArgumentParser::Base |
| { |
| public: |
| template <typename T> |
| cmArgumentParser& Bind(cm::static_string_view name, T& ref) |
| { |
| this->Base::Bind(name, [&ref](Instance& instance) { instance.Bind(ref); }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind(cm::static_string_view name, |
| std::function<Continue(cm::string_view)> f, |
| ExpectAtLeast expect = { 1 }) |
| { |
| this->Base::Bind(name, [f, expect](Instance& instance) { |
| instance.Bind([&f](cm::string_view arg) -> Continue { return f(arg); }, |
| expect); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind( |
| cm::static_string_view name, |
| std::function<Continue(cm::string_view, cm::string_view)> f, |
| ExpectAtLeast expect = { 1 }) |
| { |
| this->Base::Bind(name, [f, expect](Instance& instance) { |
| cm::string_view keyword = instance.Keyword; |
| instance.Bind( |
| [keyword, &f](cm::string_view arg) -> Continue { |
| return f(keyword, arg); |
| }, |
| expect); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& Bind(std::size_t position, cm::optional<std::string>& ref) |
| { |
| this->Base::Bind(position, |
| [&ref](Instance&, std::size_t, cm::string_view arg) { |
| ref = std::string(arg); |
| }); |
| return *this; |
| } |
| |
| cmArgumentParser& BindParsedKeywords(std::vector<cm::string_view>& ref) |
| { |
| this->Base::BindParsedKeyword( |
| [&ref](Instance&, cm::string_view arg) { ref.emplace_back(arg); }); |
| return *this; |
| } |
| |
| template <typename Range> |
| ParseResult Parse(Range const& args, |
| std::vector<std::string>* unparsedArguments, |
| std::size_t pos = 0) const |
| { |
| ParseResult parseResult; |
| Instance instance(this->Bindings, &parseResult, unparsedArguments); |
| instance.Parse(args, pos); |
| return parseResult; |
| } |
| |
| protected: |
| using Base::Instance; |
| using Base::BindKeywordMissingValue; |
| |
| template <typename T> |
| bool Bind(cm::string_view name, T& ref) |
| { |
| return this->MaybeBind(name, |
| [&ref](Instance& instance) { instance.Bind(ref); }); |
| } |
| }; |