/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst 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 #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; KeywordAction TrailingArgs; }; 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 BindTrailingArgs(KeywordAction action) { assert(!this->Bindings.TrailingArgs); this->Bindings.TrailingArgs = 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 ParserState { public: cm::string_view Keyword; std::size_t Pos = 0; std::size_t KeywordValuesSeen = 0; std::size_t KeywordValuesExpected = 0; std::function KeywordValueFunc = nullptr; ActionMap const& Bindings; void* Result = nullptr; bool DoneWithPositional = false; ParserState(ActionMap const& bindings, void* result) : Bindings(bindings) , Result(result) { } }; class Instance { public: Instance(ActionMap const& bindings, ParseResult* parseResult, std::vector* unparsedArguments, void* result = nullptr) : ParseResults(parseResult) , UnparsedArguments(unparsedArguments) { PushState(bindings, result); } ParserState& GetState() { return this->Stack.back(); } void PushState(ActionMap const& bindings, void* result) { this->Stack.emplace_back(bindings, result); } void PopState() { if (!this->Stack.empty()) { this->Stack.pop_back(); } } 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); template void Bind(NonEmpty>>& val, U const& context) { this->Bind( [&val, &context](cm::string_view arg) -> Continue { val.emplace_back(std::string(arg), context); return Continue::Yes; }, ExpectAtLeast{ 1 }); } // 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 Bind(cm::optional& optVal, U const& context) { if (!optVal) { optVal.emplace(); } this->Bind(*optVal, context); } template void Parse(Range& args, std::size_t const pos = 0) { GetState().Pos = pos; for (cm::string_view arg : args) { for (size_t j = 0; j < FindKeywordOwnerLevel(arg); ++j) { this->PopState(); } this->Consume(arg); GetState().Pos++; } this->FinishKeyword(); while (this->Stack.size() > 1) { this->FinishKeyword(); this->PopState(); } } std::size_t FindKeywordOwnerLevel(cm::string_view arg) const { for (std::size_t i = Stack.size(); i--;) { if (this->Stack[i].Bindings.Keywords.Find(arg) != this->Stack[i].Bindings.Keywords.end()) { return (this->Stack.size() - 1) - i; } } return 0; } private: std::vector Stack; ParseResult* ParseResults = nullptr; std::vector* UnparsedArguments = nullptr; void Consume(cm::string_view arg); void FinishKeyword(); template friend class ::cmArgumentParser; }; } // namespace ArgumentParser template class cmArgumentParser : private ArgumentParser::Base { public: using Base::Bindings; // 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 mT = cm::remove_member_pointer_t, typename = cm::enable_if_t::value>, typename = cm::enable_if_t::value>> cmArgumentParser& Bind(cm::static_string_view name, T member) { this->Base::Bind(name, [member](Instance& instance) { instance.Bind(static_cast(instance.GetState().Result)->*member); }); return *this; } template cmArgumentParser& BindWithContext(cm::static_string_view name, T Result::*member, U Result::*context) { this->Base::Bind(name, [member, context](Instance& instance) { auto* result = static_cast(instance.GetState().Result); instance.Bind(result->*member, result->*context); }); 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.GetState().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.GetState().Result); cm::string_view keyword = instance.GetState().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.GetState().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.GetState().Result); cm::string_view keyword = instance.GetState().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.GetState().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.GetState().Result)->*member) .emplace_back(arg); }); return *this; } template , typename mT = cm::remove_member_pointer_t, typename = cm::enable_if_t::value>, typename = cm::enable_if_t::value>> cmArgumentParser& BindTrailingArgs(T member) { this->Base::BindTrailingArgs([member](Instance& instance) { instance.Bind(static_cast(instance.GetState().Result)->*member); }); return *this; } template cmArgumentParser& BindSubParser(cm::static_string_view name, cmArgumentParser& parser, cm::optional U::*member) { this->Base::Bind(name, [name, parser, member](Instance& instance) { auto* parentResult = static_cast(instance.GetState().Result); auto& childResult = parentResult->*member; childResult.emplace(T()); instance.Bind(nullptr, ExpectAtLeast{ 0 }); instance.PushState(parser.Bindings, &(*childResult)); instance.Consume(name); }); return *this; } template cmArgumentParser& BindSubParser(cm::static_string_view name, cmArgumentParser& parser, T U::*member) { this->Base::Bind(name, [name, parser, member](Instance& instance) { auto* parentResult = static_cast(instance.GetState().Result); auto* childResult = &(parentResult->*member); instance.Bind(nullptr, ExpectAtLeast{ 1 }); instance.PushState(parser.Bindings, childResult); instance.Consume(name); }); 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; } bool HasKeyword(cm::string_view key) const { return this->Bindings.Keywords.Find(key) != this->Bindings.Keywords.end(); } }; template <> class cmArgumentParser : private ArgumentParser::Base { public: using Base::Bindings; 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.GetState().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 cmArgumentParser& BindTrailingArgs(T& ref) { this->Base::BindTrailingArgs( [&ref](Instance& instance) { instance.Bind(ref); }); return *this; } template cmArgumentParser& BindSubParser(cm::static_string_view name, cmArgumentParser& parser, cm::optional& subResult) { this->Base::Bind(name, [name, parser, &subResult](Instance& instance) { subResult.emplace(U()); instance.Bind(nullptr, ExpectAtLeast{ 0 }); instance.PushState(parser.Bindings, &(*subResult)); instance.Consume(name); }); return *this; } template cmArgumentParser& BindSubParser(cm::static_string_view name, cmArgumentParser& parser, U& subResult) { this->Base::Bind(name, [name, parser, &subResult](Instance& instance) { instance.Bind(nullptr, ExpectAtLeast{ 1 }); instance.PushState(parser.Bindings, &subResult); instance.Consume(name); }); 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; } bool HasKeyword(cm::string_view key) const { return this->Bindings.Keywords.Find(key) != this->Bindings.Keywords.end(); } 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); }); } };