libcmo21/Unvirt/CmdHelper.cpp

352 lines
7.0 KiB
C++
Raw Normal View History

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
2023-08-27 12:30:12 +08:00
std::deque<std::string> CmdSplitter::Convert(const std::string& u8cmd) {
2023-03-03 11:06:26 +08:00
// set up variables
std::deque<std::string> result;
std::string buffer;
mBuffer = &buffer;
mResult = &result;
mState = mPreState = StateType::SPACE;
2023-02-13 10:57:10 +08:00
2023-03-03 11:06:26 +08:00
// split
2023-08-27 12:30:12 +08:00
for (auto& c : u8cmd) {
mCmdChar = c;
2023-03-04 11:11:36 +08:00
// skip all invalid characters, \0 and etc.
// mCmdChar >= 0 to ensure all non-ASCII UTF8 char can be accepted directly.
if (mCmdChar >= 0 && (!std::isprint(mCmdChar)))
continue;
2023-02-13 10:57:10 +08:00
switch (mState) {
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
switch (mState) {
case StateType::SPACE:
break;
case StateType::NORMAL:
// push the last one
mResult->push_back(*mBuffer);
break;
case StateType::SINGLE:
case StateType::DOUBLE:
case StateType::ESCAPE:
// error
result.clear();
break;
}
// return value
return result;
}
2023-02-13 10:57:10 +08:00
2023-08-27 12:30:12 +08:00
void CmdSplitter::ProcSpace(void) {
switch (mCmdChar) {
case '\'':
mState = StateType::SINGLE;
break;
case '"':
mState = StateType::DOUBLE;
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::NORMAL;
break;
case ' ':
break; // skip blank
default:
mBuffer->push_back(mCmdChar);
mState = StateType::NORMAL;
break;
2023-03-03 11:06:26 +08:00
}
}
2023-08-27 12:30:12 +08:00
void CmdSplitter::ProcSingle(void) {
switch (mCmdChar) {
case '\'':
mState = StateType::NORMAL;
break;
case '"':
mBuffer->push_back('"');
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::SINGLE;
break;
case ' ':
mBuffer->push_back(' ');
break;
default:
mBuffer->push_back(mCmdChar);
break;
2023-03-03 11:06:26 +08:00
}
}
2023-08-27 12:30:12 +08:00
void CmdSplitter::ProcDouble(void) {
switch (mCmdChar) {
case '\'':
mBuffer->push_back('\'');
break;
case '"':
mState = StateType::NORMAL;
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::DOUBLE;
break;
case ' ':
mBuffer->push_back(' ');
break;
default:
mBuffer->push_back(mCmdChar);
break;
2023-03-03 11:06:26 +08:00
}
2023-08-27 12:30:12 +08:00
}
void CmdSplitter::ProcEscape(void) {
// add itself
mBuffer->push_back(mCmdChar);
// restore state
mState = mPreState;
}
void CmdSplitter::ProcNormal(void) {
switch (mCmdChar) {
case '\'':
mBuffer->push_back('\'');
break;
case '"':
mBuffer->push_back('"');
break;
case '\\':
mState = StateType::ESCAPE;
mPreState = StateType::NORMAL;
break;
case ' ':
mResult->push_back(*mBuffer);
mBuffer->clear();
mState = StateType::SPACE;
break;
default:
mBuffer->push_back(mCmdChar);
break;
2023-03-03 11:06:26 +08:00
}
}
2023-02-13 10:57:10 +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 12:30:12 +08:00
AbstractNode* AbstractNode::Then(AbstractNode* node) {
// check literal duplication
for (auto& pnode : m_Nodes) {
if (pnode->IsConflictWith(node))
throw std::invalid_argument("conflict node.");
}
// add into list
m_Nodes.emplace_back(node);
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) {
if (m_Execution != nullptr)
throw std::invalid_argument("duplicated executions.");
m_Execution = fct;
m_Comment = cmt;
return this;
2023-03-04 11:11:36 +08:00
}
2023-08-27 12:30:12 +08:00
void AbstractNode::Help(HelpDocument& doc) {
// add self
BeginHelp(doc);
2023-03-04 11:11:36 +08:00
2023-08-27 12:30:12 +08:00
// check terminal
if (m_Execution != nullptr) {
doc.Terminate(m_Comment);
}
2023-03-04 11:11:36 +08:00
2023-08-27 12:30:12 +08:00
// iterate children
for (auto& pnode : m_Nodes) {
pnode->Help(doc);
}
2023-03-03 11:06:26 +08:00
2023-08-27 12:30:12 +08:00
// pop self
EndHelp(doc);
2023-03-04 11:11:36 +08:00
}
2023-08-27 12:30:12 +08:00
bool AbstractNode::Consume(std::deque<std::string>& arglist, ArgumentsMap& argmap) {
// 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
for (auto& pnode : m_Nodes) {
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 12:30:12 +08:00
#pragma region Argument 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 12:30:12 +08:00
m_ParsedData = new int32_t(value);
return true;
2023-03-03 11:06:26 +08:00
}
2023-08-27 12:30:12 +08:00
void IntArgument::EndParse() {
delete reinterpret_cast<int32_t*>(m_ParsedData);
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
m_ParsedData = new std::string(strl);
return true;
2023-03-03 11:06:26 +08:00
}
2023-08-27 12:30:12 +08:00
void StringArgument::EndParse() {
delete reinterpret_cast<std::string*>(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-08-27 12:30:12 +08:00
#pragma region Command Root
2023-03-03 11:06:26 +08:00
2023-08-27 12:30:12 +08:00
bool CommandRoot::RootConsume(std::deque<std::string>& arglist) {
// 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
// create a argument map
ArgumentsMap amap;
2023-03-04 00:13:03 +08:00
2023-08-27 12:30:12 +08:00
// and we only just need iterate all children
for (auto& pnode : m_Nodes) {
if (pnode->Consume(arglist, amap)) {
return true;
2023-03-04 00:13:03 +08:00
}
2023-08-27 12:30:12 +08:00
}
2023-03-04 00:13:03 +08:00
2023-08-27 12:30:12 +08:00
// no matched
return false;
}
2023-03-04 00:13:03 +08:00
2023-08-27 12:30:12 +08:00
HelpDocument* CommandRoot::RootHelp() {
HelpDocument* doc = new HelpDocument();
2023-03-04 00:13:03 +08:00
2023-08-27 12:30:12 +08:00
// we only just need iterate all children
for (auto& pnode : m_Nodes) {
pnode->Help(*doc);
2023-03-04 00:13:03 +08:00
}
2023-03-03 11:06:26 +08:00
2023-08-27 12:30:12 +08:00
return doc;
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-08-27 12:30:12 +08:00
#pragma region Help Document
void HelpDocument::Terminate(std::string& command_desc) {
// create new result and copy stack
ResultItem result(command_desc);
result.m_ArgDesc.insert(result.m_ArgDesc.end(), m_Stack.begin(), m_Stack.end());
// add into result
m_Results.emplace_back(std::move(result));
2023-03-03 16:05:32 +08:00
}
2023-08-27 12:30:12 +08:00
void HelpDocument::Print() {
fputs("Command Help:\n", stdout);
2023-02-13 10:57:10 +08:00
2023-08-27 12:30:12 +08:00
for (auto& item : m_Results) {
for (auto& cmd : item.m_ArgDesc) {
fputs(cmd.m_Name.c_str(), stdout);
fputc(' ', stdout);
2023-02-14 16:28:37 +08:00
}
2023-08-27 12:30:12 +08:00
fputc('\n', stdout);
2023-02-13 10:57:10 +08:00
2023-08-27 12:30:12 +08:00
if (!item.m_CmdDesc.empty()) {
fputs(item.m_CmdDesc.c_str(), stdout);
fputc('\n', stdout);
}
2023-02-13 10:57:10 +08:00
2023-08-27 12:30:12 +08:00
for (auto& cmd : item.m_ArgDesc) {
if (!cmd.m_Desc.empty()) {
fprintf(stdout, "\t%s: %s\n", cmd.m_Name.c_str(), cmd.m_Desc.c_str());
}
}
2023-02-13 22:13:30 +08:00
2023-08-27 12:30:12 +08:00
fputc('\n', stdout);
2023-02-13 22:13:30 +08:00
}
2023-03-04 11:11:36 +08:00
}
2023-02-13 10:57:10 +08:00
2023-02-14 16:28:37 +08:00
#pragma endregion
2023-02-13 10:57:10 +08:00
2023-08-27 12:30:12 +08:00
2023-03-03 11:06:26 +08:00
}