From 0db8007fcb80b319f5fec3a9b2b245a79c7e9141 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sat, 24 Aug 2024 23:05:41 +0800 Subject: [PATCH] fix: update CmdHelper in Unvirt but not finished --- Unvirt/CmdHelper.cpp | 634 ++++++++++++++++++------------------------- Unvirt/CmdHelper.hpp | 478 ++++++++++++++++++++------------ 2 files changed, 562 insertions(+), 550 deletions(-) diff --git a/Unvirt/CmdHelper.cpp b/Unvirt/CmdHelper.cpp index 284787e..b10c475 100644 --- a/Unvirt/CmdHelper.cpp +++ b/Unvirt/CmdHelper.cpp @@ -169,6 +169,10 @@ namespace Unvirt::CmdHelper { #pragma region Arguments Map + ArgumentsMap::ArgumentsMap() : m_Data() {} + + ArgumentsMap::~ArgumentsMap() {} + #pragma endregion #pragma region Help Document @@ -229,160 +233,288 @@ namespace Unvirt::CmdHelper { #pragma endregion + namespace Nodes { + #pragma region Abstract Node - AbstractNode::AbstractNode() : - m_Execution(nullptr), m_Comment(), - m_Literals(), m_Choices(), m_Args() {} + AbstractNode::AbstractNode() : + m_Execution(nullptr), m_Comment(), m_Nodes() {} - AbstractNode::~AbstractNode() { - for (auto& ptr : m_Literals) { - delete ptr; - } - for (auto& ptr : m_Choices) { - delete ptr; - } - for (auto& ptr : m_Args) { - delete ptr; - } - } + AbstractNode::~AbstractNode() {} - AbstractNode* AbstractNode::Then(AbstractNode* node) { - // check conflict - for (auto& pnode : m_Literals) { - if (pnode->IsConflictWith(node)) - throw std::invalid_argument("conflict node."); - } - for (auto& pnode : m_Choices) { - if (pnode->IsConflictWith(node)) - throw std::invalid_argument("conflict node."); - } - for (auto& pnode : m_Args) { - if (pnode->IsConflictWith(node)) - throw std::invalid_argument("conflict node."); + AbstractNode& AbstractNode::Executes(FctExecution_t fct, const std::u8string_view& exec_desc) { + if (m_Execution != nullptr) + throw std::invalid_argument("you should not assign execution multiuple times."); + if (fct == nullptr) + throw std::invalid_argument("the function passed for executing should not be nullptr."); + m_Execution = fct; + m_ExecutionDesc = exec_desc; + return *this; } - // add into list - switch (node->GetNodeType()) { - case NodeType::Literal: - m_Literals.emplace_back(node); - break; - case NodeType::Choice: - m_Choices.emplace_back(node); - break; - case NodeType::Argument: - m_Args.emplace_back(node); - break; - default: - throw std::runtime_error("No such node type."); + AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) { + m_Comment = comment; + return *this; } - return this; - } + void AbstractNode::Help(HelpDocument& doc) { + // Push self symbol to help document stack. + doc.Push(GetHelpSymbol(), m_Comment); - AbstractNode* AbstractNode::Executes(ExecutionFct fct, const char* cmt) { - if (m_Execution != nullptr) throw std::invalid_argument("duplicated executions."); - if (fct == nullptr) throw std::invalid_argument("no function."); - m_Execution = fct; - m_ExecutionDesc = cmt == nullptr ? "" : cmt; - return this; - } + // Check whether this node is terminal. + // If it is, terminate it once. + if (m_Execution != nullptr) { + doc.Terminate(m_ExecutionDesc); + } - AbstractNode* AbstractNode::Comment(const char* cmt) { - if (cmt == nullptr) - throw std::invalid_argument("no comment."); - m_Comment = cmt; - return this; - } + // Then process its children nodes. + for (auto& node : m_Nodes) { + node->Help(doc); + } - void AbstractNode::Help(HelpDocument* doc) { - // add self - std::string symbol(GetHelpSymbol()); - doc->Push(symbol, m_Comment); - - // check terminal - if (m_Execution != nullptr) { - doc->Terminate(m_ExecutionDesc); + // Pop self from help document stack + doc.Pop(); } - // iterate children - for (auto& pnode : m_Literals) { - pnode->Help(doc); - } - for (auto& pnode : m_Choices) { - pnode->Help(doc); - } - for (auto& pnode : m_Args) { - pnode->Help(doc); - } + bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) { + // if no data can consume, return + if (al.empty()) return false; - // pop self - doc->Pop(); - } + // backup current value + std::u8string cur_cmd = al.front(); + // consume self + if (!BeginConsume(cur_cmd, am)) { + // fail to consume self. not matched. return + return false; + } - bool AbstractNode::Consume(std::deque& arglist, ArgumentsMap* argmap) { - // if no data can consume, return - if (arglist.empty()) return false; - - // backup current value - std::string cur = arglist.front(); - // consume self - if (!BeginAccept(cur, argmap)) { - // fail to consume self. not matched. return - return false; - } - - // pop front for following code - arglist.pop_front(); + // pop front for processing child nodes. + al.pop_front(); #define CONSUME_DEFER \ - arglist.push_front(cur); \ - EndAccept(argmap); + al.emplace_front(cur_cmd); \ + EndConsume(am); - if (arglist.empty()) { - // this is must be a terminal. - // check whether we have execution. - if (m_Execution == nullptr) { + if (al.empty()) { + // if no more data for parsing. + // this is must be a terminal. + // check whether we have execution. + if (m_Execution == nullptr) { + CONSUME_DEFER; + return false; + } else { + m_Execution(am); + CONSUME_DEFER; + return true; + } + } else { + // still have data to be parsed. try to match them. + // iterate node list to find the real terminal + // however, we need iterate literal and choice first, the iterate argument. + for (auto& node : m_Nodes) { + if (node->IsArgument()) continue; + if (node->Consume(al, am)) { + CONSUME_DEFER; + return true; + } + } + for (auto& node : m_Nodes) { + if (!node->IsArgument()) continue; + if (node->Consume(al, am)) { + CONSUME_DEFER; + return true; + } + } + + // if still nothing to match, return false CONSUME_DEFER; return false; - } else { - m_Execution(argmap); - CONSUME_DEFER; - return true; } - } else { - // have following command, try match them - // iterate literal and argument to check terminal - for (auto& pnode : m_Literals) { - if (pnode->Consume(arglist, argmap)) { - CONSUME_DEFER; - return true; - } - } - for (auto& pnode : m_Choices) { - if (pnode->Consume(arglist, argmap)) { - CONSUME_DEFER; - return true; - } - } - for (auto& pnode : m_Args) { - if (pnode->Consume(arglist, argmap)) { - CONSUME_DEFER; - return true; - } - } - - // if still nothing to match, return false - CONSUME_DEFER; - return false; - } #undef CONSUME_DEFER - } + } #pragma endregion +#pragma region Literal + + Literal::Literal(const std::u8string_view& words) : + AbstractNode(), m_Literal(words), m_ConflictSet { m_Literal } { + if (words.empty()) + throw std::invalid_argument("The word of literal node should not be empty."); + } + + Literal::~Literal() {} + + bool Literal::IsArgument() { return false; } + const std::set& Literal::GetConflictSet() { return m_ConflictSet; } + std::u8string Literal::GetHelpSymbol() { return m_Literal; } + bool Literal::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return cur_cmd == m_Literal; } + void Literal::EndConsume(ArgumentsMap& am) {} + +#pragma endregion + +#pragma region Choice + + Choice::Choice(const std::u8string_view& argname, const std::initializer_list& vocabulary) : + AbstractNode(), + m_ChoiceName(argname), m_Vocabulary(vocabulary), + m_ConflictSet() { + // check argument + if (argname.empty()) + throw std::invalid_argument("Choice argument name should not be empty."); + if (m_Vocabulary.size() < 2u) + throw std::invalid_argument("Too less vocabulary for choice. At least 2 items."); + // init conflict set + m_ConflictSet.insert(m_ChoiceName); + m_ConflictSet.insert(m_Vocabulary.begin(), m_Vocabulary.end()); + } + + Choice::~Choice() {} + + bool Choice::IsArgument() { return false; } + const std::set& Choice::GetConflictSet() { return m_ConflictSet; } + std::u8string Choice::GetHelpSymbol() { + return YYCC::StringHelper::Printf(u8"[%s]", + YYCC::StringHelper::Join(m_Vocabulary, u8" | ").c_str() + ); + } + bool Choice::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { + for (size_t i = 0; i < m_Vocabulary.size(); ++i) { + if (cur_cmd == m_Vocabulary[i]) { + am.Add(m_ChoiceName, cur_cmd); + return true; + } + } + return false; + } + void Choice::EndConsume(ArgumentsMap& am) { am.Remove(m_ChoiceName); } + +#pragma endregion + +#pragma region Abstract Argument + + AbstractArgument::AbstractArgument(const std::u8string_view& argname) : + AbstractNode(), + m_ArgName(argname == nullptr ? "" : argname), + m_Accepted(false), m_ParsedData(nullptr) { + if (argname == nullptr || m_ArgName.empty()) + throw std::invalid_argument("Invalid argument name."); + } + + AbstractArgument::~AbstractArgument() {} + + NodeType AbstractArgument::GetNodeType() { + return NodeType::Argument; + } + + bool AbstractArgument::IsConflictWith(AbstractNode* node) { + switch (node->GetNodeType()) { + case NodeType::Literal: + return false; + case NodeType::Choice: + return m_ArgName == dynamic_cast(node)->m_ChoiceName; + case NodeType::Argument: + return m_ArgName == dynamic_cast(node)->m_ArgName; + default: + throw std::runtime_error("No such node type."); + } + } + + std::string AbstractArgument::GetHelpSymbol() { + std::string newargname = "<"; + newargname.append(m_ArgName); + newargname.append(">"); + return newargname; + } + + bool AbstractArgument::BeginAccept(const std::string& strl, ArgumentsMap* amap) { + m_Accepted = BeginParse(strl); + if (m_Accepted) amap->Add(m_ArgName, this); + return m_Accepted; + } + + void AbstractArgument::EndAccept(ArgumentsMap* amap) { + if (m_Accepted) { + amap->Remove(m_ArgName); + EndParse(); + m_Accepted = false; + } + } + +#pragma endregion + +#pragma region Argument Detail Impl + + bool IntArgument::BeginParse(const std::string& val) { + char* pend = nullptr; + errno = 0; + int64_t v = std::strtoll(val.c_str(), &pend, 10); + + if (pend == val.c_str() || errno == ERANGE) return false; + + // check limit + int32_t value = static_cast(v); + if (m_IntLimit != nullptr && !m_IntLimit(value)) { + return false; + } + + m_ParsedData = new IntArgument::vType(value); + return true; + } + + void IntArgument::EndParse() { + delete reinterpret_cast(m_ParsedData); + m_ParsedData = nullptr; + } + + bool StringArgument::BeginParse(const std::string& strl) { + // string always accept every text + m_ParsedData = new StringArgument::vType(strl); + return true; + } + + void StringArgument::EndParse() { + delete reinterpret_cast(m_ParsedData); + m_ParsedData = nullptr; + } + + // Copy from Gamepiaynmo/BallanceModLoader + std::vector SplitString(const std::string& str, const std::string& de) { + size_t lpos, pos = 0; + std::vector res; + + lpos = str.find_first_not_of(de, pos); + while (lpos != std::string::npos) { + pos = str.find_first_of(de, lpos); + res.push_back(str.substr(lpos, pos - lpos)); + if (pos == std::string::npos) break; + + lpos = str.find_first_not_of(de, pos); + } + + if (pos != std::string::npos) + res.push_back(""); + + return res; + } + + bool EncodingArgument::BeginParse(const std::string& strl) { + // encoding always accept every text + m_ParsedData = new EncodingArgument::vType(SplitString(strl, ",")); + return true; + } + + void EncodingArgument::EndParse() { + delete reinterpret_cast(m_ParsedData); + m_ParsedData = nullptr; + } + +#pragma endregion + + } + #pragma region Command Root CommandRoot::CommandRoot() : AbstractNode() {} @@ -436,252 +568,4 @@ namespace Unvirt::CmdHelper { #pragma endregion -#pragma region Literal - - Literal::Literal(const char* words) : - AbstractNode(), - m_Literal(words == nullptr ? "" : words) { - if (words == nullptr || m_Literal.empty()) - throw std::invalid_argument("Invalid literal."); - } - - Literal::~Literal() {} - - NodeType Literal::GetNodeType() { - return NodeType::Literal; - } - - bool Literal::IsConflictWith(AbstractNode* node) { - switch (node->GetNodeType()) { - case NodeType::Literal: - return dynamic_cast(node)->m_Literal == m_Literal; - case NodeType::Choice: - for (const auto& item : dynamic_cast(node)->m_Vocabulary) { - if (item == m_Literal) return true; - } - return false; - case NodeType::Argument: - return false; - default: - throw std::runtime_error("No such node type."); - } - } - - std::string Literal::GetHelpSymbol() { - return m_Literal; - } - - bool Literal::BeginAccept(const std::string& strl, ArgumentsMap*) { - return strl == m_Literal; - } - - void Literal::EndAccept(ArgumentsMap*) {} - -#pragma endregion - -#pragma region Choice - - Choice::Choice(const char* argname, const std::initializer_list& vocabulary) : - AbstractNode(), - m_GottenIndex(0u), m_Accepted(false), - m_ChoiceName(argname == nullptr ? "" : argname), m_Vocabulary(vocabulary) { - if (argname == nullptr || m_ChoiceName.empty()) - throw std::invalid_argument("Invalid choice name."); - if (m_Vocabulary.size() < 2) - throw std::invalid_argument("Too less vocabulary. At least 2 items."); - } - - Choice::~Choice() {} - - size_t* Choice::GetIndex() { - return &m_GottenIndex; - } - - NodeType Choice::GetNodeType() { - return NodeType::Choice; - } - - bool Choice::IsConflictWith(AbstractNode* node) { - switch (node->GetNodeType()) { - case NodeType::Literal: - { - Literal* pliteral = dynamic_cast(node); - for (const auto& word : m_Vocabulary) { - if (word == pliteral->m_Literal) - return true; - } - return false; - } - case NodeType::Choice: - { - Choice* pchoice = dynamic_cast(node); - if (pchoice->m_ChoiceName == m_ChoiceName) - return true; - - for (const auto& thisword : m_Vocabulary) { - for (const auto& thatword : pchoice->m_Vocabulary) { - if (thisword == thatword) - return true; - } - } - return false; - } - case NodeType::Argument: - return m_ChoiceName == dynamic_cast(node)->m_ArgName; - default: - throw std::runtime_error("No such node type."); - } - } - - std::string Choice::GetHelpSymbol() { - std::string switches; - for (const auto& item : m_Vocabulary) { - if (!switches.empty()) switches += " | "; - switches += item; - } - return "[" + switches + "]"; - } - - bool Choice::BeginAccept(const std::string& strl, ArgumentsMap* amap) { - for (size_t i = 0; i < m_Vocabulary.size(); ++i) { - if (strl == m_Vocabulary[i]) { - m_Accepted = true; - m_GottenIndex = i; - amap->Add(m_ChoiceName, this); - return true; - } - } - - return false; - } - - void Choice::EndAccept(ArgumentsMap* amap) { - if (m_Accepted) { - m_Accepted = false; - amap->Remove(m_ChoiceName); - } - } - -#pragma endregion - -#pragma region Abstract Argument - - AbstractArgument::AbstractArgument(const char* argname) : - AbstractNode(), - m_ArgName(argname == nullptr ? "" : argname), - m_Accepted(false), m_ParsedData(nullptr) { - if (argname == nullptr || m_ArgName.empty()) - throw std::invalid_argument("Invalid argument name."); - } - - AbstractArgument::~AbstractArgument() {} - - NodeType AbstractArgument::GetNodeType() { - return NodeType::Argument; - } - - bool AbstractArgument::IsConflictWith(AbstractNode* node) { - switch (node->GetNodeType()) { - case NodeType::Literal: - return false; - case NodeType::Choice: - return m_ArgName == dynamic_cast(node)->m_ChoiceName; - case NodeType::Argument: - return m_ArgName == dynamic_cast(node)->m_ArgName; - default: - throw std::runtime_error("No such node type."); - } - } - - std::string AbstractArgument::GetHelpSymbol() { - std::string newargname = "<"; - newargname.append(m_ArgName); - newargname.append(">"); - return newargname; - } - - bool AbstractArgument::BeginAccept(const std::string& strl, ArgumentsMap* amap) { - m_Accepted = BeginParse(strl); - if (m_Accepted) amap->Add(m_ArgName, this); - return m_Accepted; - } - - void AbstractArgument::EndAccept(ArgumentsMap* amap) { - if (m_Accepted) { - amap->Remove(m_ArgName); - EndParse(); - m_Accepted = false; - } - } - -#pragma endregion - -#pragma region Argument Detail Impl - - bool IntArgument::BeginParse(const std::string& val) { - char* pend = nullptr; - errno = 0; - int64_t v = std::strtoll(val.c_str(), &pend, 10); - - if (pend == val.c_str() || errno == ERANGE) return false; - - // check limit - int32_t value = static_cast(v); - if (m_IntLimit != nullptr && !m_IntLimit(value)) { - return false; - } - - m_ParsedData = new IntArgument::vType(value); - return true; - } - - void IntArgument::EndParse() { - delete reinterpret_cast(m_ParsedData); - m_ParsedData = nullptr; - } - - bool StringArgument::BeginParse(const std::string& strl) { - // string always accept every text - m_ParsedData = new StringArgument::vType(strl); - return true; - } - - void StringArgument::EndParse() { - delete reinterpret_cast(m_ParsedData); - m_ParsedData = nullptr; - } - - // Copy from Gamepiaynmo/BallanceModLoader - std::vector SplitString(const std::string& str, const std::string& de) { - size_t lpos, pos = 0; - std::vector res; - - lpos = str.find_first_not_of(de, pos); - while (lpos != std::string::npos) { - pos = str.find_first_of(de, lpos); - res.push_back(str.substr(lpos, pos - lpos)); - if (pos == std::string::npos) break; - - lpos = str.find_first_not_of(de, pos); - } - - if (pos != std::string::npos) - res.push_back(""); - - return res; - } - - bool EncodingArgument::BeginParse(const std::string& strl) { - // encoding always accept every text - m_ParsedData = new EncodingArgument::vType(SplitString(strl, ",")); - return true; - } - - void EncodingArgument::EndParse() { - delete reinterpret_cast(m_ParsedData); - m_ParsedData = nullptr; - } - -#pragma endregion - } diff --git a/Unvirt/CmdHelper.hpp b/Unvirt/CmdHelper.hpp index 741be7c..1d84a97 100644 --- a/Unvirt/CmdHelper.hpp +++ b/Unvirt/CmdHelper.hpp @@ -1,15 +1,16 @@ #pragma once -#include +#include #include #include -#include #include #include -#include -#include +#include +#include +#include #include #include +#include #include namespace Unvirt::CmdHelper { @@ -50,9 +51,7 @@ namespace Unvirt::CmdHelper { StateType m_State, m_PrevState; }; -#pragma region ArgumentsMap - - namespace ArgumentsMapItem { + namespace AMItems { class AbstractItem { public: @@ -111,25 +110,25 @@ namespace Unvirt::CmdHelper { class ArgumentsMap { public: - ArgumentsMap() : m_Data() {} - ~ArgumentsMap() {} + ArgumentsMap(); + ~ArgumentsMap(); YYCC_DEF_CLS_COPY_MOVE(ArgumentsMap); protected: - std::map> m_Data; + std::map> m_Data; public: - template, int> = 0> + template, int> = 0> void Add(const std::u8string_view& key, _Types&&... args) { // check argument if (key.empty()) throw std::invalid_argument("argument key should not be empty"); // insert into data - auto result = m_Data.try_emplace(std::u8string(key), std::make_unique<_Ty>(std::forward<_Types>(args)...)); + auto result = m_Data.try_emplace(std::u8string(key), std::make_shared<_Ty>(std::forward<_Types>(args)...)); if (!result.second) throw std::runtime_error("try to add an existing key."); } - template, int> = 0> + template, int> = 0> const _Ty& Get() const { // check argument if (key.empty()) @@ -139,21 +138,19 @@ namespace Unvirt::CmdHelper { if (finder == m_Data.end()) throw std::runtime_error("try to get a non-existent key."); // get stored value data - const ArgumentsMapItem::AbstractItem& value = *finder->second.get(); + const AMItems::AbstractItem& value = *finder->second.get(); return static_cast(value); } void Remove(const std::u8string_view& key) { // check argument if (key.empty()) throw std::invalid_argument("argument key should not be empty"); - // remove and return remove result. + // remove it from map and check whether remove item if (m_Data.erase(std::u8string(key)) == 0u) throw std::runtime_error("try to delete a non-existent key."); } }; -#pragma endregion - class HelpDocument { public: HelpDocument(); @@ -188,171 +185,302 @@ namespace Unvirt::CmdHelper { std::vector m_Results; }; - class AbstractNode { - friend class CommandRoot; + namespace Nodes { + + class AbstractNode { + friend class CommandRoot; + public: + using FctExecution_t = void(*)(const ArgumentsMap&); + + public: + AbstractNode(); + virtual ~AbstractNode(); + YYCC_DEF_CLS_COPY_MOVE(AbstractNode); + + protected: + std::vector> m_Nodes; + FctExecution_t m_Execution; + std::u8string m_ExecutionDesc; + std::u8string m_Comment; + + protected: + /** + * @brief The core function to generate help document by following hierarchy. + * @param[in] doc The generating help document. + */ + void Help(HelpDocument& doc); + /** + * @brief The core function to consume splitted commands by following hierarchy. + * @param[in] al The splitted commands deque. + * @param[in] am Argument map for operating. + * @return True if we reach a legal terminal, otherwise false. + * Once this function return true, there is no need to process other nodes. + * Because the final command processor has been executed when this function return true. + */ + bool Consume(CmdSplitter::Result_t& al, ArgumentsMap& am); + + protected: + /** + * @brief Check whether current node is argument. + * @return True if it is. + * @remakrs + * \li Sub-class must implement this function. + * \li This function is used internally because when consuming nodes, + * we need consume literal and choice first, then consume argument. + */ + virtual bool IsArgument() = 0; + /** + * @brief Get a set of identifier used for checking node conflict. + * @return The set of identifier. + * @remarks + * Sub-class must implement this function. + * \par + * This function return the reference to the set. + * It means that sub-class need allocate some memory by themselves + * to store the value returned by this function. + * \par + * When adding new nodes, we use this function to check whether there is conflict. + * If the intersection between 2 sets coming from different nodes is not empty, + * it means that they have conflict, the process of adding will be aborted. + * \par + * In details: + * \li Literal: Put its literal in set directly. + * \li Choice: Put its vocabulary and associated argument name in set. + * \li Argument: Put its argument name in set. + */ + virtual const std::set& GetConflictSet() = 0; + /** + * @brief Get the string presented in syntax part in help messages. + * @return The string presented in syntax part in help messages. + * @remarks Sub-class must implement this function. + * \li Literal: Return its literal directly. + * \li Choice: Join vocabulary with \c \| then brack it with square bracker. + * \li Argument: Bracket its argument name with sharp bracket. + */ + virtual std::u8string GetHelpSymbol() = 0; + /** + * @brief Try consume given command for this node. + * @param[in] cur_cmd The command hope to be accepted. + * @param[in] am Argument map for operating. + * @return True if this node accept this command. + * @remarks + * Sub-class must implement this function. + * \par + * For choice and argument, if given command is accepted, + * implementation should insert data into argument map. + * So that user can visit it. + */ + virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) = 0; + /** + * @brief End the consume of this node. + * @param[in] am Argument map for operating. + * @remarks + * Sub-class must implement this function. + * \par + * If BeginConsume() return false, this function will not be called. + * \par + * For choice and argument, if you have accepted one command, + * implementation should remove data from argument map. + * So that following nodes (not belongs to this tree) may add the argument with same name. + */ + virtual void EndConsume(ArgumentsMap& am) = 0; + + public: + /** + * @brief Add a new node as child nodes for this node. + * @tparam _Ty The child class type of AbstractNode. + * @param[in] node The node instance to be added. + * @return Return self for chain calling. + */ + template, int> = 0> + AbstractNode& Then(_Ty&& node) { + // create node first + auto new_node = std::make_shared<_Ty>(std::forward<_Types>(node)); + // check conflict + std::vector intersection; + const auto& new_node_set = new_node->GetConflictSet(); + for (auto& node : mNodes) { + const auto& node_set = node->GetConflictSet(); + // compute intersection + intersection.clear(); + std::set_intersection( + new_node_set.begin(), new_node_set.end(), + node_set.begin(), node_set.begin(), + std::back_inserter(intersection) + ); + // check whether it is empty intersection + if (!intersection.empty()) + throw std::invalid_argument("try to add a conflict node. please check your code."); + } + // add into list + m_Nodes.emplace_back(std::move(new_node)); + return *this; + } + /** + * @brief Setup execution infomation for this node. + * @param[in] fct Associated execution function. nullptr is not allowed. + * @param[in] exec_desc Associated execution message presented in help message. + * @return Return self for chain calling. + * @remarks This function only can be called once for one node. + */ + AbstractNode& Executes(FctExecution_t fct, const std::u8string_view& exec_desc = u8""); + /** + * @brief Setup command for this node. + * @param[in] comment The command of current node. + * @return Return self for chain calling. + */ + AbstractNode& Comment(const std::u8string_view& comment = u8""); + + }; + + class Literal : public AbstractNode { + public: + Literal(const std::u8string_view& words); + virtual ~Literal(); + YYCC_DEF_CLS_COPY_MOVE(Literal); + + protected: + virtual bool IsArgument() override; + virtual const std::set& GetConflictSet() override; + virtual std::u8string GetHelpSymbol() override; + virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; + virtual void EndConsume(ArgumentsMap& am) override; + + std::u8string m_Literal; + std::set m_ConflictSet; + }; + + class Choice : public AbstractNode { + public: + using ArgValue_t = size_t; + public: + Choice(const std::u8string_view& argname, const std::initializer_list& vocabulary); + virtual ~Choice(); + YYCC_DEF_CLS_COPY_MOVE(Choice); + + protected: + virtual bool IsArgument() override; + virtual const std::set& GetConflictSet() override; + virtual std::u8string GetHelpSymbol() override; + virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; + virtual void EndConsume(ArgumentsMap& am) override; + + std::u8string m_ChoiceName; + std::vector m_Vocabulary; + std::set m_ConflictSet; + }; + + class AbstractArgument : public AbstractNode { + public: + AbstractArgument(const std::u8string_view& argname); + virtual ~AbstractArgument(); + YYCC_DEF_CLS_COPY_MOVE(AbstractArgument); + + // AbstractArgument do not implement BeginConsume(). + // Because it involve the detail of data parsing. + // However, other parts are shared by all argument type. + + protected: + virtual bool IsArgument() override; + virtual const std::set& GetConflictSet() override; + virtual std::u8string GetHelpSymbol() override; + // virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; + virtual void EndConsume(ArgumentsMap& am) override; + + std::u8string m_ArgName; + std::set m_ConflictSet; + }; + + /** + * @brief Return true mean this value can accept. + */ + using IntLimit = std::function; + class IntArgument : public AbstractArgument { + public: + using vType = int32_t; + IntArgument(const char* argname, IntLimit limit = nullptr) : + AbstractArgument(argname), m_IntLimit(limit) {} + virtual ~IntArgument() {} + YYCC_DEL_CLS_COPY_MOVE(IntArgument); + + protected: + virtual bool BeginParse(const std::string&) override; + virtual void EndParse() override; + + IntLimit m_IntLimit; + }; + + class StringArgument : public AbstractArgument { + public: + using vType = std::string; + StringArgument(const char* argname) : + AbstractArgument(argname) {} + virtual ~StringArgument() {} + YYCC_DEL_CLS_COPY_MOVE(StringArgument); + + protected: + virtual bool BeginParse(const std::string&) override; + virtual void EndParse() override; + }; + + class EncodingArgument : public AbstractArgument { + public: + using vType = std::vector; + EncodingArgument(const char* argname) : + AbstractArgument(argname) {} + virtual ~EncodingArgument() {} + YYCC_DEL_CLS_COPY_MOVE(EncodingArgument); + + protected: + virtual bool BeginParse(const std::string&) override; + virtual void EndParse() override; + }; + + } + + class CommandParser { public: - using ExecutionFct = void(*)(const ArgumentsMap&); + CommandParser(); + ~CommandParser(); public: - AbstractNode(); - virtual ~AbstractNode(); - YYCC_DEF_CLS_COPY_MOVE(AbstractNode); + bool Parse(std::deque& cmds); + HelpDocument Help(); - AbstractNode* Then(AbstractNode*); - AbstractNode* Executes(ExecutionFct, const char* = nullptr); - AbstractNode* Comment(const char*); - - protected: - void Help(HelpDocument& doc); - bool Consume(CmdSplitter::Result_t& cmds, ArgumentsMap& am); - virtual bool IsConflictWith(AbstractNode* node) = 0; - virtual bool IsArgument() = 0; - virtual std::u8string HelpSymbol() = 0; - virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) = 0; - virtual void EndConsume(ArgumentsMap*) = 0; - - protected: - std::vector> m_Nodes; - ExecutionFct m_Execution; - std::string m_ExecutionDesc; - std::string m_Comment; + private: + class RootNode : Nodes::AbstractNode { + public: + RootNode(); + virtual ~RootNode(); + YYCC_DEF_CLS_COPY_MOVE(RootNode); + + protected: + virtual bool IsArgument() override; + virtual const std::set& GetConflictSet() override; + virtual std::u8string GetHelpSymbol() override; + virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; + virtual void EndConsume(ArgumentsMap& am) override; + }; + RootNode m_RootNode; }; - class CommandRoot : public AbstractNode { - public: - CommandRoot(); - virtual ~CommandRoot(); - YYCC_DEL_CLS_COPY_MOVE(CommandRoot); + //class CommandRoot : public AbstractNode { + //public: + // CommandRoot(); + // virtual ~CommandRoot(); + // YYCC_DEL_CLS_COPY_MOVE(CommandRoot); - // Root use special consume and help functions. - bool RootConsume(std::deque&); - HelpDocument* RootHelp(); + // // Root use special consume and help functions. + // bool RootConsume(std::deque&); + // HelpDocument* RootHelp(); - public: - virtual NodeType GetNodeType() override { throw std::logic_error("Root can not be called."); } - virtual bool IsConflictWith(AbstractNode*) override { throw std::logic_error("Root can not be called."); } - protected: - virtual std::string GetHelpSymbol() override { throw std::logic_error("Root can not be called."); } - virtual bool BeginAccept(const std::string&, ArgumentsMap*) override { throw std::logic_error("Root can not be called."); } - virtual void EndAccept(ArgumentsMap*) override { throw std::logic_error("Root can not be called."); } - }; - - class Literal : public AbstractNode { - friend class Choice; - friend class AbstractArgument; - public: - Literal(const char* words); - virtual ~Literal(); - YYCC_DEL_CLS_COPY_MOVE(Literal); - - public: - virtual NodeType GetNodeType() override; - virtual bool IsConflictWith(AbstractNode* node) override; - protected: - virtual std::string GetHelpSymbol() override; - virtual bool BeginAccept(const std::string&, ArgumentsMap*) override; - virtual void EndAccept(ArgumentsMap*) override; - - std::string m_Literal; - }; - - class Choice : public AbstractNode { - friend class Literal; - friend class AbstractArgument; - public: - using vType = size_t; - Choice(const char* argname, const std::initializer_list& vocabulary); - virtual ~Choice(); - YYCC_DEL_CLS_COPY_MOVE(Choice); - - vType* GetIndex(); - - public: - virtual NodeType GetNodeType() override; - virtual bool IsConflictWith(AbstractNode* node) override; - protected: - virtual std::string GetHelpSymbol() override; - virtual bool BeginAccept(const std::string&, ArgumentsMap*) override; - virtual void EndAccept(ArgumentsMap*) override; - - std::string m_ChoiceName; - std::vector m_Vocabulary; - bool m_Accepted; - size_t m_GottenIndex; - }; - - class AbstractArgument : public AbstractNode { - friend class Literal; - friend class Choice; - public: - AbstractArgument(const char* argname); - virtual ~AbstractArgument(); - YYCC_DEL_CLS_COPY_MOVE(AbstractArgument); - - template - T GetData() { - return reinterpret_cast(m_ParsedData); - } - - public: - virtual NodeType GetNodeType() override; - virtual bool IsConflictWith(AbstractNode* node) override; - protected: - virtual std::string GetHelpSymbol() override; - virtual bool BeginAccept(const std::string&, ArgumentsMap*) override; - virtual void EndAccept(ArgumentsMap*) override; - - virtual bool BeginParse(const std::string&) = 0; - virtual void EndParse() = 0; - - std::string m_ArgName; - bool m_Accepted; - void* m_ParsedData; - }; - - /** - * @brief Return true mean this value can accept. - */ - using IntLimit = std::function; - class IntArgument : public AbstractArgument { - public: - using vType = int32_t; - IntArgument(const char* argname, IntLimit limit = nullptr) : - AbstractArgument(argname), m_IntLimit(limit) {} - virtual ~IntArgument() {} - YYCC_DEL_CLS_COPY_MOVE(IntArgument); - - protected: - virtual bool BeginParse(const std::string&) override; - virtual void EndParse() override; - - IntLimit m_IntLimit; - }; - - class StringArgument : public AbstractArgument { - public: - using vType = std::string; - StringArgument(const char* argname) : - AbstractArgument(argname) {} - virtual ~StringArgument() {} - YYCC_DEL_CLS_COPY_MOVE(StringArgument); - - protected: - virtual bool BeginParse(const std::string&) override; - virtual void EndParse() override; - }; - - class EncodingArgument : public AbstractArgument { - public: - using vType = std::vector; - EncodingArgument(const char* argname) : - AbstractArgument(argname) {} - virtual ~EncodingArgument() {} - YYCC_DEL_CLS_COPY_MOVE(EncodingArgument); - - protected: - virtual bool BeginParse(const std::string&) override; - virtual void EndParse() override; - }; + //public: + //protected: + // virtual NodeType GetNodeType() override { throw std::logic_error("Root can not be called."); } + // virtual bool IsConflictWith(AbstractNode*) override { throw std::logic_error("Root can not be called."); } + // virtual std::string GetHelpSymbol() override { throw std::logic_error("Root can not be called."); } + // virtual bool BeginAccept(const std::string&, ArgumentsMap*) override { throw std::logic_error("Root can not be called."); } + // virtual void EndAccept(ArgumentsMap*) override { throw std::logic_error("Root can not be called."); } + //}; }