2023-02-13 10:57:10 +08:00
|
|
|
#include "CmdHelper.hpp"
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-03-03 11:06:26 +08:00
|
|
|
namespace Unvirt::CmdHelper {
|
2023-02-13 10:57:10 +08:00
|
|
|
|
2023-02-13 22:13:30 +08:00
|
|
|
#pragma region CmdSplitter
|
2023-02-13 10:57:10 +08:00
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
const std::deque<std::u8string>& CmdSplitter::GetResult() const {
|
|
|
|
if (!m_ValidResult)
|
|
|
|
throw std::runtime_error("try to get result from an invalid CmdSplitter.");
|
|
|
|
return m_Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CmdSplitter::Convert(const std::u8string& u8cmd) {
|
|
|
|
// Clear variables
|
|
|
|
m_ValidResult = false;
|
|
|
|
m_Result.clear();
|
|
|
|
m_Buffer.clear();
|
|
|
|
m_CurrentChar = u8'\0';
|
|
|
|
m_State = m_PrevState = StateType::SPACE;
|
2023-02-13 10:57:10 +08:00
|
|
|
|
2023-03-03 11:06:26 +08:00
|
|
|
// split
|
2024-08-24 21:27:23 +08:00
|
|
|
for (char8_t c : u8cmd) {
|
|
|
|
m_CurrentChar = c;
|
2023-03-04 11:11:36 +08:00
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
// skip all invalid characters (ascii code unit lower than space char)
|
|
|
|
// thus UTF8 code unit can directly accepted.
|
|
|
|
if (m_CurrentChar < u8' ')
|
2023-03-04 11:11:36 +08:00
|
|
|
continue;
|
2023-02-13 10:57:10 +08:00
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
switch (m_State) {
|
2023-02-13 10:57:10 +08:00
|
|
|
case StateType::SPACE:
|
2023-03-03 11:06:26 +08:00
|
|
|
ProcSpace();
|
2023-02-13 10:57:10 +08:00
|
|
|
break;
|
|
|
|
case StateType::SINGLE:
|
2023-03-03 11:06:26 +08:00
|
|
|
ProcSingle();
|
|
|
|
break;
|
2023-02-13 10:57:10 +08:00
|
|
|
case StateType::DOUBLE:
|
2023-03-03 11:06:26 +08:00
|
|
|
ProcDouble();
|
|
|
|
break;
|
2023-02-13 10:57:10 +08:00
|
|
|
case StateType::ESCAPE:
|
2023-03-03 11:06:26 +08:00
|
|
|
ProcEscape();
|
|
|
|
break;
|
|
|
|
case StateType::NORMAL:
|
|
|
|
ProcNormal();
|
2023-02-13 10:57:10 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// final proc
|
2024-08-24 21:27:23 +08:00
|
|
|
bool is_success = false;
|
|
|
|
switch (m_State) {
|
2023-03-03 11:06:26 +08:00
|
|
|
case StateType::SPACE:
|
2024-08-24 21:27:23 +08:00
|
|
|
is_success = true;
|
2023-03-03 11:06:26 +08:00
|
|
|
break;
|
|
|
|
case StateType::NORMAL:
|
|
|
|
// push the last one
|
2024-08-24 21:27:23 +08:00
|
|
|
m_Result.emplace_back(m_Buffer);
|
|
|
|
is_success = true;
|
2023-03-03 11:06:26 +08:00
|
|
|
break;
|
|
|
|
case StateType::SINGLE:
|
|
|
|
case StateType::DOUBLE:
|
|
|
|
case StateType::ESCAPE:
|
|
|
|
// error
|
2024-08-24 21:27:23 +08:00
|
|
|
is_success = false;
|
2023-03-03 11:06:26 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
default:
|
|
|
|
throw std::runtime_error("unreachable code.");
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
// check success
|
|
|
|
if (is_success) {
|
|
|
|
m_ValidResult = true;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
m_Result.clear();
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
2023-02-13 10:57:10 +08:00
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
void CmdSplitter::ProcSpace() {
|
|
|
|
switch (m_CurrentChar) {
|
|
|
|
case u8'\'':
|
|
|
|
m_State = StateType::SINGLE;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'"':
|
|
|
|
m_State = StateType::DOUBLE;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'\\':
|
|
|
|
m_State = StateType::ESCAPE;
|
|
|
|
m_PrevState = StateType::NORMAL;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8' ':
|
2023-08-27 12:30:12 +08:00
|
|
|
break; // skip blank
|
|
|
|
default:
|
2024-08-24 21:27:23 +08:00
|
|
|
m_Buffer.push_back(m_CurrentChar);
|
|
|
|
m_State = StateType::NORMAL;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
void CmdSplitter::ProcSingle() {
|
|
|
|
switch (m_CurrentChar) {
|
|
|
|
case u8'\'':
|
|
|
|
m_State = StateType::NORMAL;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'"':
|
|
|
|
m_Buffer.push_back('"');
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'\\':
|
|
|
|
m_State = StateType::ESCAPE;
|
|
|
|
m_PrevState = StateType::SINGLE;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8' ':
|
|
|
|
m_Buffer.push_back(u8' ');
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
|
|
|
default:
|
2024-08-24 21:27:23 +08:00
|
|
|
m_Buffer.push_back(m_CurrentChar);
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
void CmdSplitter::ProcDouble() {
|
|
|
|
switch (m_CurrentChar) {
|
|
|
|
case u8'\'':
|
|
|
|
m_Buffer.push_back(u8'\'');
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'"':
|
|
|
|
m_State = StateType::NORMAL;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'\\':
|
|
|
|
m_State = StateType::ESCAPE;
|
|
|
|
m_PrevState = StateType::DOUBLE;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8' ':
|
|
|
|
m_Buffer.push_back(u8' ');
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
|
|
|
default:
|
2024-08-24 21:27:23 +08:00
|
|
|
m_Buffer.push_back(m_CurrentChar);
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
2023-08-27 12:30:12 +08:00
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
void CmdSplitter::ProcEscape() {
|
2023-08-27 12:30:12 +08:00
|
|
|
// add itself
|
2024-08-24 21:27:23 +08:00
|
|
|
m_Buffer.push_back(m_CurrentChar);
|
2023-08-27 12:30:12 +08:00
|
|
|
// restore state
|
2024-08-24 21:27:23 +08:00
|
|
|
m_State = m_PrevState;
|
2023-08-27 12:30:12 +08:00
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
void CmdSplitter::ProcNormal() {
|
|
|
|
switch (m_CurrentChar) {
|
|
|
|
case u8'\'':
|
|
|
|
m_Buffer.push_back(u8'\'');
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'"':
|
|
|
|
m_Buffer.push_back(u8'"');
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8'\\':
|
|
|
|
m_State = StateType::ESCAPE;
|
|
|
|
m_PrevState = StateType::NORMAL;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2024-08-24 21:27:23 +08:00
|
|
|
case u8' ':
|
|
|
|
m_Result.emplace_back(m_Buffer);
|
|
|
|
m_Buffer.clear();
|
|
|
|
m_State = StateType::SPACE;
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
|
|
|
default:
|
2024-08-24 21:27:23 +08:00
|
|
|
m_Buffer.push_back(m_CurrentChar);
|
2023-08-27 12:30:12 +08:00
|
|
|
break;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
}
|
2023-02-13 10:57:10 +08:00
|
|
|
#pragma endregion
|
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
#pragma region Arguments Map
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
#pragma region Help Document
|
|
|
|
|
|
|
|
HelpDocument::HelpDocument() : m_Stack(), m_Results() {}
|
|
|
|
|
|
|
|
HelpDocument::~HelpDocument() {}
|
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
HelpDocument::StackItem::StackItem() : m_Name(), m_Desc() {}
|
|
|
|
|
|
|
|
HelpDocument::StackItem::StackItem(const std::u8string& name, const std::u8string& desc) : m_Name(name), m_Desc(desc) {}
|
|
|
|
|
|
|
|
HelpDocument::ResultItem::ResultItem() : m_CmdDesc(), m_ArgDesc() {}
|
|
|
|
|
|
|
|
HelpDocument::ResultItem::ResultItem(const std::u8string& cmd_desc, const std::deque<StackItem>& arg_desc) :
|
|
|
|
m_CmdDesc(cmd_desc), m_ArgDesc(arg_desc.begin(), arg_desc.end()) {}
|
|
|
|
|
|
|
|
void HelpDocument::Push(const std::u8string& arg_name, const std::u8string& arg_desc) {
|
2023-08-27 14:21:44 +08:00
|
|
|
m_Stack.emplace_back(StackItem { arg_name, arg_desc });
|
|
|
|
}
|
|
|
|
|
|
|
|
void HelpDocument::Pop() {
|
2024-08-24 21:27:23 +08:00
|
|
|
if (m_Stack.empty())
|
|
|
|
throw std::runtime_error("try pop back on an empty help document.");
|
2023-08-27 14:21:44 +08:00
|
|
|
m_Stack.pop_back();
|
|
|
|
}
|
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
void HelpDocument::Terminate(std::u8string& command_desc) {
|
|
|
|
// create new result
|
|
|
|
ResultItem result(command_desc, this->m_Stack);
|
2023-08-27 14:21:44 +08:00
|
|
|
// add into result
|
|
|
|
m_Results.emplace_back(std::move(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
void HelpDocument::Print() {
|
2024-08-24 21:27:23 +08:00
|
|
|
for (auto& cmd : m_Results) {
|
|
|
|
// syntax
|
|
|
|
YYCC::ConsoleHelper::WriteLine(u8"Syntax: ");
|
|
|
|
for (const auto& arg : cmd.m_ArgDesc) {
|
|
|
|
YYCC::ConsoleHelper::Format(u8"%s ", arg.m_Name.c_str());
|
2023-08-27 14:21:44 +08:00
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
YYCC::ConsoleHelper::WriteLine(u8"");
|
|
|
|
// command description
|
|
|
|
if (!cmd.m_CmdDesc.empty()) {
|
|
|
|
YYCC::ConsoleHelper::FormatLine(u8"Description: %s", cmd.m_CmdDesc.c_str());
|
2023-08-27 14:21:44 +08:00
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
// argument description
|
|
|
|
YYCC::ConsoleHelper::WriteLine(u8"Arguments:");
|
|
|
|
for (auto& arg : cmd.m_ArgDesc) {
|
|
|
|
if (!arg.m_Desc.empty()) {
|
|
|
|
YYCC::ConsoleHelper::FormatLine(u8"\t%s: %s", arg.m_Name.c_str(), arg.m_Desc.c_str());
|
2023-08-27 14:21:44 +08:00
|
|
|
}
|
|
|
|
}
|
2024-08-24 21:27:23 +08:00
|
|
|
// space between each commands
|
|
|
|
YYCC::ConsoleHelper::WriteLine(u8"");
|
2023-08-27 14:21:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
#pragma region Abstract Node
|
2023-03-04 11:11:36 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
AbstractNode::AbstractNode() :
|
|
|
|
m_Execution(nullptr), m_Comment(),
|
|
|
|
m_Literals(), m_Choices(), m_Args() {}
|
|
|
|
|
|
|
|
AbstractNode::~AbstractNode() {
|
|
|
|
for (auto& ptr : m_Literals) {
|
|
|
|
delete ptr;
|
|
|
|
}
|
|
|
|
for (auto& ptr : m_Choices) {
|
|
|
|
delete ptr;
|
|
|
|
}
|
|
|
|
for (auto& ptr : m_Args) {
|
|
|
|
delete ptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
AbstractNode* AbstractNode::Then(AbstractNode* node) {
|
2023-08-27 14:21:44 +08:00
|
|
|
// check conflict
|
|
|
|
for (auto& pnode : m_Literals) {
|
2023-08-27 12:30:12 +08:00
|
|
|
if (pnode->IsConflictWith(node))
|
|
|
|
throw std::invalid_argument("conflict node.");
|
|
|
|
}
|
2023-08-27 14:21:44 +08:00
|
|
|
for (auto& pnode : m_Choices) {
|
|
|
|
if (pnode->IsConflictWith(node))
|
|
|
|
throw std::invalid_argument("conflict node.");
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Args) {
|
|
|
|
if (pnode->IsConflictWith(node))
|
|
|
|
throw std::invalid_argument("conflict node.");
|
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// add into list
|
2023-08-27 14:21:44 +08:00
|
|
|
switch (node->GetNodeType()) {
|
2023-08-27 16:51:18 +08:00
|
|
|
case NodeType::Literal:
|
2023-08-27 14:21:44 +08:00
|
|
|
m_Literals.emplace_back(node);
|
|
|
|
break;
|
|
|
|
case NodeType::Choice:
|
|
|
|
m_Choices.emplace_back(node);
|
|
|
|
break;
|
2023-08-27 16:51:18 +08:00
|
|
|
case NodeType::Argument:
|
2023-08-27 14:21:44 +08:00
|
|
|
m_Args.emplace_back(node);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw std::runtime_error("No such node type.");
|
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
return this;
|
2023-03-04 11:11:36 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
AbstractNode* AbstractNode::Executes(ExecutionFct fct, const char* cmt) {
|
2023-08-27 14:21:44 +08:00
|
|
|
if (m_Execution != nullptr) throw std::invalid_argument("duplicated executions.");
|
|
|
|
if (fct == nullptr) throw std::invalid_argument("no function.");
|
2023-08-27 12:30:12 +08:00
|
|
|
m_Execution = fct;
|
2023-08-27 14:21:44 +08:00
|
|
|
m_ExecutionDesc = cmt == nullptr ? "" : cmt;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
AbstractNode* AbstractNode::Comment(const char* cmt) {
|
|
|
|
if (cmt == nullptr)
|
|
|
|
throw std::invalid_argument("no comment.");
|
2023-08-27 12:30:12 +08:00
|
|
|
m_Comment = cmt;
|
|
|
|
return this;
|
2023-03-04 11:11:36 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
void AbstractNode::Help(HelpDocument* doc) {
|
2023-08-27 12:30:12 +08:00
|
|
|
// add self
|
2023-08-27 14:21:44 +08:00
|
|
|
std::string symbol(GetHelpSymbol());
|
|
|
|
doc->Push(symbol, m_Comment);
|
2023-03-04 11:11:36 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// check terminal
|
|
|
|
if (m_Execution != nullptr) {
|
2023-08-27 14:21:44 +08:00
|
|
|
doc->Terminate(m_ExecutionDesc);
|
2023-08-27 12:30:12 +08:00
|
|
|
}
|
2023-03-04 11:11:36 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// iterate children
|
2023-08-27 14:21:44 +08:00
|
|
|
for (auto& pnode : m_Literals) {
|
|
|
|
pnode->Help(doc);
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Choices) {
|
|
|
|
pnode->Help(doc);
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Args) {
|
2023-08-27 12:30:12 +08:00
|
|
|
pnode->Help(doc);
|
|
|
|
}
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// pop self
|
2023-08-27 14:21:44 +08:00
|
|
|
doc->Pop();
|
2023-03-04 11:11:36 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
bool AbstractNode::Consume(std::deque<std::string>& arglist, ArgumentsMap* argmap) {
|
2023-08-27 12:30:12 +08:00
|
|
|
// if no data can consume, return
|
|
|
|
if (arglist.empty()) return false;
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// backup current value
|
|
|
|
std::string cur = arglist.front();
|
|
|
|
// consume self
|
|
|
|
if (!BeginAccept(cur, argmap)) {
|
|
|
|
// fail to consume self. not matched. return
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// pop front for following code
|
|
|
|
arglist.pop_front();
|
|
|
|
|
|
|
|
#define CONSUME_DEFER \
|
|
|
|
arglist.push_front(cur); \
|
|
|
|
EndAccept(argmap);
|
|
|
|
|
|
|
|
if (arglist.empty()) {
|
|
|
|
// this is must be a terminal.
|
|
|
|
// check whether we have execution.
|
|
|
|
if (m_Execution == nullptr) {
|
|
|
|
CONSUME_DEFER;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
m_Execution(argmap);
|
|
|
|
CONSUME_DEFER;
|
|
|
|
return true;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
2023-08-27 12:30:12 +08:00
|
|
|
} else {
|
|
|
|
// have following command, try match them
|
|
|
|
// iterate literal and argument to check terminal
|
2023-08-27 14:21:44 +08:00
|
|
|
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) {
|
2023-08-27 12:30:12 +08:00
|
|
|
if (pnode->Consume(arglist, argmap)) {
|
|
|
|
CONSUME_DEFER;
|
|
|
|
return true;
|
|
|
|
}
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// if still nothing to match, return false
|
|
|
|
CONSUME_DEFER;
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
#undef CONSUME_DEFER
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
}
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
#pragma endregion
|
2023-03-04 11:11:36 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
#pragma region Command Root
|
|
|
|
|
|
|
|
CommandRoot::CommandRoot() : AbstractNode() {}
|
|
|
|
|
|
|
|
CommandRoot::~CommandRoot() {}
|
|
|
|
|
|
|
|
bool CommandRoot::RootConsume(std::deque<std::string>& arglist) {
|
|
|
|
// if no data can consume, return
|
|
|
|
if (arglist.empty()) return false;
|
|
|
|
|
|
|
|
// create a argument map
|
|
|
|
ArgumentsMap amap;
|
|
|
|
|
|
|
|
// and we only just need iterate all children
|
|
|
|
for (auto& pnode : m_Literals) {
|
|
|
|
if (pnode->Consume(arglist, &amap)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Choices) {
|
|
|
|
if (pnode->Consume(arglist, &amap)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Args) {
|
|
|
|
if (pnode->Consume(arglist, &amap)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// no matched
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
HelpDocument* CommandRoot::RootHelp() {
|
|
|
|
HelpDocument* doc = new HelpDocument();
|
|
|
|
|
|
|
|
// we only just need iterate all children
|
|
|
|
for (auto& pnode : m_Literals) {
|
|
|
|
pnode->Help(doc);
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Choices) {
|
|
|
|
pnode->Help(doc);
|
|
|
|
}
|
|
|
|
for (auto& pnode : m_Args) {
|
|
|
|
pnode->Help(doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
|
|
|
|
#pragma region Literal
|
|
|
|
|
2024-08-24 21:27:23 +08:00
|
|
|
Literal::Literal(const char* words) :
|
|
|
|
AbstractNode(),
|
2023-08-27 14:21:44 +08:00
|
|
|
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(),
|
2024-08-24 21:27:23 +08:00
|
|
|
m_ArgName(argname == nullptr ? "" : argname),
|
2023-08-27 14:21:44 +08:00
|
|
|
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
|
2023-03-04 11:11:36 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
bool IntArgument::BeginParse(const std::string& val) {
|
|
|
|
char* pend = nullptr;
|
|
|
|
errno = 0;
|
|
|
|
int64_t v = std::strtoll(val.c_str(), &pend, 10);
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
if (pend == val.c_str() || errno == ERANGE) return false;
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
// check limit
|
|
|
|
int32_t value = static_cast<int32_t>(v);
|
|
|
|
if (m_IntLimit != nullptr && !m_IntLimit(value)) {
|
|
|
|
return false;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
m_ParsedData = new IntArgument::vType(value);
|
2023-08-27 12:30:12 +08:00
|
|
|
return true;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
void IntArgument::EndParse() {
|
2023-08-27 14:21:44 +08:00
|
|
|
delete reinterpret_cast<IntArgument::vType*>(m_ParsedData);
|
2023-08-27 12:30:12 +08:00
|
|
|
m_ParsedData = nullptr;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
bool StringArgument::BeginParse(const std::string& strl) {
|
|
|
|
// string always accept every text
|
2023-08-27 14:21:44 +08:00
|
|
|
m_ParsedData = new StringArgument::vType(strl);
|
2023-08-27 12:30:12 +08:00
|
|
|
return true;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
void StringArgument::EndParse() {
|
2023-08-27 14:21:44 +08:00
|
|
|
delete reinterpret_cast<StringArgument::vType*>(m_ParsedData);
|
2023-08-27 12:30:12 +08:00
|
|
|
m_ParsedData = nullptr;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
// 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;
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
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;
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
lpos = str.find_first_not_of(de, pos);
|
2023-08-27 12:30:12 +08:00
|
|
|
}
|
2023-03-04 00:13:03 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
if (pos != std::string::npos)
|
|
|
|
res.push_back("");
|
2023-03-04 00:13:03 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
return res;
|
|
|
|
}
|
2023-03-04 00:13:03 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
bool EncodingArgument::BeginParse(const std::string& strl) {
|
|
|
|
// encoding always accept every text
|
|
|
|
m_ParsedData = new EncodingArgument::vType(SplitString(strl, ","));
|
|
|
|
return true;
|
|
|
|
}
|
2023-03-03 11:06:26 +08:00
|
|
|
|
2023-08-27 14:21:44 +08:00
|
|
|
void EncodingArgument::EndParse() {
|
|
|
|
delete reinterpret_cast<EncodingArgument::vType*>(m_ParsedData);
|
|
|
|
m_ParsedData = nullptr;
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
2023-08-27 12:30:12 +08:00
|
|
|
#pragma endregion
|
2023-03-03 16:05:32 +08:00
|
|
|
|
2023-03-03 11:06:26 +08:00
|
|
|
}
|