fix: update CmdHelper in Unvirt but not finished
This commit is contained in:
parent
88ce33c358
commit
0db8007fcb
|
@ -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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user