fix: update CmdHelper in Unvirt but not finished

This commit is contained in:
yyc12345 2024-08-24 23:05:41 +08:00
parent 88ce33c358
commit 0db8007fcb
2 changed files with 562 additions and 550 deletions

View File

@ -169,6 +169,10 @@ namespace Unvirt::CmdHelper {
#pragma region Arguments Map #pragma region Arguments Map
ArgumentsMap::ArgumentsMap() : m_Data() {}
ArgumentsMap::~ArgumentsMap() {}
#pragma endregion #pragma endregion
#pragma region Help Document #pragma region Help Document
@ -229,144 +233,94 @@ namespace Unvirt::CmdHelper {
#pragma endregion #pragma endregion
namespace Nodes {
#pragma region Abstract Node #pragma region Abstract Node
AbstractNode::AbstractNode() : AbstractNode::AbstractNode() :
m_Execution(nullptr), m_Comment(), m_Execution(nullptr), m_Comment(), m_Nodes() {}
m_Literals(), m_Choices(), m_Args() {}
AbstractNode::~AbstractNode() { 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::Then(AbstractNode* node) { AbstractNode& AbstractNode::Executes(FctExecution_t fct, const std::u8string_view& exec_desc) {
// check conflict if (m_Execution != nullptr)
for (auto& pnode : m_Literals) { throw std::invalid_argument("you should not assign execution multiuple times.");
if (pnode->IsConflictWith(node)) if (fct == nullptr)
throw std::invalid_argument("conflict node."); throw std::invalid_argument("the function passed for executing should not be nullptr.");
}
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.");
}
// 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.");
}
return this;
}
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_Execution = fct;
m_ExecutionDesc = cmt == nullptr ? "" : cmt; m_ExecutionDesc = exec_desc;
return this; return *this;
} }
AbstractNode* AbstractNode::Comment(const char* cmt) { AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) {
if (cmt == nullptr) m_Comment = comment;
throw std::invalid_argument("no comment."); return *this;
m_Comment = cmt;
return this;
} }
void AbstractNode::Help(HelpDocument* doc) { void AbstractNode::Help(HelpDocument& doc) {
// add self // Push self symbol to help document stack.
std::string symbol(GetHelpSymbol()); doc.Push(GetHelpSymbol(), m_Comment);
doc->Push(symbol, m_Comment);
// check terminal // Check whether this node is terminal.
// If it is, terminate it once.
if (m_Execution != nullptr) { if (m_Execution != nullptr) {
doc->Terminate(m_ExecutionDesc); doc.Terminate(m_ExecutionDesc);
} }
// iterate children // Then process its children nodes.
for (auto& pnode : m_Literals) { for (auto& node : m_Nodes) {
pnode->Help(doc); node->Help(doc);
}
for (auto& pnode : m_Choices) {
pnode->Help(doc);
}
for (auto& pnode : m_Args) {
pnode->Help(doc);
} }
// pop self // Pop self from help document stack
doc->Pop(); doc.Pop();
} }
bool AbstractNode::Consume(std::deque<std::string>& arglist, ArgumentsMap* argmap) { bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) {
// if no data can consume, return // if no data can consume, return
if (arglist.empty()) return false; if (al.empty()) return false;
// backup current value // backup current value
std::string cur = arglist.front(); std::u8string cur_cmd = al.front();
// consume self // consume self
if (!BeginAccept(cur, argmap)) { if (!BeginConsume(cur_cmd, am)) {
// fail to consume self. not matched. return // fail to consume self. not matched. return
return false; return false;
} }
// pop front for following code // pop front for processing child nodes.
arglist.pop_front(); al.pop_front();
#define CONSUME_DEFER \ #define CONSUME_DEFER \
arglist.push_front(cur); \ al.emplace_front(cur_cmd); \
EndAccept(argmap); EndConsume(am);
if (arglist.empty()) { if (al.empty()) {
// if no more data for parsing.
// this is must be a terminal. // this is must be a terminal.
// check whether we have execution. // check whether we have execution.
if (m_Execution == nullptr) { if (m_Execution == nullptr) {
CONSUME_DEFER; CONSUME_DEFER;
return false; return false;
} else { } else {
m_Execution(argmap); m_Execution(am);
CONSUME_DEFER; CONSUME_DEFER;
return true; return true;
} }
} else { } else {
// have following command, try match them // still have data to be parsed. try to match them.
// iterate literal and argument to check terminal // iterate node list to find the real terminal
for (auto& pnode : m_Literals) { // however, we need iterate literal and choice first, the iterate argument.
if (pnode->Consume(arglist, argmap)) { for (auto& node : m_Nodes) {
if (node->IsArgument()) continue;
if (node->Consume(al, am)) {
CONSUME_DEFER; CONSUME_DEFER;
return true; return true;
} }
} }
for (auto& pnode : m_Choices) { for (auto& node : m_Nodes) {
if (pnode->Consume(arglist, argmap)) { if (!node->IsArgument()) continue;
CONSUME_DEFER; if (node->Consume(al, am)) {
return true;
}
}
for (auto& pnode : m_Args) {
if (pnode->Consume(arglist, argmap)) {
CONSUME_DEFER; CONSUME_DEFER;
return true; return true;
} }
@ -383,190 +337,65 @@ namespace Unvirt::CmdHelper {
#pragma endregion #pragma endregion
#pragma region Command Root
CommandRoot::CommandRoot() : AbstractNode() {}
CommandRoot::~CommandRoot() {}
bool CommandRoot::RootConsume(std::deque<std::string>& arglist) {
// if no data can consume, return
if (arglist.empty()) return false;
// create a argument map
ArgumentsMap amap;
// and we only just need iterate all children
for (auto& pnode : m_Literals) {
if (pnode->Consume(arglist, &amap)) {
return true;
}
}
for (auto& pnode : m_Choices) {
if (pnode->Consume(arglist, &amap)) {
return true;
}
}
for (auto& pnode : m_Args) {
if (pnode->Consume(arglist, &amap)) {
return true;
}
}
// no matched
return false;
}
HelpDocument* CommandRoot::RootHelp() {
HelpDocument* doc = new HelpDocument();
// we only just need iterate all 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);
}
return doc;
}
#pragma endregion
#pragma region Literal #pragma region Literal
Literal::Literal(const char* words) : Literal::Literal(const std::u8string_view& words) :
AbstractNode(), AbstractNode(), m_Literal(words), m_ConflictSet { m_Literal } {
m_Literal(words == nullptr ? "" : words) { if (words.empty())
if (words == nullptr || m_Literal.empty()) throw std::invalid_argument("The word of literal node should not be empty.");
throw std::invalid_argument("Invalid literal.");
} }
Literal::~Literal() {} Literal::~Literal() {}
NodeType Literal::GetNodeType() { bool Literal::IsArgument() { return false; }
return NodeType::Literal; const std::set<std::u8string>& 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; }
bool Literal::IsConflictWith(AbstractNode* node) { void Literal::EndConsume(ArgumentsMap& am) {}
switch (node->GetNodeType()) {
case NodeType::Literal:
return dynamic_cast<Literal*>(node)->m_Literal == m_Literal;
case NodeType::Choice:
for (const auto& item : dynamic_cast<Choice*>(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 endregion
#pragma region Choice #pragma region Choice
Choice::Choice(const char* argname, const std::initializer_list<std::string>& vocabulary) : Choice::Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary) :
AbstractNode(), AbstractNode(),
m_GottenIndex(0u), m_Accepted(false), m_ChoiceName(argname), m_Vocabulary(vocabulary),
m_ChoiceName(argname == nullptr ? "" : argname), m_Vocabulary(vocabulary) { m_ConflictSet() {
if (argname == nullptr || m_ChoiceName.empty()) // check argument
throw std::invalid_argument("Invalid choice name."); if (argname.empty())
if (m_Vocabulary.size() < 2) throw std::invalid_argument("Choice argument name should not be empty.");
throw std::invalid_argument("Too less vocabulary. At least 2 items."); 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() {} Choice::~Choice() {}
size_t* Choice::GetIndex() { bool Choice::IsArgument() { return false; }
return &m_GottenIndex; const std::set<std::u8string>& 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) {
NodeType Choice::GetNodeType() {
return NodeType::Choice;
}
bool Choice::IsConflictWith(AbstractNode* node) {
switch (node->GetNodeType()) {
case NodeType::Literal:
{
Literal* pliteral = dynamic_cast<Literal*>(node);
for (const auto& word : m_Vocabulary) {
if (word == pliteral->m_Literal)
return true;
}
return false;
}
case NodeType::Choice:
{
Choice* pchoice = dynamic_cast<Choice*>(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<AbstractArgument*>(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) { for (size_t i = 0; i < m_Vocabulary.size(); ++i) {
if (strl == m_Vocabulary[i]) { if (cur_cmd == m_Vocabulary[i]) {
m_Accepted = true; am.Add<AMItems::StringItem>(m_ChoiceName, cur_cmd);
m_GottenIndex = i;
amap->Add(m_ChoiceName, this);
return true; return true;
} }
} }
return false; return false;
} }
void Choice::EndConsume(ArgumentsMap& am) { am.Remove(m_ChoiceName); }
void Choice::EndAccept(ArgumentsMap* amap) {
if (m_Accepted) {
m_Accepted = false;
amap->Remove(m_ChoiceName);
}
}
#pragma endregion #pragma endregion
#pragma region Abstract Argument #pragma region Abstract Argument
AbstractArgument::AbstractArgument(const char* argname) : AbstractArgument::AbstractArgument(const std::u8string_view& argname) :
AbstractNode(), AbstractNode(),
m_ArgName(argname == nullptr ? "" : argname), m_ArgName(argname == nullptr ? "" : argname),
m_Accepted(false), m_ParsedData(nullptr) { m_Accepted(false), m_ParsedData(nullptr) {
@ -682,6 +511,61 @@ namespace Unvirt::CmdHelper {
m_ParsedData = nullptr; m_ParsedData = nullptr;
} }
#pragma endregion
}
#pragma region Command Root
CommandRoot::CommandRoot() : AbstractNode() {}
CommandRoot::~CommandRoot() {}
bool CommandRoot::RootConsume(std::deque<std::string>& arglist) {
// if no data can consume, return
if (arglist.empty()) return false;
// create a argument map
ArgumentsMap amap;
// and we only just need iterate all children
for (auto& pnode : m_Literals) {
if (pnode->Consume(arglist, &amap)) {
return true;
}
}
for (auto& pnode : m_Choices) {
if (pnode->Consume(arglist, &amap)) {
return true;
}
}
for (auto& pnode : m_Args) {
if (pnode->Consume(arglist, &amap)) {
return true;
}
}
// no matched
return false;
}
HelpDocument* CommandRoot::RootHelp() {
HelpDocument* doc = new HelpDocument();
// we only just need iterate all 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);
}
return doc;
}
#pragma endregion #pragma endregion
} }

View File

@ -1,15 +1,16 @@
#pragma once #pragma once
#include <VTAll.hpp> #include <YYCCommonplace.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
#include <functional>
#include <deque> #include <deque>
#include <map> #include <map>
#include <stdexcept> #include <set>
#include <cinttypes> #include <functional>
#include <algorithm>
#include <initializer_list> #include <initializer_list>
#include <type_traits> #include <type_traits>
#include <stdexcept>
#include <memory> #include <memory>
namespace Unvirt::CmdHelper { namespace Unvirt::CmdHelper {
@ -50,9 +51,7 @@ namespace Unvirt::CmdHelper {
StateType m_State, m_PrevState; StateType m_State, m_PrevState;
}; };
#pragma region ArgumentsMap namespace AMItems {
namespace ArgumentsMapItem {
class AbstractItem { class AbstractItem {
public: public:
@ -111,25 +110,25 @@ namespace Unvirt::CmdHelper {
class ArgumentsMap { class ArgumentsMap {
public: public:
ArgumentsMap() : m_Data() {} ArgumentsMap();
~ArgumentsMap() {} ~ArgumentsMap();
YYCC_DEF_CLS_COPY_MOVE(ArgumentsMap); YYCC_DEF_CLS_COPY_MOVE(ArgumentsMap);
protected: protected:
std::map<std::u8string, std::unique_ptr<ArgumentsMapItem::AbstractItem>> m_Data; std::map<std::u8string, std::shared_ptr<AMItems::AbstractItem>> m_Data;
public: public:
template<class _Ty, class... _Types, std::enable_if_t<std::is_base_of_v<ArgumentsMapItem::AbstractItem, _Ty>, int> = 0> template<class _Ty, class... _Types, std::enable_if_t<std::is_base_of_v<AMItems::AbstractItem, _Ty>, int> = 0>
void Add(const std::u8string_view& key, _Types&&... args) { void Add(const std::u8string_view& key, _Types&&... args) {
// check argument // check argument
if (key.empty()) if (key.empty())
throw std::invalid_argument("argument key should not be empty"); throw std::invalid_argument("argument key should not be empty");
// insert into data // 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) if (!result.second)
throw std::runtime_error("try to add an existing key."); throw std::runtime_error("try to add an existing key.");
} }
template<class _Ty, std::enable_if_t<std::is_base_of_v<ArgumentsMapItem::AbstractItem, _Ty>, int> = 0> template<class _Ty, std::enable_if_t<std::is_base_of_v<AMItems::AbstractItem, _Ty>, int> = 0>
const _Ty& Get() const { const _Ty& Get() const {
// check argument // check argument
if (key.empty()) if (key.empty())
@ -139,21 +138,19 @@ namespace Unvirt::CmdHelper {
if (finder == m_Data.end()) if (finder == m_Data.end())
throw std::runtime_error("try to get a non-existent key."); throw std::runtime_error("try to get a non-existent key.");
// get stored value data // get stored value data
const ArgumentsMapItem::AbstractItem& value = *finder->second.get(); const AMItems::AbstractItem& value = *finder->second.get();
return static_cast<const _Ty&>(value); return static_cast<const _Ty&>(value);
} }
void Remove(const std::u8string_view& key) { void Remove(const std::u8string_view& key) {
// check argument // check argument
if (key.empty()) if (key.empty())
throw std::invalid_argument("argument key should not be 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) if (m_Data.erase(std::u8string(key)) == 0u)
throw std::runtime_error("try to delete a non-existent key."); throw std::runtime_error("try to delete a non-existent key.");
} }
}; };
#pragma endregion
class HelpDocument { class HelpDocument {
public: public:
HelpDocument(); HelpDocument();
@ -188,126 +185,210 @@ namespace Unvirt::CmdHelper {
std::vector<ResultItem> m_Results; std::vector<ResultItem> m_Results;
}; };
namespace Nodes {
class AbstractNode { class AbstractNode {
friend class CommandRoot; friend class CommandRoot;
public: public:
using ExecutionFct = void(*)(const ArgumentsMap&); using FctExecution_t = void(*)(const ArgumentsMap&);
public: public:
AbstractNode(); AbstractNode();
virtual ~AbstractNode(); virtual ~AbstractNode();
YYCC_DEF_CLS_COPY_MOVE(AbstractNode); YYCC_DEF_CLS_COPY_MOVE(AbstractNode);
AbstractNode* Then(AbstractNode*); protected:
AbstractNode* Executes(ExecutionFct, const char* = nullptr); std::vector<std::shared_ptr<AbstractNode>> m_Nodes;
AbstractNode* Comment(const char*); FctExecution_t m_Execution;
std::u8string m_ExecutionDesc;
std::u8string m_Comment;
protected: protected:
/**
* @brief The core function to generate help document by following hierarchy.
* @param[in] doc The generating help document.
*/
void Help(HelpDocument& doc); void Help(HelpDocument& doc);
bool Consume(CmdSplitter::Result_t& cmds, ArgumentsMap& am); /**
virtual bool IsConflictWith(AbstractNode* node) = 0; * @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; virtual bool IsArgument() = 0;
virtual std::u8string HelpSymbol() = 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<std::u8string>& 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; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) = 0;
virtual void EndConsume(ArgumentsMap*) = 0; /**
* @brief End the consume of this node.
protected: * @param[in] am Argument map for operating.
std::vector<std::unique_ptr<AbstractNode>> m_Nodes; * @remarks
ExecutionFct m_Execution; * Sub-class must implement this function.
std::string m_ExecutionDesc; * \par
std::string m_Comment; * If BeginConsume() return false, this function will not be called.
}; * \par
* For choice and argument, if you have accepted one command,
class CommandRoot : public AbstractNode { * implementation should remove data from argument map.
public: * So that following nodes (not belongs to this tree) may add the argument with same name.
CommandRoot(); */
virtual ~CommandRoot(); virtual void EndConsume(ArgumentsMap& am) = 0;
YYCC_DEL_CLS_COPY_MOVE(CommandRoot);
// Root use special consume and help functions.
bool RootConsume(std::deque<std::string>&);
HelpDocument* RootHelp();
public: 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."); } * @brief Add a new node as child nodes for this node.
protected: * @tparam _Ty The child class type of AbstractNode.
virtual std::string GetHelpSymbol() override { throw std::logic_error("Root can not be called."); } * @param[in] node The node instance to be added.
virtual bool BeginAccept(const std::string&, ArgumentsMap*) override { throw std::logic_error("Root can not be called."); } * @return Return self for chain calling.
virtual void EndAccept(ArgumentsMap*) override { throw std::logic_error("Root can not be called."); } */
template<class _Ty, std::enable_if_t<std::is_base_of_v<AbstractNode, _Ty>, int> = 0>
AbstractNode& Then(_Ty&& node) {
// create node first
auto new_node = std::make_shared<_Ty>(std::forward<_Types>(node));
// check conflict
std::vector<std::u8string> 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 { class Literal : public AbstractNode {
friend class Choice;
friend class AbstractArgument;
public: public:
Literal(const char* words); Literal(const std::u8string_view& words);
virtual ~Literal(); virtual ~Literal();
YYCC_DEL_CLS_COPY_MOVE(Literal); YYCC_DEF_CLS_COPY_MOVE(Literal);
public:
virtual NodeType GetNodeType() override;
virtual bool IsConflictWith(AbstractNode* node) override;
protected: protected:
virtual std::string GetHelpSymbol() override; virtual bool IsArgument() override;
virtual bool BeginAccept(const std::string&, ArgumentsMap*) override; virtual const std::set<std::u8string>& GetConflictSet() override;
virtual void EndAccept(ArgumentsMap*) override; virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
std::string m_Literal; std::u8string m_Literal;
std::set<std::u8string> m_ConflictSet;
}; };
class Choice : public AbstractNode { class Choice : public AbstractNode {
friend class Literal;
friend class AbstractArgument;
public: public:
using vType = size_t; using ArgValue_t = size_t;
Choice(const char* argname, const std::initializer_list<std::string>& vocabulary); public:
Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary);
virtual ~Choice(); virtual ~Choice();
YYCC_DEL_CLS_COPY_MOVE(Choice); YYCC_DEF_CLS_COPY_MOVE(Choice);
vType* GetIndex();
public:
virtual NodeType GetNodeType() override;
virtual bool IsConflictWith(AbstractNode* node) override;
protected: protected:
virtual std::string GetHelpSymbol() override; virtual bool IsArgument() override;
virtual bool BeginAccept(const std::string&, ArgumentsMap*) override; virtual const std::set<std::u8string>& GetConflictSet() override;
virtual void EndAccept(ArgumentsMap*) override; virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
std::string m_ChoiceName; std::u8string m_ChoiceName;
std::vector<std::string> m_Vocabulary; std::vector<std::u8string> m_Vocabulary;
bool m_Accepted; std::set<std::u8string> m_ConflictSet;
size_t m_GottenIndex;
}; };
class AbstractArgument : public AbstractNode { class AbstractArgument : public AbstractNode {
friend class Literal;
friend class Choice;
public: public:
AbstractArgument(const char* argname); AbstractArgument(const std::u8string_view& argname);
virtual ~AbstractArgument(); virtual ~AbstractArgument();
YYCC_DEL_CLS_COPY_MOVE(AbstractArgument); YYCC_DEF_CLS_COPY_MOVE(AbstractArgument);
template<class T> // AbstractArgument do not implement BeginConsume().
T GetData() { // Because it involve the detail of data parsing.
return reinterpret_cast<T>(m_ParsedData); // However, other parts are shared by all argument type.
}
public:
virtual NodeType GetNodeType() override;
virtual bool IsConflictWith(AbstractNode* node) override;
protected: protected:
virtual std::string GetHelpSymbol() override; virtual bool IsArgument() override;
virtual bool BeginAccept(const std::string&, ArgumentsMap*) override; virtual const std::set<std::u8string>& GetConflictSet() override;
virtual void EndAccept(ArgumentsMap*) override; virtual std::u8string GetHelpSymbol() override;
// virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
virtual bool BeginParse(const std::string&) = 0; std::u8string m_ArgName;
virtual void EndParse() = 0; std::set<std::u8string> m_ConflictSet;
std::string m_ArgName;
bool m_Accepted;
void* m_ParsedData;
}; };
/** /**
@ -355,4 +436,51 @@ namespace Unvirt::CmdHelper {
virtual void EndParse() override; virtual void EndParse() override;
}; };
}
class CommandParser {
public:
CommandParser();
~CommandParser();
public:
bool Parse(std::deque<std::string>& cmds);
HelpDocument Help();
private:
class RootNode : Nodes::AbstractNode {
public:
RootNode();
virtual ~RootNode();
YYCC_DEF_CLS_COPY_MOVE(RootNode);
protected:
virtual bool IsArgument() override;
virtual const std::set<std::u8string>& 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);
// // Root use special consume and help functions.
// bool RootConsume(std::deque<std::string>&);
// HelpDocument* RootHelp();
//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."); }
//};
} }