refactor: finish CmdHelper refactor in Unvirt
This commit is contained in:
@@ -1,574 +1,453 @@
|
|||||||
#include "CmdHelper.hpp"
|
#include "CmdHelper.hpp"
|
||||||
#include <VTAll.hpp>
|
#include <yycc/carton/lexer61.hpp>
|
||||||
#include <algorithm>
|
#include <yycc/num/parse.hpp>
|
||||||
|
|
||||||
namespace Unvirt::CmdHelper {
|
namespace Unvirt::CmdHelper {
|
||||||
|
|
||||||
#pragma region CmdSplitter
|
#pragma region Enums Parser
|
||||||
|
|
||||||
const std::deque<std::u8string>& CmdSplitter::GetResult() const {
|
static std::optional<LoadStage> ParseLoadStage(const std::u8string_view &sv) {
|
||||||
if (!m_ValidResult)
|
if (sv == u8"shallow") return LoadStage::Shallow;
|
||||||
throw std::runtime_error("try to get result from an invalid CmdSplitter.");
|
else if (sv == u8"deel") return LoadStage::Deep;
|
||||||
return m_Result;
|
else return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CmdSplitter::Lex(const std::u8string& u8cmd) {
|
static std::optional<LsPart> ParseLsPart(const std::u8string_view &sv) {
|
||||||
// Clear variables
|
if (sv == u8"mgr") return LsPart::Manager;
|
||||||
m_ValidResult = false;
|
else if (sv == u8"obj") return LsPart::Object;
|
||||||
m_Result.clear();
|
else if (sv == u8"search") return LsPart::Search;
|
||||||
m_Buffer.clear();
|
else return std::nullopt;
|
||||||
m_CurrentChar = u8'\0';
|
}
|
||||||
m_State = m_PrevState = StateType::SPACE;
|
|
||||||
|
|
||||||
// split
|
static std::optional<DataPart> ParseDataPart(const std::u8string_view &sv) {
|
||||||
for (char8_t c : u8cmd) {
|
if (sv == u8"mgr") return DataPart::Manager;
|
||||||
m_CurrentChar = c;
|
else if (sv == u8"obj") return DataPart::Object;
|
||||||
|
else return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
// skip all invalid characters (ascii code unit lower than space char)
|
static std::optional<ChunkPart> ParseChunkPart(const std::u8string_view &sv) {
|
||||||
// thus UTF8 code unit can directly accepted.
|
if (sv == u8"mgr") return ChunkPart::Manager;
|
||||||
if (m_CurrentChar < u8' ')
|
else if (sv == u8"obj") return ChunkPart::Object;
|
||||||
continue;
|
else return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_State) {
|
static std::optional<SearchPart> ParseSearchPart(const std::u8string_view &sv) {
|
||||||
case StateType::SPACE:
|
if (sv == u8"mgr") return SearchPart::Manager;
|
||||||
ProcSpace();
|
else if (sv == u8"obj") return SearchPart::Object;
|
||||||
break;
|
else return std::nullopt;
|
||||||
case StateType::SINGLE:
|
}
|
||||||
ProcSingle();
|
|
||||||
break;
|
|
||||||
case StateType::DOUBLE:
|
|
||||||
ProcDouble();
|
|
||||||
break;
|
|
||||||
case StateType::ESCAPE:
|
|
||||||
ProcEscape();
|
|
||||||
break;
|
|
||||||
case StateType::NORMAL:
|
|
||||||
ProcNormal();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// final proc
|
static std::optional<SearchMode> ParseSearchMode(const std::u8string_view &sv) {
|
||||||
bool is_success = false;
|
if (sv == u8"plain") return SearchMode::PlainText;
|
||||||
switch (m_State) {
|
else if (sv == u8"re") return SearchMode::Regex;
|
||||||
case StateType::SPACE:
|
else return std::nullopt;
|
||||||
is_success = true;
|
}
|
||||||
break;
|
|
||||||
case StateType::NORMAL:
|
|
||||||
// push the last one
|
|
||||||
m_Result.emplace_back(m_Buffer);
|
|
||||||
is_success = true;
|
|
||||||
break;
|
|
||||||
case StateType::SINGLE:
|
|
||||||
case StateType::DOUBLE:
|
|
||||||
case StateType::ESCAPE:
|
|
||||||
// error
|
|
||||||
is_success = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("unreachable code.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// check success
|
static std::optional<StyleLevel> ParseStyleLevel(const std::u8string_view &sv) {
|
||||||
if (is_success) {
|
if (sv == u8"simple") return StyleLevel::Simple;
|
||||||
m_ValidResult = true;
|
else if (sv == u8"full") return StyleLevel::Full;
|
||||||
return true;
|
else return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region Parameter Stack
|
||||||
|
|
||||||
|
ParamStack::ParamStack(std::vector<std::u8string> &¶ms) : params(std::move(params)), cursor(0) {}
|
||||||
|
|
||||||
|
ParamStack::~ParamStack() {}
|
||||||
|
|
||||||
|
std::optional<std::u8string_view> ParamStack::Next() {
|
||||||
|
if (this->cursor >= this->params.size()) {
|
||||||
|
return std::nullopt;
|
||||||
} else {
|
} else {
|
||||||
m_Result.clear();
|
return this->params[this->cursor++];
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CmdSplitter::ProcSpace() {
|
|
||||||
switch (m_CurrentChar) {
|
|
||||||
case u8'\'':
|
|
||||||
m_State = StateType::SINGLE;
|
|
||||||
break;
|
|
||||||
case u8'"':
|
|
||||||
m_State = StateType::DOUBLE;
|
|
||||||
break;
|
|
||||||
case u8'\\':
|
|
||||||
m_State = StateType::ESCAPE;
|
|
||||||
m_PrevState = StateType::NORMAL;
|
|
||||||
break;
|
|
||||||
case u8' ':
|
|
||||||
break; // skip blank
|
|
||||||
default:
|
|
||||||
m_Buffer.push_back(m_CurrentChar);
|
|
||||||
m_State = StateType::NORMAL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void CmdSplitter::ProcSingle() {
|
|
||||||
switch (m_CurrentChar) {
|
|
||||||
case u8'\'':
|
|
||||||
m_State = StateType::NORMAL;
|
|
||||||
break;
|
|
||||||
case u8'"':
|
|
||||||
m_Buffer.push_back('"');
|
|
||||||
break;
|
|
||||||
case u8'\\':
|
|
||||||
m_State = StateType::ESCAPE;
|
|
||||||
m_PrevState = StateType::SINGLE;
|
|
||||||
break;
|
|
||||||
case u8' ':
|
|
||||||
m_Buffer.push_back(u8' ');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Buffer.push_back(m_CurrentChar);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void CmdSplitter::ProcDouble() {
|
|
||||||
switch (m_CurrentChar) {
|
|
||||||
case u8'\'':
|
|
||||||
m_Buffer.push_back(u8'\'');
|
|
||||||
break;
|
|
||||||
case u8'"':
|
|
||||||
m_State = StateType::NORMAL;
|
|
||||||
break;
|
|
||||||
case u8'\\':
|
|
||||||
m_State = StateType::ESCAPE;
|
|
||||||
m_PrevState = StateType::DOUBLE;
|
|
||||||
break;
|
|
||||||
case u8' ':
|
|
||||||
m_Buffer.push_back(u8' ');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Buffer.push_back(m_CurrentChar);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void CmdSplitter::ProcEscape() {
|
|
||||||
// add itself
|
|
||||||
m_Buffer.push_back(m_CurrentChar);
|
|
||||||
// restore state
|
|
||||||
m_State = m_PrevState;
|
|
||||||
}
|
|
||||||
void CmdSplitter::ProcNormal() {
|
|
||||||
switch (m_CurrentChar) {
|
|
||||||
case u8'\'':
|
|
||||||
m_Buffer.push_back(u8'\'');
|
|
||||||
break;
|
|
||||||
case u8'"':
|
|
||||||
m_Buffer.push_back(u8'"');
|
|
||||||
break;
|
|
||||||
case u8'\\':
|
|
||||||
m_State = StateType::ESCAPE;
|
|
||||||
m_PrevState = StateType::NORMAL;
|
|
||||||
break;
|
|
||||||
case u8' ':
|
|
||||||
m_Result.emplace_back(m_Buffer);
|
|
||||||
m_Buffer.clear();
|
|
||||||
m_State = StateType::SPACE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_Buffer.push_back(m_CurrentChar);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region Arguments Map
|
|
||||||
|
|
||||||
ArgumentsMap::ArgumentsMap() : m_Data() {}
|
|
||||||
|
|
||||||
ArgumentsMap::~ArgumentsMap() {}
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region Help Document
|
|
||||||
|
|
||||||
HelpDocument::HelpDocument() : m_Stack(), m_Results() {}
|
|
||||||
|
|
||||||
HelpDocument::~HelpDocument() {}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
m_Stack.emplace_back(StackItem { arg_name, arg_desc });
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelpDocument::Pop() {
|
|
||||||
if (m_Stack.empty())
|
|
||||||
throw std::runtime_error("try pop back on an empty help document.");
|
|
||||||
m_Stack.pop_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelpDocument::Terminate(std::u8string& command_desc) {
|
|
||||||
// create new result
|
|
||||||
ResultItem result(command_desc, this->m_Stack);
|
|
||||||
// add into result
|
|
||||||
m_Results.emplace_back(std::move(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelpDocument::Print() {
|
|
||||||
for (auto& cmd : m_Results) {
|
|
||||||
// syntax
|
|
||||||
// check whether all description of argument are emtpty.
|
|
||||||
bool empty_arg_desc = true;
|
|
||||||
YYCC::ConsoleHelper::Write(YYCC_COLOR_LIGHT_YELLOW(u8"Syntax: "));
|
|
||||||
for (const auto& arg : cmd.m_ArgDesc) {
|
|
||||||
if (!arg.m_Desc.empty()) empty_arg_desc = false;
|
|
||||||
YYCC::ConsoleHelper::Format(u8"%s ", arg.m_Name.c_str());
|
|
||||||
}
|
|
||||||
YYCC::ConsoleHelper::WriteLine(u8"");
|
|
||||||
// command description
|
|
||||||
if (!cmd.m_CmdDesc.empty()) {
|
|
||||||
YYCC::ConsoleHelper::FormatLine(YYCC_COLOR_LIGHT_YELLOW(u8"Description: ") "%s", cmd.m_CmdDesc.c_str());
|
|
||||||
}
|
|
||||||
// argument description
|
|
||||||
if (!empty_arg_desc) {
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_COLOR_LIGHT_YELLOW(u8"Arguments:"));
|
|
||||||
for (auto& arg : cmd.m_ArgDesc) {
|
|
||||||
if (!arg.m_Desc.empty()) {
|
|
||||||
YYCC::ConsoleHelper::FormatLine(u8"\t" YYCC_COLOR_LIGHT_GREEN("%s") ": % s",
|
|
||||||
arg.m_Name.c_str(), arg.m_Desc.c_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// space between each commands
|
|
||||||
YYCC::ConsoleHelper::WriteLine(u8"");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region Conflict Set
|
#pragma region Commander
|
||||||
|
|
||||||
ConflictSet::ConflictSet() : m_ConflictSet() {}
|
Commander::Commander() {}
|
||||||
|
|
||||||
ConflictSet::~ConflictSet() {}
|
Commander::~Commander() {}
|
||||||
|
|
||||||
void ConflictSet::AddLiteral(const std::u8string_view& value) {
|
Result<void> Commander::Dispatch(const std::u8string_view &cmd) const {
|
||||||
if (value.empty())
|
auto stack = this->BuildStack(cmd);
|
||||||
throw std::invalid_argument("try to insert empty item to conflict set.");
|
if (stack.has_value()) {
|
||||||
auto result = m_ConflictSet.emplace(u8"literal:" + std::u8string(value));
|
return this->Branch(stack.value());
|
||||||
if (!result.second)
|
} else {
|
||||||
throw std::runtime_error("try to insert duplicated item in conflict set.");
|
return std::unexpected(stack.error());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::AddArgument(const std::u8string_view& value) {
|
Result<ParamStack> Commander::BuildStack(const std::u8string_view &cmd) const {
|
||||||
if (value.empty())
|
yycc::carton::lexer61::Lexer61 lexer;
|
||||||
throw std::invalid_argument("try to insert empty item to conflict set.");
|
auto rv = lexer.lex(cmd);
|
||||||
auto result = m_ConflictSet.emplace(u8"argument:" + std::u8string(value));
|
if (rv.has_value()) {
|
||||||
if (!result.second)
|
return ParamStack(std::move(rv.value()));
|
||||||
throw std::runtime_error("try to insert duplicated item in conflict set.");
|
} else {
|
||||||
|
return std::unexpected(Error::Lexer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConflictSet::IsConflictWith(const ConflictSet& rhs) const {
|
#pragma region Branch
|
||||||
// create a cache to store computed intersection
|
|
||||||
std::vector<std::u8string> intersection;
|
#define CHECK_NO_MORE_PARAM(stack) \
|
||||||
// compute intersection
|
if (stack.Next().has_value()) { \
|
||||||
std::set_intersection(
|
return std::unexpected(Error::TooMuchParam); \
|
||||||
this->m_ConflictSet.begin(), this->m_ConflictSet.end(),
|
}
|
||||||
rhs.m_ConflictSet.begin(), rhs.m_ConflictSet.begin(),
|
#define SAFE_CALL_DELEGATE(delegate, payload) \
|
||||||
std::back_inserter(intersection)
|
if ((delegate) != nullptr) { \
|
||||||
);
|
(delegate)(payload); \
|
||||||
// check whether it is empty intersection
|
|
||||||
return !intersection.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma endregion
|
Result<void> Commander::Branch(ParamStack &stack) const {
|
||||||
|
auto param = stack.Next();
|
||||||
|
if (param.has_value()) {
|
||||||
|
const auto &verb = param.value();
|
||||||
|
|
||||||
namespace Nodes {
|
if (verb == u8"load") {
|
||||||
|
return this->LoadBranch(stack);
|
||||||
#pragma region Abstract Node
|
} else if (verb == u8"unload") {
|
||||||
|
return this->UnloadBranch(stack);
|
||||||
AbstractNode::AbstractNode() :
|
} else if (verb == u8"save") {
|
||||||
m_Execution(nullptr), m_Comment(), m_Nodes() {}
|
return this->SaveBranch(stack);
|
||||||
|
} else if (verb == u8"info") {
|
||||||
AbstractNode::~AbstractNode() {}
|
return this->InfoBranch(stack);
|
||||||
|
} else if (verb == u8"ls") {
|
||||||
AbstractNode& AbstractNode::Executes(FctExecution_t fct, const std::u8string_view& exec_desc) {
|
return this->LsBranch(stack);
|
||||||
if (this->IsRootNode())
|
} else if (verb == u8"data") {
|
||||||
throw std::logic_error("root node should not have execution.");
|
return this->DataBranch(stack);
|
||||||
if (m_Execution != nullptr)
|
} else if (verb == u8"chunk") {
|
||||||
throw std::invalid_argument("you should not assign execution multiuple times.");
|
return this->ChunkBranch(stack);
|
||||||
if (fct == nullptr)
|
} else if (verb == u8"search") {
|
||||||
throw std::invalid_argument("the function passed for executing should not be nullptr.");
|
return this->SearchBranch(stack);
|
||||||
m_Execution = fct;
|
} else if (verb == u8"items") {
|
||||||
m_ExecutionDesc = exec_desc;
|
return this->ItemsBranch(stack);
|
||||||
return *this;
|
} else if (verb == u8"style") {
|
||||||
}
|
return this->StyleBranch(stack);
|
||||||
|
} else if (verb == u8"encoding") {
|
||||||
AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) {
|
return this->EncodingBranch(stack);
|
||||||
if (this->IsRootNode())
|
} else if (verb == u8"temp") {
|
||||||
throw std::logic_error("root node should not have comment.");
|
return this->TempBranch(stack);
|
||||||
m_Comment = comment;
|
} else if (verb == u8"rsc") {
|
||||||
return *this;
|
return this->RscBranch(stack);
|
||||||
}
|
} else if (verb == u8"test") {
|
||||||
|
return this->TestBranch(stack);
|
||||||
void AbstractNode::Help(HelpDocument& doc) {
|
} else if (verb == u8"version") {
|
||||||
// If this node is not root node
|
return this->VersionBranch(stack);
|
||||||
if (!this->IsRootNode()) {
|
} else if (verb == u8"help") {
|
||||||
// Push self symbol to help document stack.
|
return this->HelpBranch(stack);
|
||||||
if (!this->IsRootNode()) {
|
} else if (verb == u8"exit") {
|
||||||
doc.Push(GetHelpSymbol(), m_Comment);
|
return this->ExitBranch(stack);
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether this node is terminal.
|
|
||||||
// If it is, terminate it once.
|
|
||||||
if (m_Execution != nullptr) {
|
|
||||||
doc.Terminate(m_ExecutionDesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then process its children nodes (both root node and common node).
|
|
||||||
for (auto& node : m_Nodes) {
|
|
||||||
node->Help(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop self from help document stack
|
|
||||||
// if this node is not root node
|
|
||||||
if (!this->IsRootNode()) {
|
|
||||||
doc.Pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) {
|
|
||||||
// If no command to be consumed, return directly.
|
|
||||||
if (al.empty()) return false;
|
|
||||||
|
|
||||||
// Process for self if we are not root node
|
|
||||||
std::u8string cur_cmd;
|
|
||||||
if (!this->IsRootNode()) {
|
|
||||||
// Backup the top item in command stack for personal consume.
|
|
||||||
cur_cmd = al.front();
|
|
||||||
// Try to consume it for self.
|
|
||||||
if (!BeginConsume(cur_cmd, am)) {
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define free function if we are not the root node.
|
|
||||||
// Because we just pop the top item of command stack.
|
|
||||||
#define CONSUME_DEFER \
|
|
||||||
if (!this->IsRootNode()) { \
|
|
||||||
al.emplace_front(cur_cmd); \
|
|
||||||
EndConsume(am); \
|
|
||||||
}
|
|
||||||
|
|
||||||
if (al.empty()) {
|
|
||||||
// Root node do not have execution, return false directly
|
|
||||||
if (this->IsRootNode()) {
|
|
||||||
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) {
|
|
||||||
CONSUME_DEFER;
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
m_Execution(am);
|
|
||||||
CONSUME_DEFER;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Command stack still item to be consumed.
|
return std::unexpected(Error::BadVerb);
|
||||||
// To consume them, we need iterate node list and 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 all node can not consume it,
|
|
||||||
// it means that this command can not match this node tree.
|
|
||||||
// Return false directly.
|
|
||||||
CONSUME_DEFER;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
#undef CONSUME_DEFER
|
return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Result<void> Commander::LoadBranch(ParamStack &stack) const {
|
||||||
|
auto stage_param = stack.Next();
|
||||||
|
if (!stage_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto stage_arg = ParseLoadStage(stage_param.value());
|
||||||
|
if (!stage_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
#pragma endregion
|
auto filepath_arg = stack.Next();
|
||||||
|
if (!filepath_arg.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
#pragma region Root Node
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
RootNode::RootNode() : AbstractNode() {}
|
LoadParam param {
|
||||||
RootNode::~RootNode() {}
|
.stage = stage_arg.value(),
|
||||||
|
.filepath = std::u8string(filepath_arg.value()),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->load_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::UnloadBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->unload_delegate, UnloadParam{})
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::SaveBranch(ParamStack &stack) const {
|
||||||
|
auto filepath_arg = stack.Next();
|
||||||
|
if (!filepath_arg.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
bool RootNode::IsRootNode() { return true; }
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
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
|
SaveParam param {
|
||||||
|
.filepath = std::u8string(filepath_arg.value()),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->save_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::InfoBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->info_delegate, InfoParam{})
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::LsBranch(ParamStack &stack) const {
|
||||||
|
auto part_param = stack.Next();
|
||||||
|
if (!part_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto part_arg = ParseLsPart(part_param.value());
|
||||||
|
if (!part_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
#pragma region Literal
|
auto page_param = stack.Next();
|
||||||
|
if (!page_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto page_arg = yycc::num::parse::parse<size_t>(page_param.value());
|
||||||
|
if (!page_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
if (page_arg.value() < 1) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
Literal::Literal(const std::u8string_view& words) :
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
AbstractNode(), m_Literal(words), m_ConflictSet() {
|
|
||||||
// check argument
|
LsParam param {
|
||||||
if (words.empty())
|
.part = part_arg.value(),
|
||||||
throw std::invalid_argument("The word of literal node should not be empty.");
|
.page = page_arg.value(),
|
||||||
// set conflict set
|
};
|
||||||
m_ConflictSet.AddLiteral(m_Literal);
|
SAFE_CALL_DELEGATE(this->ls_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::DataBranch(ParamStack &stack) const {
|
||||||
|
auto part_param = stack.Next();
|
||||||
|
if (!part_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto part_arg = ParseDataPart(part_param.value());
|
||||||
|
if (!part_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
auto index_param = stack.Next();
|
||||||
|
if (!index_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto index_arg = yycc::num::parse::parse<size_t>(index_param.value());
|
||||||
|
if (!index_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
|
DataParam param {
|
||||||
|
.part = part_arg.value(),
|
||||||
|
.index = index_arg.value(),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->data_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::ChunkBranch(ParamStack &stack) const {
|
||||||
|
auto part_param = stack.Next();
|
||||||
|
if (!part_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto part_arg = ParseChunkPart(part_param.value());
|
||||||
|
if (!part_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
auto index_param = stack.Next();
|
||||||
|
if (!index_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto index_arg = yycc::num::parse::parse<size_t>(index_param.value());
|
||||||
|
if (!index_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
|
ChunkParam param {
|
||||||
|
.part = part_arg.value(),
|
||||||
|
.index = index_arg.value(),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->chunk_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::SearchBranch(ParamStack &stack) const {
|
||||||
|
auto part_param = stack.Next();
|
||||||
|
if (!part_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto part_arg = ParseSearchPart(part_param.value());
|
||||||
|
if (!part_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
auto mode_param = stack.Next();
|
||||||
|
if (!mode_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto mode_arg = ParseSearchMode(mode_param.value());
|
||||||
|
if (!mode_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
auto text_arg = stack.Next();
|
||||||
|
if (!text_arg.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
|
SearchParam param {
|
||||||
|
.part = part_arg.value(),
|
||||||
|
.mode = mode_arg.value(),
|
||||||
|
.text = std::u8string(text_arg.value()),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->search_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::ItemsBranch(ParamStack &stack) const {
|
||||||
|
auto count_param = stack.Next();
|
||||||
|
if (!count_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto count_arg = yycc::num::parse::parse<size_t>(count_param.value());
|
||||||
|
if (!count_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
if (count_arg.value() < 1) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
|
ItemsParam param {
|
||||||
|
.count = count_arg.value(),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->items_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::StyleBranch(ParamStack &stack) const {
|
||||||
|
auto level_param = stack.Next();
|
||||||
|
if (!level_param.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
auto level_arg = ParseStyleLevel(level_param.value());
|
||||||
|
if (!level_arg.has_value()) return std::unexpected(Error::BadArg);
|
||||||
|
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
|
StyleParam param {
|
||||||
|
.level = level_arg.value(),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->style_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::EncodingBranch(ParamStack &stack) const {
|
||||||
|
std::vector<std::u8string> collector;
|
||||||
|
while (true) {
|
||||||
|
auto param = stack.Next();
|
||||||
|
if (param.has_value()) collector.emplace_back(param.value());
|
||||||
|
else break;
|
||||||
}
|
}
|
||||||
Literal::~Literal() {}
|
if (collector.empty()) return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
bool Literal::IsRootNode() { return false; }
|
EncodingParam param{
|
||||||
bool Literal::IsArgument() { return false; }
|
.enc = std::move(collector),
|
||||||
const ConflictSet& Literal::GetConflictSet() { return m_ConflictSet; }
|
};
|
||||||
std::u8string Literal::GetHelpSymbol() { return m_Literal; }
|
SAFE_CALL_DELEGATE(this->encoding_delegate, param)
|
||||||
bool Literal::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return cur_cmd == m_Literal; }
|
return {};
|
||||||
void Literal::EndConsume(ArgumentsMap& am) {}
|
}
|
||||||
|
Result<void> Commander::TempBranch(ParamStack &stack) const {
|
||||||
|
auto dirpath_arg = stack.Next();
|
||||||
|
if (!dirpath_arg.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
#pragma endregion
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
|
||||||
#pragma region Choice
|
TempParam param {
|
||||||
|
.dirpath = std::u8string(dirpath_arg.value()),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->temp_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::RscBranch(ParamStack &stack) const {
|
||||||
|
auto param = stack.Next();
|
||||||
|
if (param.has_value()) {
|
||||||
|
const auto &verb = param.value();
|
||||||
|
|
||||||
Choice::Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary) :
|
if (verb == u8"clear") {
|
||||||
AbstractNode(),
|
return this->RscClearBranch(stack);
|
||||||
m_ChoiceName(argname), m_Vocabulary(vocabulary),
|
} else if (verb == u8"add") {
|
||||||
m_ConflictSet() {
|
return this->RscAddBranch(stack);
|
||||||
// check argument
|
} else {
|
||||||
if (argname.empty())
|
return std::unexpected(Error::BadVerb);
|
||||||
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.");
|
|
||||||
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
|
|
||||||
m_ConflictSet.AddArgument(m_ChoiceName);
|
|
||||||
for (const auto& word : m_Vocabulary) {
|
|
||||||
m_ConflictSet.AddLiteral(word);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return std::unexpected(Error::TooLessParam);
|
||||||
}
|
}
|
||||||
Choice::~Choice() {}
|
}
|
||||||
|
Result<void> Commander::RscClearBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->rsc_clear_delegate, RscClearParam{})
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::RscAddBranch(ParamStack &stack) const {
|
||||||
|
auto dirpath_arg = stack.Next();
|
||||||
|
if (!dirpath_arg.has_value()) return std::unexpected(Error::TooLessParam);
|
||||||
|
|
||||||
bool Choice::IsRootNode() { return false; }
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
bool Choice::IsArgument() { return false; }
|
|
||||||
const ConflictSet& Choice::GetConflictSet() { return m_ConflictSet; }
|
|
||||||
std::u8string Choice::GetHelpSymbol() {
|
|
||||||
return YYCC::StringHelper::Printf(u8"[%s]",
|
|
||||||
YYCC::StringHelper::Join(m_Vocabulary.begin(), m_Vocabulary.end(), 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<ArgValue_t>(m_ChoiceName, i);
|
|
||||||
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_ArgumentName(argname), m_ConflictSet() {
|
|
||||||
// check argument
|
|
||||||
if (argname.empty())
|
|
||||||
throw std::invalid_argument("Argument name should not be empty.");
|
|
||||||
// setup conflict set
|
|
||||||
m_ConflictSet.AddArgument(m_ArgumentName);
|
|
||||||
}
|
|
||||||
AbstractArgument::~AbstractArgument() {}
|
|
||||||
|
|
||||||
bool AbstractArgument::IsRootNode() { return false; }
|
|
||||||
bool AbstractArgument::IsArgument() { return true; }
|
|
||||||
const ConflictSet& AbstractArgument::GetConflictSet() { return m_ConflictSet; }
|
|
||||||
std::u8string AbstractArgument::GetHelpSymbol() {
|
|
||||||
return YYCC::StringHelper::Printf(u8"<%s>", m_ArgumentName.c_str());
|
|
||||||
}
|
|
||||||
//bool AbstractArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return false; }
|
|
||||||
void AbstractArgument::EndConsume(ArgumentsMap& am) { am.Remove(m_ArgumentName); }
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region Arithmetic Argument
|
|
||||||
|
|
||||||
// It's a template class
|
|
||||||
// We are forced to implement it in header file.
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region String Argument
|
|
||||||
|
|
||||||
StringArgument::StringArgument(const std::u8string_view& argname, Constraint_t constraint) :
|
|
||||||
AbstractArgument(argname), m_Constraint(constraint) {}
|
|
||||||
|
|
||||||
StringArgument::~StringArgument() {}
|
|
||||||
|
|
||||||
bool StringArgument::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) {
|
|
||||||
// check constraint
|
|
||||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(cur_cmd))
|
|
||||||
return false;
|
|
||||||
// accept
|
|
||||||
am.Add<ArgValue_t>(m_ArgumentName, cur_cmd);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
|
RscAddParam param {
|
||||||
|
.dirpath = std::u8string(dirpath_arg.value()),
|
||||||
|
};
|
||||||
|
SAFE_CALL_DELEGATE(this->rsc_add_delegate, param)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::TestBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->test_delegate, TestParam{})
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::VersionBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->version_delegate, VersionParam{})
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::HelpBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->help_delegate, HelpParam{})
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Result<void> Commander::ExitBranch(ParamStack &stack) const {
|
||||||
|
CHECK_NO_MORE_PARAM(stack)
|
||||||
|
SAFE_CALL_DELEGATE(this->exit_delegate, ExitParam{})
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma region Command Parser
|
#undef SAFE_CALL_DELEGATE
|
||||||
|
#undef CHECK_NO_MORE_PARAM
|
||||||
|
|
||||||
CommandParser::CommandParser() {}
|
#pragma endregion
|
||||||
|
|
||||||
CommandParser::~CommandParser() {}
|
#pragma region Delegate Setter
|
||||||
|
|
||||||
bool CommandParser::Parse(const CmdSplitter::Result_t& cmds) {
|
void Commander::SetLoadDelegate(LoadDelegate &&delegate) {
|
||||||
// Create a copy of given command
|
this->load_delegate = std::move(delegate);
|
||||||
CmdSplitter::Result_t al(cmds);
|
|
||||||
// Create argument map
|
|
||||||
ArgumentsMap am;
|
|
||||||
|
|
||||||
// Call root node Consume function and return its result.
|
|
||||||
return m_RootNode.Consume(al, am);
|
|
||||||
}
|
}
|
||||||
|
void Commander::SetUnloadDelegate(UnloadDelegate &&delegate) {
|
||||||
HelpDocument CommandParser::Help() {
|
this->unload_delegate = std::move(delegate);
|
||||||
// Create help docuemnt
|
|
||||||
HelpDocument doc;
|
|
||||||
// use node tree to fill help document.
|
|
||||||
m_RootNode.Help(doc);
|
|
||||||
// return result.
|
|
||||||
return doc;
|
|
||||||
}
|
}
|
||||||
|
void Commander::SetSaveDelegate(SaveDelegate &&delegate) {
|
||||||
Nodes::RootNode& CommandParser::GetRoot() {
|
this->save_delegate = std::move(delegate);
|
||||||
return m_RootNode;
|
}
|
||||||
|
void Commander::SetInfoDelegate(InfoDelegate &&delegate) {
|
||||||
|
this->info_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetLsDelegate(LsDelegate &&delegate) {
|
||||||
|
this->ls_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetDataDelegate(DataDelegate &&delegate) {
|
||||||
|
this->data_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetChunkDelegate(ChunkDelegate &&delegate) {
|
||||||
|
this->chunk_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetSearchDelegate(SearchDelegate &&delegate) {
|
||||||
|
this->search_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetItemsDelegate(ItemsDelegate &&delegate) {
|
||||||
|
this->items_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetStyleDelegate(StyleDelegate &&delegate) {
|
||||||
|
this->style_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetEncodingDelegate(EncodingDelegate &&delegate) {
|
||||||
|
this->encoding_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetTempDelegate(TempDelegate &&delegate) {
|
||||||
|
this->temp_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetRscClearDelegate(RscClearDelegate &&delegate) {
|
||||||
|
this->rsc_clear_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetRscAddDelegate(RscAddDelegate &&delegate) {
|
||||||
|
this->rsc_add_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetTestDelegate(TestDelegate &&delegate) {
|
||||||
|
this->test_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetVersionDelegate(VersionDelegate &&delegate) {
|
||||||
|
this->version_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetHelpDelegate(HelpDelegate &&delegate) {
|
||||||
|
this->help_delegate = std::move(delegate);
|
||||||
|
}
|
||||||
|
void Commander::SetExitDelegate(ExitDelegate &&delegate) {
|
||||||
|
this->exit_delegate = std::move(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
}
|
#pragma endregion
|
||||||
|
|
||||||
|
} // namespace Unvirt::CmdHelper
|
||||||
|
|||||||
@@ -1,526 +1,241 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <YYCCommonplace.hpp>
|
#include <yycc.hpp>
|
||||||
|
#include <yycc/macro/class_copy_move.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <initializer_list>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <expected>
|
||||||
#include <stdexcept>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace Unvirt::CmdHelper {
|
namespace Unvirt::CmdHelper {
|
||||||
|
|
||||||
class CmdSplitter {
|
#pragma region Delegate Parameters
|
||||||
|
|
||||||
|
enum class LoadStage { Shallow, Deep };
|
||||||
|
|
||||||
|
struct LoadParam {
|
||||||
|
LoadStage stage;
|
||||||
|
std::u8string filepath;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnloadParam {};
|
||||||
|
|
||||||
|
struct SaveParam {
|
||||||
|
std::u8string filepath;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InfoParam {};
|
||||||
|
|
||||||
|
enum class LsPart {
|
||||||
|
Object,
|
||||||
|
Manager,
|
||||||
|
Search,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LsParam {
|
||||||
|
LsPart part;
|
||||||
|
size_t page;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DataPart {
|
||||||
|
Object,
|
||||||
|
Manager,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DataParam {
|
||||||
|
DataPart part;
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ChunkPart {
|
||||||
|
Object,
|
||||||
|
Manager,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChunkParam {
|
||||||
|
ChunkPart part;
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SearchPart {
|
||||||
|
Object,
|
||||||
|
Manager,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SearchMode {
|
||||||
|
PlainText,
|
||||||
|
Regex,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SearchParam {
|
||||||
|
SearchPart part;
|
||||||
|
SearchMode mode;
|
||||||
|
std::u8string text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ItemsParam {
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class StyleLevel {
|
||||||
|
Full,
|
||||||
|
Simple,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StyleParam {
|
||||||
|
StyleLevel level;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EncodingParam {
|
||||||
|
std::vector<std::u8string> enc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TempParam {
|
||||||
|
std::u8string dirpath;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RscClearParam {};
|
||||||
|
struct RscAddParam {
|
||||||
|
std::u8string dirpath;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestParam {};
|
||||||
|
|
||||||
|
struct VersionParam {};
|
||||||
|
|
||||||
|
struct HelpParam {};
|
||||||
|
|
||||||
|
struct ExitParam {};
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region Delegates
|
||||||
|
|
||||||
|
using LoadDelegate = std::function<void(const LoadParam &)>;
|
||||||
|
using UnloadDelegate = std::function<void(const UnloadParam &)>;
|
||||||
|
using SaveDelegate = std::function<void(const SaveParam &)>;
|
||||||
|
using InfoDelegate = std::function<void(const InfoParam &)>;
|
||||||
|
using LsDelegate = std::function<void(const LsParam &)>;
|
||||||
|
using DataDelegate = std::function<void(const DataParam &)>;
|
||||||
|
using ChunkDelegate = std::function<void(const ChunkParam &)>;
|
||||||
|
using SearchDelegate = std::function<void(const SearchParam &)>;
|
||||||
|
using ItemsDelegate = std::function<void(const ItemsParam &)>;
|
||||||
|
using StyleDelegate = std::function<void(const StyleParam &)>;
|
||||||
|
using EncodingDelegate = std::function<void(const EncodingParam &)>;
|
||||||
|
using TempDelegate = std::function<void(const TempParam &)>;
|
||||||
|
using RscClearDelegate = std::function<void(const RscClearParam &)>;
|
||||||
|
using RscAddDelegate = std::function<void(const RscAddParam &)>;
|
||||||
|
using TestDelegate = std::function<void(const TestParam &)>;
|
||||||
|
using VersionDelegate = std::function<void(const VersionParam &)>;
|
||||||
|
using HelpDelegate = std::function<void(const HelpParam &)>;
|
||||||
|
using ExitDelegate = std::function<void(const ExitParam &)>;
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region Error Types
|
||||||
|
|
||||||
|
enum class Error {
|
||||||
|
Lexer, ///< Fail to do lexer.
|
||||||
|
TooMuchParam, ///< The count of parameter is larger than expected.
|
||||||
|
TooLessParam, ///< The count of parameter is to less as expected.
|
||||||
|
BadArg, ///< The format of argument is wrong.
|
||||||
|
BadVerb, ///< Given verb do not match with any existing items.
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using Result = std::expected<T, Error>;
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
class ParamStack {
|
||||||
public:
|
public:
|
||||||
using Result_t = std::deque<std::u8string>;
|
ParamStack(std::vector<std::u8string>&& params);
|
||||||
private:
|
~ParamStack();
|
||||||
enum class StateType : int {
|
YYCC_DEFAULT_COPY_MOVE(ParamStack)
|
||||||
SPACE,
|
|
||||||
SINGLE,
|
|
||||||
DOUBLE,
|
|
||||||
ESCAPE,
|
|
||||||
NORMAL
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CmdSplitter() :
|
std::optional<std::u8string_view> Next();
|
||||||
m_CurrentChar(u8'\0'), m_Buffer(), m_Result(), m_ValidResult(false),
|
|
||||||
m_State(StateType::NORMAL), m_PrevState(StateType::NORMAL) {}
|
|
||||||
~CmdSplitter() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(CmdSplitter);
|
|
||||||
|
|
||||||
bool Lex(const std::u8string& u8cmd);
|
|
||||||
const Result_t& GetResult() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ProcSpace();
|
std::vector<std::u8string> params;
|
||||||
void ProcSingle();
|
size_t cursor;
|
||||||
void ProcDouble();
|
|
||||||
void ProcEscape();
|
|
||||||
void ProcNormal();
|
|
||||||
|
|
||||||
char8_t m_CurrentChar;
|
|
||||||
std::u8string m_Buffer;
|
|
||||||
Result_t m_Result;
|
|
||||||
bool m_ValidResult;
|
|
||||||
StateType m_State, m_PrevState;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace AMItems {
|
class Commander {
|
||||||
|
|
||||||
class AbstractItem {
|
|
||||||
public:
|
|
||||||
AbstractItem() {}
|
|
||||||
virtual ~AbstractItem() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(AbstractItem);
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
|
||||||
class ArithmeticItem : public AbstractItem {
|
|
||||||
public:
|
|
||||||
ArithmeticItem(_Ty value) : AbstractItem(), m_Data(value) {}
|
|
||||||
virtual ~ArithmeticItem() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(ArithmeticItem);
|
|
||||||
public:
|
|
||||||
_Ty Get() const { return m_Data; }
|
|
||||||
protected:
|
|
||||||
_Ty m_Data;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
|
||||||
class ArithmeticArrayItem : public AbstractItem {
|
|
||||||
public:
|
|
||||||
ArithmeticArrayItem(const std::vector<_Ty>& values) : AbstractItem(), m_Data(values) {}
|
|
||||||
virtual ~ArithmeticArrayItem() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(ArithmeticArrayItem);
|
|
||||||
public:
|
|
||||||
const std::vector<_Ty>& Get() const { return m_Data; }
|
|
||||||
protected:
|
|
||||||
std::vector<_Ty> m_Data;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StringItem : public AbstractItem {
|
|
||||||
public:
|
|
||||||
StringItem(const std::u8string_view& value) : AbstractItem(), m_Data(value) {}
|
|
||||||
virtual ~StringItem() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(StringItem);
|
|
||||||
public:
|
|
||||||
const std::u8string& Get() const { return m_Data; }
|
|
||||||
protected:
|
|
||||||
std::u8string m_Data;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StringArrayItem : public AbstractItem {
|
|
||||||
public:
|
|
||||||
StringArrayItem(const std::vector<std::u8string>& value) : AbstractItem(), m_Data(value) {}
|
|
||||||
virtual ~StringArrayItem() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(StringArrayItem);
|
|
||||||
public:
|
|
||||||
const std::vector<std::u8string>& Get() const { return m_Data; }
|
|
||||||
protected:
|
|
||||||
std::vector<std::u8string> m_Data;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArgumentsMap {
|
|
||||||
public:
|
public:
|
||||||
ArgumentsMap();
|
Commander();
|
||||||
~ArgumentsMap();
|
~Commander();
|
||||||
YYCC_DEF_CLS_COPY_MOVE(ArgumentsMap);
|
YYCC_DELETE_COPY_MOVE(Commander)
|
||||||
|
|
||||||
protected:
|
|
||||||
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<AMItems::AbstractItem, _Ty>, int> = 0>
|
Result<void> Dispatch(const std::u8string_view &cmd) const;
|
||||||
void Add(const std::u8string_view& key, _Types&&... args) {
|
|
||||||
// check argument
|
|
||||||
if (key.empty())
|
|
||||||
throw std::invalid_argument("argument key should not be empty");
|
|
||||||
// insert into data
|
|
||||||
auto result = m_Data.try_emplace(std::u8string(key), std::make_shared<_Ty>(std::forward<_Types>(args)...));
|
|
||||||
if (!result.second)
|
|
||||||
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>
|
|
||||||
const _Ty& Get(const std::u8string_view& key) const {
|
|
||||||
// check argument
|
|
||||||
if (key.empty())
|
|
||||||
throw std::invalid_argument("argument key should not be empty");
|
|
||||||
// find key first
|
|
||||||
auto finder = m_Data.find(std::u8string(key));
|
|
||||||
if (finder == m_Data.end())
|
|
||||||
throw std::runtime_error("try to get a non-existent key.");
|
|
||||||
// get stored value data
|
|
||||||
const _Ty* value = static_cast<const _Ty*>(finder->second.get());
|
|
||||||
return *value;
|
|
||||||
}
|
|
||||||
void Remove(const std::u8string_view& key) {
|
|
||||||
// check argument
|
|
||||||
if (key.empty())
|
|
||||||
throw std::invalid_argument("argument key should not be empty");
|
|
||||||
// remove it from map and check whether remove item
|
|
||||||
if (m_Data.erase(std::u8string(key)) == 0u)
|
|
||||||
throw std::runtime_error("try to delete a non-existent key.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forward declaration for Nodes::AbstractNode
|
|
||||||
namespace Nodes { class AbstractNode; }
|
|
||||||
|
|
||||||
class HelpDocument {
|
|
||||||
friend class Nodes::AbstractNode;
|
|
||||||
public:
|
|
||||||
HelpDocument();
|
|
||||||
~HelpDocument();
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(HelpDocument);
|
|
||||||
|
|
||||||
public:
|
|
||||||
void Print();
|
|
||||||
|
|
||||||
// AbstractNode used
|
|
||||||
protected:
|
|
||||||
void Push(const std::u8string& arg_name, const std::u8string& arg_desc);
|
|
||||||
void Pop();
|
|
||||||
void Terminate(std::u8string& command_desc);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
struct StackItem {
|
|
||||||
StackItem();
|
|
||||||
StackItem(const std::u8string& name, const std::u8string& desc);
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(StackItem);
|
|
||||||
|
|
||||||
std::u8string m_Name;
|
|
||||||
std::u8string m_Desc;
|
|
||||||
};
|
|
||||||
std::deque<StackItem> m_Stack;
|
|
||||||
|
|
||||||
struct ResultItem {
|
|
||||||
ResultItem();
|
|
||||||
ResultItem(const std::u8string& cmd_desc, const std::deque<StackItem>& arg_desc);
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(ResultItem);
|
|
||||||
|
|
||||||
std::u8string m_CmdDesc;
|
|
||||||
std::vector<StackItem> m_ArgDesc;
|
|
||||||
};
|
|
||||||
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 {
|
|
||||||
|
|
||||||
class AbstractNode {
|
|
||||||
public:
|
|
||||||
using FctExecution_t = std::function<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 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.
|
|
||||||
* @return True if it is, otherwise false.
|
|
||||||
* @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 ConflictSet& 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> && !std::is_same_v<AbstractNode, _Ty>, int> = 0>
|
|
||||||
AbstractNode& Then(AbstractNode& node) {
|
|
||||||
// create node first
|
|
||||||
auto new_node = std::make_shared<_Ty>(static_cast<_Ty&>(node));
|
|
||||||
// get its abstract pointer for checking
|
|
||||||
AbstractNode* new_node_ptr = new_node.get();
|
|
||||||
// check root node.
|
|
||||||
if (new_node_ptr->IsRootNode())
|
|
||||||
throw std::invalid_argument("root node should not be inserted as child node.");
|
|
||||||
// check conflict
|
|
||||||
const auto& new_node_set = new_node_ptr->GetConflictSet();
|
|
||||||
for (auto& child_node : m_Nodes) {
|
|
||||||
const auto& node_set = child_node->GetConflictSet();
|
|
||||||
if (new_node_set.IsConflictWith(node_set))
|
|
||||||
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 RootNode : public AbstractNode {
|
|
||||||
friend class ::Unvirt::CmdHelper::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 {
|
|
||||||
public:
|
|
||||||
Literal(const std::u8string_view& words);
|
|
||||||
virtual ~Literal();
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(Literal);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
std::u8string m_Literal;
|
|
||||||
ConflictSet m_ConflictSet;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Choice : public AbstractNode {
|
|
||||||
public:
|
|
||||||
using ArgValue_t = AMItems::ArithmeticItem<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 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;
|
|
||||||
|
|
||||||
std::u8string m_ChoiceName;
|
|
||||||
std::vector<std::u8string> m_Vocabulary;
|
|
||||||
ConflictSet 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 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;
|
|
||||||
|
|
||||||
std::u8string m_ArgumentName;
|
|
||||||
ConflictSet m_ConflictSet;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
|
||||||
class ArithmeticArgument : public AbstractArgument {
|
|
||||||
public:
|
|
||||||
using ArgValue_t = AMItems::ArithmeticItem<_Ty>;
|
|
||||||
using Constraint_t = YYCC::Constraints::Constraint<_Ty>;
|
|
||||||
public:
|
|
||||||
ArithmeticArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}) :
|
|
||||||
AbstractArgument(argname), m_Constraint(constraint) {}
|
|
||||||
virtual ~ArithmeticArgument() {}
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(ArithmeticArgument);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override {
|
|
||||||
// try parse
|
|
||||||
_Ty result;
|
|
||||||
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 {
|
|
||||||
public:
|
|
||||||
using ArgValue_t = AMItems::StringItem;
|
|
||||||
using Constraint_t = YYCC::Constraints::Constraint<std::u8string>;
|
|
||||||
public:
|
|
||||||
StringArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {});
|
|
||||||
virtual ~StringArgument();
|
|
||||||
YYCC_DEF_CLS_COPY_MOVE(StringArgument);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
|
|
||||||
Constraint_t m_Constraint;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class CommandParser {
|
|
||||||
public:
|
|
||||||
CommandParser();
|
|
||||||
~CommandParser();
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool Parse(const CmdSplitter::Result_t& cmds);
|
|
||||||
HelpDocument Help();
|
|
||||||
Nodes::RootNode& GetRoot();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Nodes::RootNode m_RootNode;
|
Result<ParamStack> BuildStack(const std::u8string_view &cmd) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Result<void> Branch(ParamStack &stack) const;
|
||||||
|
Result<void> LoadBranch(ParamStack &stack) const;
|
||||||
|
Result<void> UnloadBranch(ParamStack &stack) const;
|
||||||
|
Result<void> SaveBranch(ParamStack &stack) const;
|
||||||
|
Result<void> InfoBranch(ParamStack &stack) const;
|
||||||
|
Result<void> LsBranch(ParamStack &stack) const;
|
||||||
|
Result<void> DataBranch(ParamStack &stack) const;
|
||||||
|
Result<void> ChunkBranch(ParamStack &stack) const;
|
||||||
|
Result<void> SearchBranch(ParamStack &stack) const;
|
||||||
|
Result<void> ItemsBranch(ParamStack &stack) const;
|
||||||
|
Result<void> StyleBranch(ParamStack &stack) const;
|
||||||
|
Result<void> EncodingBranch(ParamStack &stack) const;
|
||||||
|
Result<void> TempBranch(ParamStack &stack) const;
|
||||||
|
Result<void> RscBranch(ParamStack &stack) const;
|
||||||
|
Result<void> RscClearBranch(ParamStack &stack) const;
|
||||||
|
Result<void> RscAddBranch(ParamStack &stack) const;
|
||||||
|
Result<void> TestBranch(ParamStack &stack) const;
|
||||||
|
Result<void> VersionBranch(ParamStack &stack) const;
|
||||||
|
Result<void> HelpBranch(ParamStack &stack) const;
|
||||||
|
Result<void> ExitBranch(ParamStack &stack) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void SetLoadDelegate(LoadDelegate &&delegate);
|
||||||
|
void SetUnloadDelegate(UnloadDelegate &&delegate);
|
||||||
|
void SetSaveDelegate(SaveDelegate &&delegate);
|
||||||
|
void SetInfoDelegate(InfoDelegate &&delegate);
|
||||||
|
void SetLsDelegate(LsDelegate &&delegate);
|
||||||
|
void SetDataDelegate(DataDelegate &&delegate);
|
||||||
|
void SetChunkDelegate(ChunkDelegate &&delegate);
|
||||||
|
void SetSearchDelegate(SearchDelegate &&delegate);
|
||||||
|
void SetItemsDelegate(ItemsDelegate &&delegate);
|
||||||
|
void SetStyleDelegate(StyleDelegate &&delegate);
|
||||||
|
void SetEncodingDelegate(EncodingDelegate &&delegate);
|
||||||
|
void SetTempDelegate(TempDelegate &&delegate);
|
||||||
|
void SetRscClearDelegate(RscClearDelegate &&delegate);
|
||||||
|
void SetRscAddDelegate(RscAddDelegate &&delegate);
|
||||||
|
void SetTestDelegate(TestDelegate &&delegate);
|
||||||
|
void SetVersionDelegate(VersionDelegate &&delegate);
|
||||||
|
void SetHelpDelegate(HelpDelegate &&delegate);
|
||||||
|
void SetExitDelegate(ExitDelegate &&delegate);
|
||||||
|
|
||||||
|
private:
|
||||||
|
LoadDelegate load_delegate;
|
||||||
|
UnloadDelegate unload_delegate;
|
||||||
|
SaveDelegate save_delegate;
|
||||||
|
InfoDelegate info_delegate;
|
||||||
|
LsDelegate ls_delegate;
|
||||||
|
DataDelegate data_delegate;
|
||||||
|
ChunkDelegate chunk_delegate;
|
||||||
|
SearchDelegate search_delegate;
|
||||||
|
ItemsDelegate items_delegate;
|
||||||
|
StyleDelegate style_delegate;
|
||||||
|
EncodingDelegate encoding_delegate;
|
||||||
|
TempDelegate temp_delegate;
|
||||||
|
RscClearDelegate rsc_clear_delegate;
|
||||||
|
RscAddDelegate rsc_add_delegate;
|
||||||
|
TestDelegate test_delegate;
|
||||||
|
VersionDelegate version_delegate;
|
||||||
|
HelpDelegate help_delegate;
|
||||||
|
ExitDelegate exit_delegate;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace Unvirt::CmdHelper
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
#include <YYCCommonplace.hpp>
|
|
||||||
#include "UnvirtContext.hpp"
|
#include "UnvirtContext.hpp"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
// register exception handler on windows release
|
|
||||||
#if defined(LIBCMO_BUILD_RELEASE) && (YYCC_OS == YYCC_OS_WINDOWS)
|
|
||||||
YYCC::ExceptionHelper::Register();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// run core
|
// run core
|
||||||
Unvirt::Context::UnvirtContext ctx;
|
Unvirt::Context::UnvirtContext ctx;
|
||||||
ctx.Run();
|
ctx.Run();
|
||||||
|
|
||||||
// unregister exception handler on windows release
|
|
||||||
#if defined(LIBCMO_BUILD_RELEASE) && (YYCC_OS == YYCC_OS_WINDOWS)
|
|
||||||
YYCC::ExceptionHelper::Register();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user