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,160 +233,288 @@ 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.");
} m_Execution = fct;
for (auto& pnode : m_Choices) { m_ExecutionDesc = exec_desc;
if (pnode->IsConflictWith(node)) return *this;
throw std::invalid_argument("conflict node.");
}
for (auto& pnode : m_Args) {
if (pnode->IsConflictWith(node))
throw std::invalid_argument("conflict node.");
} }
// add into list AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) {
switch (node->GetNodeType()) { m_Comment = comment;
case NodeType::Literal: return *this;
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; 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) { // Check whether this node is terminal.
if (m_Execution != nullptr) throw std::invalid_argument("duplicated executions."); // If it is, terminate it once.
if (fct == nullptr) throw std::invalid_argument("no function."); if (m_Execution != nullptr) {
m_Execution = fct; doc.Terminate(m_ExecutionDesc);
m_ExecutionDesc = cmt == nullptr ? "" : cmt; }
return this;
}
AbstractNode* AbstractNode::Comment(const char* cmt) { // Then process its children nodes.
if (cmt == nullptr) for (auto& node : m_Nodes) {
throw std::invalid_argument("no comment."); node->Help(doc);
m_Comment = cmt; }
return this;
}
void AbstractNode::Help(HelpDocument* doc) { // Pop self from help document stack
// add self doc.Pop();
std::string symbol(GetHelpSymbol());
doc->Push(symbol, m_Comment);
// check terminal
if (m_Execution != nullptr) {
doc->Terminate(m_ExecutionDesc);
} }
// iterate children bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) {
for (auto& pnode : m_Literals) { // if no data can consume, return
pnode->Help(doc); if (al.empty()) return false;
}
for (auto& pnode : m_Choices) {
pnode->Help(doc);
}
for (auto& pnode : m_Args) {
pnode->Help(doc);
}
// pop self // backup current value
doc->Pop(); 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<std::string>& arglist, ArgumentsMap* argmap) { // pop front for processing child nodes.
// if no data can consume, return al.pop_front();
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();
#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()) {
// this is must be a terminal. // if no more data for parsing.
// check whether we have execution. // this is must be a terminal.
if (m_Execution == nullptr) { // 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; CONSUME_DEFER;
return false; 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 #undef CONSUME_DEFER
} }
#pragma endregion #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<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; }
void Literal::EndConsume(ArgumentsMap& am) {}
#pragma endregion
#pragma region Choice
Choice::Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& 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<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) {
for (size_t i = 0; i < m_Vocabulary.size(); ++i) {
if (cur_cmd == m_Vocabulary[i]) {
am.Add<AMItems::StringItem>(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<Choice*>(node)->m_ChoiceName;
case NodeType::Argument:
return m_ArgName == dynamic_cast<AbstractArgument*>(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<int32_t>(v);
if (m_IntLimit != nullptr && !m_IntLimit(value)) {
return false;
}
m_ParsedData = new IntArgument::vType(value);
return true;
}
void IntArgument::EndParse() {
delete reinterpret_cast<IntArgument::vType*>(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<StringArgument::vType*>(m_ParsedData);
m_ParsedData = nullptr;
}
// Copy from Gamepiaynmo/BallanceModLoader
std::vector<std::string> SplitString(const std::string& str, const std::string& de) {
size_t lpos, pos = 0;
std::vector<std::string> 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<EncodingArgument::vType*>(m_ParsedData);
m_ParsedData = nullptr;
}
#pragma endregion
}
#pragma region Command Root #pragma region Command Root
CommandRoot::CommandRoot() : AbstractNode() {} CommandRoot::CommandRoot() : AbstractNode() {}
@ -436,252 +568,4 @@ namespace Unvirt::CmdHelper {
#pragma endregion #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<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 region Choice
Choice::Choice(const char* argname, const std::initializer_list<std::string>& 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<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) {
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<Choice*>(node)->m_ChoiceName;
case NodeType::Argument:
return m_ArgName == dynamic_cast<AbstractArgument*>(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<int32_t>(v);
if (m_IntLimit != nullptr && !m_IntLimit(value)) {
return false;
}
m_ParsedData = new IntArgument::vType(value);
return true;
}
void IntArgument::EndParse() {
delete reinterpret_cast<IntArgument::vType*>(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<StringArgument::vType*>(m_ParsedData);
m_ParsedData = nullptr;
}
// Copy from Gamepiaynmo/BallanceModLoader
std::vector<std::string> SplitString(const std::string& str, const std::string& de) {
size_t lpos, pos = 0;
std::vector<std::string> 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<EncodingArgument::vType*>(m_ParsedData);
m_ParsedData = nullptr;
}
#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,171 +185,302 @@ namespace Unvirt::CmdHelper {
std::vector<ResultItem> m_Results; std::vector<ResultItem> m_Results;
}; };
class AbstractNode { namespace Nodes {
friend class CommandRoot;
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<std::shared_ptr<AbstractNode>> 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<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;
/**
* @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<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 {
public:
Literal(const std::u8string_view& words);
virtual ~Literal();
YYCC_DEF_CLS_COPY_MOVE(Literal);
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;
std::u8string m_Literal;
std::set<std::u8string> m_ConflictSet;
};
class Choice : public AbstractNode {
public:
using ArgValue_t = size_t;
public:
Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary);
virtual ~Choice();
YYCC_DEF_CLS_COPY_MOVE(Choice);
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;
std::u8string m_ChoiceName;
std::vector<std::u8string> m_Vocabulary;
std::set<std::u8string> 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<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;
std::u8string m_ArgName;
std::set<std::u8string> m_ConflictSet;
};
/**
* @brief Return true mean this value can accept.
*/
using IntLimit = std::function<bool(int32_t)>;
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<std::string>;
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: public:
using ExecutionFct = void(*)(const ArgumentsMap&); CommandParser();
~CommandParser();
public: public:
AbstractNode(); bool Parse(std::deque<std::string>& cmds);
virtual ~AbstractNode(); HelpDocument Help();
YYCC_DEF_CLS_COPY_MOVE(AbstractNode);
AbstractNode* Then(AbstractNode*); private:
AbstractNode* Executes(ExecutionFct, const char* = nullptr); class RootNode : Nodes::AbstractNode {
AbstractNode* Comment(const char*); public:
RootNode();
virtual ~RootNode();
YYCC_DEF_CLS_COPY_MOVE(RootNode);
protected: protected:
void Help(HelpDocument& doc); virtual bool IsArgument() override;
bool Consume(CmdSplitter::Result_t& cmds, ArgumentsMap& am); virtual const std::set<std::u8string>& GetConflictSet() override;
virtual bool IsConflictWith(AbstractNode* node) = 0; virtual std::u8string GetHelpSymbol() override;
virtual bool IsArgument() = 0; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual std::u8string HelpSymbol() = 0; virtual void EndConsume(ArgumentsMap& am) override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) = 0; };
virtual void EndConsume(ArgumentsMap*) = 0; RootNode m_RootNode;
protected:
std::vector<std::unique_ptr<AbstractNode>> m_Nodes;
ExecutionFct m_Execution;
std::string m_ExecutionDesc;
std::string m_Comment;
}; };
class CommandRoot : public AbstractNode { //class CommandRoot : public AbstractNode {
public: //public:
CommandRoot(); // CommandRoot();
virtual ~CommandRoot(); // virtual ~CommandRoot();
YYCC_DEL_CLS_COPY_MOVE(CommandRoot); // YYCC_DEL_CLS_COPY_MOVE(CommandRoot);
// Root use special consume and help functions. // // Root use special consume and help functions.
bool RootConsume(std::deque<std::string>&); // bool RootConsume(std::deque<std::string>&);
HelpDocument* RootHelp(); // HelpDocument* RootHelp();
public: //public:
virtual NodeType GetNodeType() override { throw std::logic_error("Root can not be called."); } //protected:
virtual bool IsConflictWith(AbstractNode*) override { throw std::logic_error("Root can not be called."); } // virtual NodeType GetNodeType() override { throw std::logic_error("Root can not be called."); }
protected: // 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 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 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."); } // 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<std::string>& 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<std::string> 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<class T>
T GetData() {
return reinterpret_cast<T>(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<bool(int32_t)>;
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<std::string>;
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;
};
} }