fix: fix CmdHelper but still not finished

This commit is contained in:
yyc12345 2024-08-25 22:27:17 +08:00
parent 0db8007fcb
commit bd96f26cfd
2 changed files with 309 additions and 243 deletions

View File

@ -1,4 +1,5 @@
#include "CmdHelper.hpp" #include "CmdHelper.hpp"
#include <algorithm>
namespace Unvirt::CmdHelper { namespace Unvirt::CmdHelper {
@ -231,6 +232,43 @@ namespace Unvirt::CmdHelper {
} }
} }
#pragma endregion
#pragma region Conflict Set
ConflictSet::ConflictSet() : m_ConflictSet() {}
ConflictSet::~ConflictSet() {}
void ConflictSet::AddLiteral(const std::u8string_view& value) {
if (value.empty())
throw std::invalid_argument("try to insert empty item to conflict set.");
auto result = m_ConflictSet.emplace(u8"literal:" + std::u8string(value));
if (!result.second)
throw std::runtime_error("try to insert duplicated item in conflict set.");
}
void ConflictSet::AddArgument(const std::u8string_view& value) {
if (value.empty())
throw std::invalid_argument("try to insert empty item to conflict set.");
auto result = m_ConflictSet.emplace(u8"argument:" + std::u8string(value));
if (!result.second)
throw std::runtime_error("try to insert duplicated item in conflict set.");
}
bool ConflictSet::IsConflictWith(const ConflictSet& rhs) const {
// create a cache to store computed intersection
std::vector<std::u8string> intersection;
// compute intersection
std::set_intersection(
this->m_ConflictSet.begin(), this->m_ConflictSet.end(),
rhs.m_ConflictSet.begin(), rhs.m_ConflictSet.begin(),
std::back_inserter(intersection)
);
// check whether it is empty intersection
return !intersection.empty();
}
#pragma endregion #pragma endregion
namespace Nodes { namespace Nodes {
@ -243,6 +281,8 @@ namespace Unvirt::CmdHelper {
AbstractNode::~AbstractNode() {} AbstractNode::~AbstractNode() {}
AbstractNode& AbstractNode::Executes(FctExecution_t fct, const std::u8string_view& exec_desc) { AbstractNode& AbstractNode::Executes(FctExecution_t fct, const std::u8string_view& exec_desc) {
if (this->IsRootNode())
throw std::logic_error("root node should not have execution.");
if (m_Execution != nullptr) if (m_Execution != nullptr)
throw std::invalid_argument("you should not assign execution multiuple times."); throw std::invalid_argument("you should not assign execution multiuple times.");
if (fct == nullptr) if (fct == nullptr)
@ -253,52 +293,76 @@ namespace Unvirt::CmdHelper {
} }
AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) { AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) {
if (this->IsRootNode())
throw std::logic_error("root node should not have comment.");
m_Comment = comment; m_Comment = comment;
return *this; return *this;
} }
void AbstractNode::Help(HelpDocument& doc) { void AbstractNode::Help(HelpDocument& doc) {
// If this node is not root node
if (!this->IsRootNode()) {
// Push self symbol to help document stack. // Push self symbol to help document stack.
if (!this->IsRootNode()) {
doc.Push(GetHelpSymbol(), m_Comment); doc.Push(GetHelpSymbol(), m_Comment);
}
// Check whether this node is terminal. // Check whether this node is terminal.
// If it is, terminate it once. // If it is, terminate it once.
if (m_Execution != nullptr) { if (m_Execution != nullptr) {
doc.Terminate(m_ExecutionDesc); doc.Terminate(m_ExecutionDesc);
} }
}
// Then process its children nodes. // Then process its children nodes (both root node and common node).
for (auto& node : m_Nodes) { for (auto& node : m_Nodes) {
node->Help(doc); node->Help(doc);
} }
// Pop self from help document stack // Pop self from help document stack
// if this node is not root node
if (!this->IsRootNode()) {
doc.Pop(); doc.Pop();
} }
}
bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) { bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) {
// if no data can consume, return // If no command to be consumed, return directly.
if (al.empty()) return false; if (al.empty()) return false;
// backup current value // Process for self if we are not root node
std::u8string cur_cmd = al.front(); std::u8string cur_cmd;
// consume self if (!this->IsRootNode()) {
// Backup the top item in command stack for personal consume.
cur_cmd = al.front();
// Try to consume it for self.
if (!BeginConsume(cur_cmd, am)) { if (!BeginConsume(cur_cmd, am)) {
// fail to consume self. not matched. return // Fail to consume self.
// It means that this command is not matched with self.
// Return directly.
return false; return false;
} }
// Yes, command matched, we try consume it for child nodes.
// pop front for processing child nodes. // Pop the top item of command stack for child nodes processing.
al.pop_front(); al.pop_front();
}
// Define free function if we are not the root node.
// Because we just pop the top item of command stack.
#define CONSUME_DEFER \ #define CONSUME_DEFER \
if (!this->IsRootNode()) { \
al.emplace_front(cur_cmd); \ al.emplace_front(cur_cmd); \
EndConsume(am); EndConsume(am); \
}
if (al.empty()) { if (al.empty()) {
// if no more data for parsing. // Root node do not have execution, return false directly
// this is must be a terminal. if (this->IsRootNode()) {
// check whether we have execution. CONSUME_DEFER;
return false;
}
// If no more data for parsing, this is must be a terminal.
// Check whether we have execution if we are not root node.
if (m_Execution == nullptr) { if (m_Execution == nullptr) {
CONSUME_DEFER; CONSUME_DEFER;
return false; return false;
@ -308,9 +372,9 @@ namespace Unvirt::CmdHelper {
return true; return true;
} }
} else { } else {
// still have data to be parsed. try to match them. // Command stack still item to be consumed.
// iterate node list to find the real terminal // To consume them, we need iterate node list and find the real terminal.
// however, we need iterate literal and choice first, the iterate argument. // However, we need iterate literal and choice first, the iterate argument.
for (auto& node : m_Nodes) { for (auto& node : m_Nodes) {
if (node->IsArgument()) continue; if (node->IsArgument()) continue;
if (node->Consume(al, am)) { if (node->Consume(al, am)) {
@ -326,7 +390,9 @@ namespace Unvirt::CmdHelper {
} }
} }
// if still nothing to match, return false // If all node can not consume it,
// it means that this command can not match this node tree.
// Return false directly.
CONSUME_DEFER; CONSUME_DEFER;
return false; return false;
} }
@ -337,18 +403,35 @@ namespace Unvirt::CmdHelper {
#pragma endregion #pragma endregion
#pragma region Root Node
RootNode::RootNode() : AbstractNode() {}
RootNode::~RootNode() {}
bool RootNode::IsRootNode() { return true; }
bool RootNode::IsArgument() { throw std::logic_error("this function is not allowed on root function."); }
const ConflictSet& RootNode::GetConflictSet() { throw std::logic_error("this function is not allowed on root function."); }
std::u8string RootNode::GetHelpSymbol() { throw std::logic_error("this function is not allowed on root function."); }
bool RootNode::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { throw std::logic_error("this function is not allowed on root function."); }
void RootNode::EndConsume(ArgumentsMap& am) { throw std::logic_error("this function is not allowed on root function."); }
#pragma endregion
#pragma region Literal #pragma region Literal
Literal::Literal(const std::u8string_view& words) : Literal::Literal(const std::u8string_view& words) :
AbstractNode(), m_Literal(words), m_ConflictSet { m_Literal } { AbstractNode(), m_Literal(words), m_ConflictSet() {
// check argument
if (words.empty()) if (words.empty())
throw std::invalid_argument("The word of literal node should not be empty."); throw std::invalid_argument("The word of literal node should not be empty.");
// set conflict set
m_ConflictSet.AddLiteral(m_Literal);
} }
Literal::~Literal() {} Literal::~Literal() {}
bool Literal::IsRootNode() { return false; }
bool Literal::IsArgument() { return false; } bool Literal::IsArgument() { return false; }
const std::set<std::u8string>& Literal::GetConflictSet() { return m_ConflictSet; } const ConflictSet& Literal::GetConflictSet() { return m_ConflictSet; }
std::u8string Literal::GetHelpSymbol() { return m_Literal; } std::u8string Literal::GetHelpSymbol() { return m_Literal; }
bool Literal::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return cur_cmd == m_Literal; } bool Literal::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return cur_cmd == m_Literal; }
void Literal::EndConsume(ArgumentsMap& am) {} void Literal::EndConsume(ArgumentsMap& am) {}
@ -366,15 +449,20 @@ namespace Unvirt::CmdHelper {
throw std::invalid_argument("Choice argument name should not be empty."); throw std::invalid_argument("Choice argument name should not be empty.");
if (m_Vocabulary.size() < 2u) if (m_Vocabulary.size() < 2u)
throw std::invalid_argument("Too less vocabulary for choice. At least 2 items."); throw std::invalid_argument("Too less vocabulary for choice. At least 2 items.");
std::set<std::u8string> vocabulary_set(m_Vocabulary.begin(), m_Vocabulary.end());
if (vocabulary_set.size() != m_Vocabulary.size())
throw std::invalid_argument("Vocabulary of choice should not have duplicated items.");
// init conflict set // init conflict set
m_ConflictSet.insert(m_ChoiceName); m_ConflictSet.AddArgument(m_ChoiceName);
m_ConflictSet.insert(m_Vocabulary.begin(), m_Vocabulary.end()); for (const auto& word : m_Vocabulary) {
m_ConflictSet.AddLiteral(word);
}
} }
Choice::~Choice() {} Choice::~Choice() {}
bool Choice::IsRootNode() { return false; }
bool Choice::IsArgument() { return false; } bool Choice::IsArgument() { return false; }
const std::set<std::u8string>& Choice::GetConflictSet() { return m_ConflictSet; } const ConflictSet& Choice::GetConflictSet() { return m_ConflictSet; }
std::u8string Choice::GetHelpSymbol() { std::u8string Choice::GetHelpSymbol() {
return YYCC::StringHelper::Printf(u8"[%s]", return YYCC::StringHelper::Printf(u8"[%s]",
YYCC::StringHelper::Join(m_Vocabulary, u8" | ").c_str() YYCC::StringHelper::Join(m_Vocabulary, u8" | ").c_str()
@ -383,7 +471,7 @@ namespace Unvirt::CmdHelper {
bool Choice::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { bool Choice::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) {
for (size_t i = 0; i < m_Vocabulary.size(); ++i) { for (size_t i = 0; i < m_Vocabulary.size(); ++i) {
if (cur_cmd == m_Vocabulary[i]) { if (cur_cmd == m_Vocabulary[i]) {
am.Add<AMItems::StringItem>(m_ChoiceName, cur_cmd); am.Add<ArgValue_t>(m_ChoiceName, i);
return true; return true;
} }
} }
@ -397,175 +485,89 @@ namespace Unvirt::CmdHelper {
AbstractArgument::AbstractArgument(const std::u8string_view& argname) : AbstractArgument::AbstractArgument(const std::u8string_view& argname) :
AbstractNode(), AbstractNode(),
m_ArgName(argname == nullptr ? "" : argname), m_ArgumentName(argname), m_ConflictSet() {
m_Accepted(false), m_ParsedData(nullptr) { // check argument
if (argname == nullptr || m_ArgName.empty()) if (argname.empty())
throw std::invalid_argument("Invalid argument name."); throw std::invalid_argument("Argument name should not be empty.");
// setup conflict set
m_ConflictSet.AddArgument(m_ArgumentName);
} }
AbstractArgument::~AbstractArgument() {} AbstractArgument::~AbstractArgument() {}
NodeType AbstractArgument::GetNodeType() { bool AbstractArgument::IsRootNode() { return false; }
return NodeType::Argument; bool AbstractArgument::IsArgument() { return true; }
const ConflictSet& AbstractArgument::GetConflictSet() { return m_ConflictSet; }
std::u8string AbstractArgument::GetHelpSymbol() {
return YYCC::StringHelper::Printf(u8"<%s>", m_ArgumentName.c_str());
} }
//bool AbstractArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return false; }
void AbstractArgument::EndConsume(ArgumentsMap& am) { am.Remove(m_ArgumentName); }
bool AbstractArgument::IsConflictWith(AbstractNode* node) { #pragma endregion
switch (node->GetNodeType()) {
case NodeType::Literal: #pragma region Arithmetic Argument
// It's a template class
// We are forced to implement it in header file.
#pragma endregion
#pragma region String Argument
bool StringArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) {
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(cur_cmd))
return false; return false;
case NodeType::Choice: // accept
return m_ArgName == dynamic_cast<Choice*>(node)->m_ChoiceName; am.Add<ArgValue_t>(m_ArgumentName, cur_cmd);
case NodeType::Argument: return true;
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 endregion
#pragma region Argument Detail Impl #pragma region Encoding List Argument
bool IntArgument::BeginParse(const std::string& val) { bool EncodingListArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) {
char* pend = nullptr; // split given argument
errno = 0; std::vector<std::u8string> encs = YYCC::StringHelper::Split(cur_cmd, u8",");
int64_t v = std::strtoll(val.c_str(), &pend, 10); // add into map
am.Add<ArgValue_t>(m_ArgumentName, encs);
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; 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 endregion
} }
#pragma region Command Root #pragma region Command Parser
CommandRoot::CommandRoot() : AbstractNode() {} CommandParser::CommandParser() {}
CommandRoot::~CommandRoot() {} CommandParser::~CommandParser() {}
bool CommandRoot::RootConsume(std::deque<std::string>& arglist) { bool CommandParser::Parse(const CmdSplitter::Result_t& cmds) {
// if no data can consume, return // Create a copy of given command
if (arglist.empty()) return false; CmdSplitter::Result_t al(cmds);
// Create argument map
ArgumentsMap am;
// create a argument map // Call root node Consume function and return its result.
ArgumentsMap amap; return m_RootNode.Consume(al, am);
// 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);
} }
HelpDocument CommandParser::Help() {
// Create help docuemnt
HelpDocument doc;
// use node tree to fill help document.
m_RootNode.Help(doc);
// return result.
return doc; return doc;
} }
Nodes::RootNode& CommandParser::GetRoot() {
return m_RootNode;
}
#pragma endregion #pragma endregion
} }

View File

@ -7,7 +7,6 @@
#include <map> #include <map>
#include <set> #include <set>
#include <functional> #include <functional>
#include <algorithm>
#include <initializer_list> #include <initializer_list>
#include <type_traits> #include <type_traits>
#include <stdexcept> #include <stdexcept>
@ -129,7 +128,7 @@ namespace Unvirt::CmdHelper {
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<AMItems::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 std::u8string_view& key) const {
// 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");
@ -185,10 +184,55 @@ namespace Unvirt::CmdHelper {
std::vector<ResultItem> m_Results; std::vector<ResultItem> m_Results;
}; };
class ConflictSet {
public:
ConflictSet();
~ConflictSet();
YYCC_DEF_CLS_COPY_MOVE(ConflictSet);
public:
/**
* @brief Add literal item into conflict set.
* @param[in] value Literal item.
* @remarks
* \li Literal item is the string input in command line.
* \li The word in Literal, and the vocabulary in Choice should be put by this function.
* \li Added item will add \c literal: prefix to make it in literal scope,
* so that it will not be compared with argument name items.
* Because we allow 2 literal item and argument name item have same name.
*/
void AddLiteral(const std::u8string_view& value);
/**
* @brief Add argument name item into conflict
* @param[in] value Argument name item.
* @remarks
* \li Argument name item is the key name put in ArgumentsMap.
* \li The argument name in Choice and Argument should be put by this function.
* \li Added item will add \c argument: prefix to make it in argument name scope,
* so that it will not be compared with literal items.
* Because we allow 2 literal item and argument name item have same name.
*/
void AddArgument(const std::u8string_view& value);
/**
* @brief Check whether this set is conflict with another.
* @param[in] rhs The set to be compared.
* @return True if they have conflict.
* @remarks
* This function simply compute the intersection of two set.
* If the result is not empty, it means that there is a conflict.
*/
bool IsConflictWith(const ConflictSet& rhs) const;
protected:
std::set<std::u8string> m_ConflictSet;
};
// Forward declaration of CommandParser for Nodes::RootNode
class CommandParser;
namespace Nodes { namespace Nodes {
class AbstractNode { class AbstractNode {
friend class CommandRoot;
public: public:
using FctExecution_t = void(*)(const ArgumentsMap&); using FctExecution_t = void(*)(const ArgumentsMap&);
@ -220,9 +264,17 @@ namespace Unvirt::CmdHelper {
bool Consume(CmdSplitter::Result_t& al, ArgumentsMap& am); bool Consume(CmdSplitter::Result_t& al, ArgumentsMap& am);
protected: protected:
/**
* @brief Check whether current node is root node.
* @return True if it is, otherwise false.
* @remarks
* This function usually return false.
* It only return true when using special node called root node with CommandParser.
*/
virtual bool IsRootNode() = 0;
/** /**
* @brief Check whether current node is argument. * @brief Check whether current node is argument.
* @return True if it is. * @return True if it is, otherwise false.
* @remakrs * @remakrs
* \li Sub-class must implement this function. * \li Sub-class must implement this function.
* \li This function is used internally because when consuming nodes, * \li This function is used internally because when consuming nodes,
@ -248,7 +300,7 @@ namespace Unvirt::CmdHelper {
* \li Choice: Put its vocabulary and associated argument name in set. * \li Choice: Put its vocabulary and associated argument name in set.
* \li Argument: Put its argument name in set. * \li Argument: Put its argument name in set.
*/ */
virtual const std::set<std::u8string>& GetConflictSet() = 0; virtual const ConflictSet& GetConflictSet() = 0;
/** /**
* @brief Get the string presented in syntax part in help messages. * @brief Get the string presented in syntax part in help messages.
* @return The string presented in syntax part in help messages. * @return The string presented in syntax part in help messages.
@ -292,24 +344,18 @@ namespace Unvirt::CmdHelper {
* @param[in] node The node instance to be added. * @param[in] node The node instance to be added.
* @return Return self for chain calling. * @return Return self for chain calling.
*/ */
template<class _Ty, std::enable_if_t<std::is_base_of_v<AbstractNode, _Ty>, int> = 0> template<class _Ty, std::enable_if_t<std::is_base_of_v<AbstractNode, _Ty> && !std::is_same_v<AbstractNode, _Ty>, int> = 0>
AbstractNode& Then(_Ty&& node) { AbstractNode& Then(_Ty&& node) {
// create node first // create node first
auto new_node = std::make_shared<_Ty>(std::forward<_Types>(node)); auto new_node = std::make_shared<_Ty>(std::forward<_Ty>(node));
// check root node.
if (new_node->IsRootNode())
throw std::invalid_argument("root node should not be inserted as child node.");
// check conflict // check conflict
std::vector<std::u8string> intersection;
const auto& new_node_set = new_node->GetConflictSet(); const auto& new_node_set = new_node->GetConflictSet();
for (auto& node : mNodes) { for (auto& node : m_Nodes) {
const auto& node_set = node->GetConflictSet(); const auto& node_set = node->GetConflictSet();
// compute intersection if (new_node_set.IsConflictWith(node_set))
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."); throw std::invalid_argument("try to add a conflict node. please check your code.");
} }
// add into list // add into list
@ -333,6 +379,22 @@ namespace Unvirt::CmdHelper {
}; };
class RootNode : public AbstractNode {
friend class CommandParser;
public:
RootNode();
virtual ~RootNode();
YYCC_DEF_CLS_COPY_MOVE(RootNode);
protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override;
virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
};
class Literal : public AbstractNode { class Literal : public AbstractNode {
public: public:
Literal(const std::u8string_view& words); Literal(const std::u8string_view& words);
@ -340,34 +402,36 @@ namespace Unvirt::CmdHelper {
YYCC_DEF_CLS_COPY_MOVE(Literal); YYCC_DEF_CLS_COPY_MOVE(Literal);
protected: protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override; virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override; virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override; virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override; virtual void EndConsume(ArgumentsMap& am) override;
std::u8string m_Literal; std::u8string m_Literal;
std::set<std::u8string> m_ConflictSet; ConflictSet m_ConflictSet;
}; };
class Choice : public AbstractNode { class Choice : public AbstractNode {
public: public:
using ArgValue_t = size_t; using ArgValue_t = AMItems::ArithmeticItem<size_t>;
public: public:
Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary); Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary);
virtual ~Choice(); virtual ~Choice();
YYCC_DEF_CLS_COPY_MOVE(Choice); YYCC_DEF_CLS_COPY_MOVE(Choice);
protected: protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override; virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override; virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override; virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override; virtual void EndConsume(ArgumentsMap& am) override;
std::u8string m_ChoiceName; std::u8string m_ChoiceName;
std::vector<std::u8string> m_Vocabulary; std::vector<std::u8string> m_Vocabulary;
std::set<std::u8string> m_ConflictSet; ConflictSet m_ConflictSet;
}; };
class AbstractArgument : public AbstractNode { class AbstractArgument : public AbstractNode {
@ -381,59 +445,71 @@ namespace Unvirt::CmdHelper {
// However, other parts are shared by all argument type. // However, other parts are shared by all argument type.
protected: protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override; virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override; virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override; virtual std::u8string GetHelpSymbol() override;
// virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override; //virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override; virtual void EndConsume(ArgumentsMap& am) override;
std::u8string m_ArgName; std::u8string m_ArgumentName;
std::set<std::u8string> m_ConflictSet; ConflictSet m_ConflictSet;
}; };
/** template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
* @brief Return true mean this value can accept. class ArithmeticArgument : public AbstractArgument {
*/
using IntLimit = std::function<bool(int32_t)>;
class IntArgument : public AbstractArgument {
public: public:
using vType = int32_t; using ArgValue_t = AMItems::ArithmeticItem<_Ty>;
IntArgument(const char* argname, IntLimit limit = nullptr) : using Constraint_t = YYCC::Constraints::Constraint<_Ty>;
AbstractArgument(argname), m_IntLimit(limit) {} public:
virtual ~IntArgument() {} ArithmeticArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}) :
YYCC_DEL_CLS_COPY_MOVE(IntArgument); AbstractArgument(argname), m_Constraint(constraint) {}
virtual ~ArithmeticArgument() {}
YYCC_DEF_CLS_COPY_MOVE(ArithmeticArgument);
protected: protected:
virtual bool BeginParse(const std::string&) override; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override {
virtual void EndParse() override; // try parse
_Ty result;
IntLimit m_IntLimit; if (!YYCC::ParserHelper::TryParse<_Ty>(cur_cmd, result))
return false;
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(result))
return false;
// okey
am.Add<ArgValue_t>(m_ArgumentName, result);
return true;
}
protected:
Constraint_t m_Constraint;
}; };
class StringArgument : public AbstractArgument { class StringArgument : public AbstractArgument {
public: public:
using vType = std::string; using ArgValue_t = AMItems::StringItem;
StringArgument(const char* argname) : using Constraint_t = YYCC::Constraints::Constraint<std::u8string>;
AbstractArgument(argname) {} public:
StringArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}) :
AbstractArgument(argname), m_Constraint(constraint) {}
virtual ~StringArgument() {} virtual ~StringArgument() {}
YYCC_DEL_CLS_COPY_MOVE(StringArgument); YYCC_DEF_CLS_COPY_MOVE(StringArgument);
protected: protected:
virtual bool BeginParse(const std::string&) override; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndParse() override; Constraint_t m_Constraint;
}; };
class EncodingArgument : public AbstractArgument { class EncodingListArgument : public AbstractArgument {
public: public:
using vType = std::vector<std::string>; using ArgValue_t = AMItems::StringArrayItem;
EncodingArgument(const char* argname) : public:
EncodingListArgument(const std::u8string_view& argname) :
AbstractArgument(argname) {} AbstractArgument(argname) {}
virtual ~EncodingArgument() {} virtual ~EncodingListArgument() {}
YYCC_DEL_CLS_COPY_MOVE(EncodingArgument); YYCC_DEF_CLS_COPY_MOVE(EncodingListArgument);
protected: protected:
virtual bool BeginParse(const std::string&) override; virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndParse() override;
}; };
} }
@ -444,24 +520,12 @@ namespace Unvirt::CmdHelper {
~CommandParser(); ~CommandParser();
public: public:
bool Parse(std::deque<std::string>& cmds); bool Parse(const CmdSplitter::Result_t& cmds);
HelpDocument Help(); HelpDocument Help();
Nodes::RootNode& GetRoot();
private: private:
class RootNode : Nodes::AbstractNode { Nodes::RootNode m_RootNode;
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 { //class CommandRoot : public AbstractNode {