/* 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 #include #include #include #include #include #include #include #include #include #include #include "cmArgumentParserTypes.h" // IWYU pragma: keep template class cmArgumentParser; // IWYU pragma: keep class cmMakefile; namespace ArgumentParser { class ParseResult { std::map 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 const& GetKeywordErrors() const { return this->KeywordErrors; } bool MaybeReportError(cmMakefile& mf) const; }; template typename std::enable_if::value, ParseResult*>::type AsParseResultPtr(Result& result) { return &result; } template typename std::enable_if::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; using KeywordNameAction = std::function; using PositionAction = std::function; // using KeywordActionMap = cm::flat_map; class KeywordActionMap : public std::vector> { public: std::pair Emplace(cm::string_view name, KeywordAction action); const_iterator Find(cm::string_view name) const; }; // using PositionActionMap = cm::flat_map; class PositionActionMap : public std::vector> { public: std::pair 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(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(inserted); } }; class Instance { public: Instance(ActionMap const& bindings, ParseResult* parseResult, std::vector* unparsedArguments, void* result = nullptr) : Bindings(bindings) , ParseResults(parseResult) , UnparsedArguments(unparsedArguments) , Result(result) { } void Bind(std::function f, ExpectAtLeast expect); void Bind(bool& val); void Bind(std::string& val); void Bind(NonEmpty& val); void Bind(Maybe& val); void Bind(MaybeEmpty>& val); void Bind(NonEmpty>& val); void Bind(std::vector>& val); // cm::optional<> records the presence the keyword to which it binds. template void Bind(cm::optional& optVal) { if (!optVal) { optVal.emplace(); } this->Bind(*optVal); } template 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* UnparsedArguments = nullptr; void* Result = nullptr; cm::string_view Keyword; std::size_t KeywordValuesSeen = 0; std::size_t KeywordValuesExpected = 0; std::function KeywordValueFunc; bool DoneWithPositional = false; void Consume(std::size_t pos, cm::string_view arg); void FinishKeyword(); template friend class ::cmArgumentParser; }; } // namespace ArgumentParser template 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 cmArgumentParser& Bind(cm::static_string_view name, T Result::*member) { this->Base::Bind(name, [member](Instance& instance) { instance.Bind(static_cast(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(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(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 f, ExpectAtLeast expect = { 1 }) { this->Base::Bind(name, [f, expect](Instance& instance) { Result* result = static_cast(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 f, ExpectAtLeast expect = { 1 }) { this->Base::Bind(name, [f, expect](Instance& instance) { Result* result = static_cast(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 Result::*member) { this->Base::Bind( position, [member](Instance& instance, std::size_t, cm::string_view arg) { Result* result = static_cast(instance.Result); result->*member = arg; }); return *this; } cmArgumentParser& BindParsedKeywords( std::vector Result::*member) { this->Base::BindParsedKeyword( [member](Instance& instance, cm::string_view arg) { (static_cast(instance.Result)->*member).emplace_back(arg); }); return *this; } template bool Parse(Result& result, Range const& args, std::vector* 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(*parseResultPtr) : true; } template Result Parse(Range const& args, std::vector* unparsedArguments, std::size_t pos = 0) const { Result result; this->Parse(result, args, unparsedArguments, pos); return result; } }; template <> class cmArgumentParser : private ArgumentParser::Base { public: template 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 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 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& ref) { this->Base::Bind(position, [&ref](Instance&, std::size_t, cm::string_view arg) { ref = std::string(arg); }); return *this; } cmArgumentParser& BindParsedKeywords(std::vector& ref) { this->Base::BindParsedKeyword( [&ref](Instance&, cm::string_view arg) { ref.emplace_back(arg); }); return *this; } template ParseResult Parse(Range const& args, std::vector* 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 bool Bind(cm::string_view name, T& ref) { return this->MaybeBind(name, [&ref](Instance& instance) { instance.Bind(ref); }); } };