fix: fix CmdHelper but still not finished
This commit is contained in:
parent
0db8007fcb
commit
bd96f26cfd
|
@ -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) {
|
||||||
// Push self symbol to help document stack.
|
// If this node is not root node
|
||||||
doc.Push(GetHelpSymbol(), m_Comment);
|
if (!this->IsRootNode()) {
|
||||||
|
// Push self symbol to help document stack.
|
||||||
|
if (!this->IsRootNode()) {
|
||||||
|
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
|
||||||
doc.Pop();
|
// if this node is not root node
|
||||||
|
if (!this->IsRootNode()) {
|
||||||
|
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()) {
|
||||||
if (!BeginConsume(cur_cmd, am)) {
|
// Backup the top item in command stack for personal consume.
|
||||||
// fail to consume self. not matched. return
|
cur_cmd = al.front();
|
||||||
return false;
|
// Try to consume it for self.
|
||||||
|
if (!BeginConsume(cur_cmd, am)) {
|
||||||
|
// Fail to consume self.
|
||||||
|
// It means that this command is not matched with self.
|
||||||
|
// Return directly.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Yes, command matched, we try consume it for child nodes.
|
||||||
|
// Pop the top item of command stack for child nodes processing.
|
||||||
|
al.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
// pop front for processing child nodes.
|
// Define free function if we are not the root node.
|
||||||
al.pop_front();
|
// 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() {
|
||||||
bool AbstractArgument::IsConflictWith(AbstractNode* node) {
|
return YYCC::StringHelper::Printf(u8"<%s>", m_ArgumentName.c_str());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//bool AbstractArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return false; }
|
||||||
|
void AbstractArgument::EndConsume(ArgumentsMap& am) { am.Remove(m_ArgumentName); }
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region Argument Detail Impl
|
#pragma region Arithmetic Argument
|
||||||
|
|
||||||
bool IntArgument::BeginParse(const std::string& val) {
|
// It's a template class
|
||||||
char* pend = nullptr;
|
// We are forced to implement it in header file.
|
||||||
errno = 0;
|
|
||||||
int64_t v = std::strtoll(val.c_str(), &pend, 10);
|
|
||||||
|
|
||||||
if (pend == val.c_str() || errno == ERANGE) return false;
|
#pragma endregion
|
||||||
|
|
||||||
// check limit
|
#pragma region String Argument
|
||||||
int32_t value = static_cast<int32_t>(v);
|
|
||||||
if (m_IntLimit != nullptr && !m_IntLimit(value)) {
|
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;
|
||||||
}
|
// accept
|
||||||
|
am.Add<ArgValue_t>(m_ArgumentName, cur_cmd);
|
||||||
m_ParsedData = new IntArgument::vType(value);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntArgument::EndParse() {
|
#pragma endregion
|
||||||
delete reinterpret_cast<IntArgument::vType*>(m_ParsedData);
|
|
||||||
m_ParsedData = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StringArgument::BeginParse(const std::string& strl) {
|
#pragma region Encoding List Argument
|
||||||
// string always accept every text
|
|
||||||
m_ParsedData = new StringArgument::vType(strl);
|
bool EncodingListArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) {
|
||||||
|
// split given argument
|
||||||
|
std::vector<std::u8string> encs = YYCC::StringHelper::Split(cur_cmd, u8",");
|
||||||
|
// add into map
|
||||||
|
am.Add<ArgValue_t>(m_ArgumentName, encs);
|
||||||
return true;
|
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 CommandParser::Help() {
|
||||||
HelpDocument* doc = new HelpDocument();
|
// Create help docuemnt
|
||||||
|
HelpDocument doc;
|
||||||
// we only just need iterate all children
|
// use node tree to fill help document.
|
||||||
for (auto& pnode : m_Literals) {
|
m_RootNode.Help(doc);
|
||||||
pnode->Help(doc);
|
// return result.
|
||||||
}
|
|
||||||
for (auto& pnode : m_Choices) {
|
|
||||||
pnode->Help(doc);
|
|
||||||
}
|
|
||||||
for (auto& pnode : m_Args) {
|
|
||||||
pnode->Help(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Nodes::RootNode& CommandParser::GetRoot() {
|
||||||
|
return m_RootNode;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -375,65 +439,77 @@ namespace Unvirt::CmdHelper {
|
||||||
AbstractArgument(const std::u8string_view& argname);
|
AbstractArgument(const std::u8string_view& argname);
|
||||||
virtual ~AbstractArgument();
|
virtual ~AbstractArgument();
|
||||||
YYCC_DEF_CLS_COPY_MOVE(AbstractArgument);
|
YYCC_DEF_CLS_COPY_MOVE(AbstractArgument);
|
||||||
|
|
||||||
// AbstractArgument do not implement BeginConsume().
|
// AbstractArgument do not implement BeginConsume().
|
||||||
// Because it involve the detail of data parsing.
|
// Because it involve the detail of data parsing.
|
||||||
// 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 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user