libcmo21/Unvirt/CmdHelper.cpp

575 lines
16 KiB
C++
Raw Normal View History

2023-02-13 10:57:10 +08:00
#include "CmdHelper.hpp"
#include <VTAll.hpp>
#include <algorithm>
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::Lex(const std::u8string& u8cmd) {
2024-08-24 21:27:23 +08:00
// 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
ArgumentsMap::ArgumentsMap() : m_Data() {}
ArgumentsMap::~ArgumentsMap() {}
2024-08-24 21:27:23 +08:00
#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
// check whether all description of argument are emtpty.
bool empty_arg_desc = true;
YYCC::ConsoleHelper::Write(YYCC_COLOR_LIGHT_YELLOW(u8"Syntax: "));
2024-08-24 21:27:23 +08:00
for (const auto& arg : cmd.m_ArgDesc) {
if (!arg.m_Desc.empty()) empty_arg_desc = false;
2024-08-24 21:27:23 +08:00
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(YYCC_COLOR_LIGHT_YELLOW(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
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()
);
}
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
#pragma region Conflict Set
ConflictSet::ConflictSet() : m_ConflictSet() {}
ConflictSet::~ConflictSet() {}
void ConflictSet::AddLiteral(const std::u8string_view& value) {
if (value.empty())
throw std::invalid_argument("try to insert empty item to conflict set.");
auto result = m_ConflictSet.emplace(u8"literal:" + std::u8string(value));
if (!result.second)
throw std::runtime_error("try to insert duplicated item in conflict set.");
}
void ConflictSet::AddArgument(const std::u8string_view& value) {
if (value.empty())
throw std::invalid_argument("try to insert empty item to conflict set.");
auto result = m_ConflictSet.emplace(u8"argument:" + std::u8string(value));
if (!result.second)
throw std::runtime_error("try to insert duplicated item in conflict set.");
}
bool ConflictSet::IsConflictWith(const ConflictSet& rhs) const {
// create a cache to store computed intersection
std::vector<std::u8string> intersection;
// compute intersection
std::set_intersection(
this->m_ConflictSet.begin(), this->m_ConflictSet.end(),
rhs.m_ConflictSet.begin(), rhs.m_ConflictSet.begin(),
std::back_inserter(intersection)
);
// check whether it is empty intersection
return !intersection.empty();
}
2023-08-27 14:21:44 +08:00
#pragma endregion
namespace Nodes {
2023-08-27 12:30:12 +08:00
#pragma region Abstract Node
2023-03-04 11:11:36 +08:00
AbstractNode::AbstractNode() :
m_Execution(nullptr), m_Comment(), m_Nodes() {}
2023-08-27 14:21:44 +08:00
AbstractNode::~AbstractNode() {}
2023-08-27 14:21:44 +08:00
AbstractNode& AbstractNode::Executes(FctExecution_t fct, const std::u8string_view& exec_desc) {
if (this->IsRootNode())
throw std::logic_error("root node should not have execution.");
if (m_Execution != nullptr)
throw std::invalid_argument("you should not assign execution multiuple times.");
if (fct == nullptr)
throw std::invalid_argument("the function passed for executing should not be nullptr.");
m_Execution = fct;
m_ExecutionDesc = exec_desc;
return *this;
2023-08-27 14:21:44 +08:00
}
AbstractNode& AbstractNode::Comment(const std::u8string_view& comment) {
if (this->IsRootNode())
throw std::logic_error("root node should not have comment.");
m_Comment = comment;
return *this;
2023-08-27 14:21:44 +08:00
}
void AbstractNode::Help(HelpDocument& doc) {
// If this node is not root node
if (!this->IsRootNode()) {
// Push self symbol to help document stack.
if (!this->IsRootNode()) {
doc.Push(GetHelpSymbol(), m_Comment);
}
2023-08-27 14:21:44 +08:00
// Check whether this node is terminal.
// If it is, terminate it once.
if (m_Execution != nullptr) {
doc.Terminate(m_ExecutionDesc);
}
}
2023-03-04 11:11:36 +08:00
// Then process its children nodes (both root node and common node).
for (auto& node : m_Nodes) {
node->Help(doc);
}
2023-03-04 11:11:36 +08:00
// Pop self from help document stack
// if this node is not root node
if (!this->IsRootNode()) {
doc.Pop();
}
2023-08-27 14:21:44 +08:00
}
2023-03-04 11:11:36 +08:00
bool AbstractNode::Consume(CmdSplitter::Result_t& al, ArgumentsMap& am) {
// If no command to be consumed, return directly.
if (al.empty()) return false;
2023-03-03 11:06:26 +08:00
// 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();
}
2023-03-03 11:06:26 +08:00
// Define free function if we are not the root node.
// Because we just pop the top item of command stack.
2023-08-27 12:30:12 +08:00
#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) {
2023-08-27 14:21:44 +08:00
CONSUME_DEFER;
return false;
} else {
m_Execution(am);
2023-08-27 14:21:44 +08:00
CONSUME_DEFER;
return true;
}
} else {
// Command stack still item to be consumed.
// 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;
}
2023-08-27 12:30:12 +08:00
}
2023-03-03 16:05:32 +08:00
// 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;
}
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-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
#pragma region Root Node
RootNode::RootNode() : AbstractNode() {}
RootNode::~RootNode() {}
bool RootNode::IsRootNode() { return true; }
bool RootNode::IsArgument() { throw std::logic_error("this function is not allowed on root function."); }
const ConflictSet& RootNode::GetConflictSet() { throw std::logic_error("this function is not allowed on root function."); }
std::u8string RootNode::GetHelpSymbol() { throw std::logic_error("this function is not allowed on root function."); }
bool RootNode::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { throw std::logic_error("this function is not allowed on root function."); }
void RootNode::EndConsume(ArgumentsMap& am) { throw std::logic_error("this function is not allowed on root function."); }
#pragma endregion
#pragma region Literal
2023-08-27 14:21:44 +08:00
Literal::Literal(const std::u8string_view& words) :
AbstractNode(), m_Literal(words), m_ConflictSet() {
// check argument
if (words.empty())
throw std::invalid_argument("The word of literal node should not be empty.");
// set conflict set
m_ConflictSet.AddLiteral(m_Literal);
}
Literal::~Literal() {}
2023-08-27 14:21:44 +08:00
bool Literal::IsRootNode() { return false; }
bool Literal::IsArgument() { return false; }
const ConflictSet& Literal::GetConflictSet() { return m_ConflictSet; }
std::u8string Literal::GetHelpSymbol() { return m_Literal; }
bool Literal::BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) { return cur_cmd == m_Literal; }
void Literal::EndConsume(ArgumentsMap& am) {}
2023-08-27 14:21:44 +08:00
#pragma endregion
2023-08-27 14:21:44 +08:00
#pragma region Choice
2023-08-27 14:21:44 +08:00
Choice::Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary) :
AbstractNode(),
m_ChoiceName(argname), m_Vocabulary(vocabulary),
m_ConflictSet() {
// check argument
if (argname.empty())
throw std::invalid_argument("Choice argument name should not be empty.");
if (m_Vocabulary.size() < 2u)
throw std::invalid_argument("Too less vocabulary for choice. At least 2 items.");
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);
}
}
Choice::~Choice() {}
bool Choice::IsRootNode() { return false; }
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;
2023-08-27 14:21:44 +08:00
}
void Choice::EndConsume(ArgumentsMap& am) { am.Remove(m_ChoiceName); }
2023-08-27 14:21:44 +08:00
#pragma endregion
#pragma region Abstract Argument
2023-08-27 14:21:44 +08:00
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() {}
2023-08-27 14:21:44 +08:00
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); }
2023-08-27 14:21:44 +08:00
#pragma endregion
2023-08-27 14:21:44 +08:00
#pragma region Arithmetic Argument
2023-08-27 14:21:44 +08:00
// It's a template class
// We are forced to implement it in header file.
2023-08-27 14:21:44 +08:00
#pragma endregion
#pragma region String Argument
2023-08-27 14:21:44 +08:00
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))
2023-08-27 14:21:44 +08:00
return false;
// accept
am.Add<ArgValue_t>(m_ArgumentName, cur_cmd);
return true;
2023-08-27 14:21:44 +08:00
}
#pragma endregion
2023-03-03 11:06:26 +08:00
}
#pragma region Command Parser
2023-03-03 11:06:26 +08:00
CommandParser::CommandParser() {}
2023-03-03 11:06:26 +08:00
CommandParser::~CommandParser() {}
2023-03-03 11:06:26 +08:00
bool CommandParser::Parse(const CmdSplitter::Result_t& cmds) {
// Create a copy of given command
CmdSplitter::Result_t al(cmds);
// Create argument map
ArgumentsMap am;
2023-03-03 11:06:26 +08:00
// Call root node Consume function and return its result.
return m_RootNode.Consume(al, am);
2023-08-27 14:21:44 +08:00
}
2023-03-04 00:13:03 +08:00
HelpDocument CommandParser::Help() {
// Create help docuemnt
HelpDocument doc;
// use node tree to fill help document.
m_RootNode.Help(doc);
// return result.
return doc;
2023-03-03 11:06:26 +08:00
}
Nodes::RootNode& CommandParser::GetRoot() {
return m_RootNode;
}
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
}