diff --git a/README.md b/README.md index b0e3341..7d9eef4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The Library for CMO File Read/Write. Also the Minimalist Virtools Environment. ## Status -This is a long time project and I am stucked at the analyze of CKBeObject. I don't know its class layout, And I don't know its complex `CKBeObject::Save()`. +This is a long time project. This project welcome everyone's contribution, except the employee of Dassault, which created Virtools. ## Introduction @@ -35,9 +35,8 @@ There are 3 lists which indicate our accept guideline. These features will be accepted as soon as possible. -* The save steps of Virtools file. * The bug fix of Virtools file reader. -* Class layout, `Save()` and `Load()` functions of following `CKObject` based classes. +* Class layout, `Load()` functions of following `CKObject` based classes. - `CKBeObject` - `CKGroup` - `CKMaterial` @@ -46,7 +45,7 @@ These features will be accepted as soon as possible. - `CKRenderObject` - `CK3dEntity` - `CK3dObject` -* Class layout, `SaveData()` and `LoadData()` functions of following `CKBaseManager` based classes. +* Class layout, and `LoadData()` functions of following `CKBaseManager` based classes. - `CKAttributeManager` - `CKBehaviorManager` @@ -55,6 +54,8 @@ These features will be accepted as soon as possible. These features are in plan, but not urge to merge. * The `CK_ID` remap system of Reader & Writer. +* Any Save functions. +* The save steps of Virtools file. * Other CK classes implementations. * Non-Virtools 2.1 implementations. @@ -74,4 +75,5 @@ This project require: * zlib * iconv (non-Windows system required) -It can be compiled on Windows via sln file, or on Linux platform via CMake file. +It can be compiled on Windows via sln file, or on Linux platform via CMake file. +However CMake may not be updated in time because I develop this project on Windows frequently. diff --git a/Unvirt/CmdHelper.cpp b/Unvirt/CmdHelper.cpp index a50dca0..a473f1e 100644 --- a/Unvirt/CmdHelper.cpp +++ b/Unvirt/CmdHelper.cpp @@ -1,32 +1,10 @@ #include "CmdHelper.hpp" -#include "TerminalHelper.hpp" -#include "StructFormatter.hpp" -#include "AccessibleValue.hpp" - -#include -#include - -#include -#include -#include -#include namespace Unvirt::CmdHelper { - static FILE* fout = stdout; - #pragma region CmdSplitter - CmdSplitter::CmdSplitter() : - mCmdChar(0), mBuffer(nullptr), mResult(nullptr), - mState(StateType::NORMAL), mPreState(StateType::NORMAL) { - ; - } - CmdSplitter::~CmdSplitter() { - ; - } - - const std::deque CmdSplitter::Convert(const std::string& u8cmd) { + std::deque CmdSplitter::Convert(const std::string& u8cmd) { // set up variables std::deque result; std::string buffer; @@ -35,8 +13,8 @@ namespace Unvirt::CmdHelper { mState = mPreState = StateType::SPACE; // split - for (auto it = u8cmd.begin(); it != u8cmd.end(); ++it) { - mCmdChar = (*it); + for (auto& c : u8cmd) { + mCmdChar = c; // skip all invalid characters, \0 and etc. // mCmdChar >= 0 to ensure all non-ASCII UTF8 char can be accepted directly. @@ -82,367 +60,292 @@ namespace Unvirt::CmdHelper { return result; } + 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; + } + } + 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; + } + } + 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; + } + } + 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; + } + } #pragma endregion -#pragma region ArgParser +#pragma region Abstract Node - bool ArgParser::ParseInt(const std::deque& cmd, const size_t expected_index, int32_t& result) { - if (expected_index >= cmd.size()) { - result = 0; + 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; + } + + 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; + } + + void AbstractNode::Help(HelpDocument& doc) { + // add self + BeginHelp(doc); + + // check terminal + if (m_Execution != nullptr) { + doc.Terminate(m_Comment); + } + + // iterate children + for (auto& pnode : m_Nodes) { + pnode->Help(doc); + } + + // pop self + EndHelp(doc); + } + + bool AbstractNode::Consume(std::deque& arglist, ArgumentsMap& argmap) { + // if no data can consume, return + if (arglist.empty()) return false; + + // backup current value + std::string cur = arglist.front(); + // consume self + if (!BeginAccept(cur, argmap)) { + // fail to consume self. not matched. return return false; } + // pop front for following code + arglist.pop_front(); + +#define CONSUME_DEFER \ + 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; + } + } 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; + } + } + + // if still nothing to match, return false + CONSUME_DEFER; + return false; + } + +#undef CONSUME_DEFER + + } + +#pragma endregion + +#pragma region Argument Impl + + bool IntArgument::BeginParse(const std::string& val) { char* pend = nullptr; errno = 0; - int64_t v = std::strtoll(cmd[expected_index].c_str(), &pend, 10); + int64_t v = std::strtoll(val.c_str(), &pend, 10); - if (pend == cmd[expected_index].c_str() || errno == ERANGE) { - result = 0; + if (pend == val.c_str() || errno == ERANGE) return false; + + // check limit + int32_t value = static_cast(v); + if (m_IntLimit != nullptr && !m_IntLimit(value)) { return false; } - result = static_cast(v); + m_ParsedData = new int32_t(value); return true; } - bool ArgParser::ParseString(const std::deque& cmd, const size_t expected_index, std::string& result) { - if (expected_index >= cmd.size()) { - result.clear(); - return false; - } else { - result = cmd[expected_index]; - return true; - } + void IntArgument::EndParse() { + delete reinterpret_cast(m_ParsedData); + m_ParsedData = nullptr; } - bool ArgParser::ParseSwitch(const std::deque& cmd, const size_t expected_index, const std::vector& switches, std::string& gotten) { - if (expected_index >= cmd.size()) { - gotten.clear(); - return false; - } + bool StringArgument::BeginParse(const std::string& strl) { + // string always accept every text + m_ParsedData = new std::string(strl); + return true; + } - for (const auto& sw : switches) { - if (cmd[expected_index] == sw) { - gotten = cmd[expected_index]; + void StringArgument::EndParse() { + delete reinterpret_cast(m_ParsedData); + m_ParsedData = nullptr; + } + +#pragma endregion + +#pragma region Command Root + + bool CommandRoot::RootConsume(std::deque& arglist) { + // if no data can consume, return + if (arglist.empty()) return false; + + // create a argument map + ArgumentsMap amap; + + // and we only just need iterate all children + for (auto& pnode : m_Nodes) { + if (pnode->Consume(arglist, amap)) { return true; } } - gotten.clear(); + // no matched return false; } -#pragma endregion + HelpDocument* CommandRoot::RootHelp() { + HelpDocument* doc = new HelpDocument(); -#pragma region InteractiveCmd Misc + // we only just need iterate all children + for (auto& pnode : m_Nodes) { + pnode->Help(*doc); + } - InteractiveCmd::InteractiveCmd() : - m_CmdSplitter(), m_PageLen(10), - m_Ctx(nullptr), m_File(nullptr), m_Doc(nullptr) { - - // create context and file - m_Ctx = new LibCmo::CK2::CKMinContext(); - m_File = new LibCmo::CK2::CKFile(m_Ctx); - - // bind callback - m_Ctx->SetPrintCallback(std::bind(&InteractiveCmd::PrintMinContextMsg, this, std::placeholders::_1)); - - } - - InteractiveCmd::~InteractiveCmd() { - // delete doc if necessary - if (m_Doc != nullptr) delete m_Doc; - // delete file and ctx - delete m_File; - delete m_Ctx; - } - - bool InteractiveCmd::HasOpenedFile(void) { - return m_Doc != nullptr; - } - - void InteractiveCmd::ClearDocument(void) { - if (m_Doc == nullptr) return; - - // clear doc - delete m_Doc; - m_Doc = nullptr; - - // clear all loaded objects - m_Ctx->ClearCKObject(); - } - - void InteractiveCmd::PrintMinContextMsg(const std::string& msg) { - fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("[CKMinContext] ")), fout); - fputs(msg.c_str(), fout); - fputc('\n', fout); + return doc; } #pragma endregion -#pragma region InteractiveCmd Dispatch +#pragma region Help Document - void InteractiveCmd::Run(void) { - std::string u8cmd; + 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)); + } - while (true) { - // get command - GetCmdLine(u8cmd); + void HelpDocument::Print() { + fputs("Command Help:\n", stdout); - // split cmd and parse it - auto cmds = m_CmdSplitter.Convert(u8cmd); - - // get sub command - if (cmds.size() < 1u) { - this->PrintCommonError("No command specified!"); - this->PrintHelp(); - continue; + for (auto& item : m_Results) { + for (auto& cmd : item.m_ArgDesc) { + fputs(cmd.m_Name.c_str(), stdout); + fputc(' ', stdout); } - std::string subcmd(cmds.front()); - cmds.pop_front(); + fputc('\n', stdout); - // dispatch command - if (subcmd == "load") this->ProcLoad(cmds); - else if (subcmd == "unload") this->ProcUnLoad(cmds); - else if (subcmd == "info") this->ProcInfo(cmds); - else if (subcmd == "ls") this->ProcLs(cmds); - else if (subcmd == "items") this->ProcItems(cmds); - else if (subcmd == "encoding") this->ProcEncoding(cmds); - else if (subcmd == "temp") this->ProcTemp(cmds); - else if (subcmd == "help") this->PrintHelp(); - else if (subcmd == "exit") break; - else { - this->PrintCommonError("No such command \"%s\".", subcmd.c_str()); - this->PrintHelp(); + if (!item.m_CmdDesc.empty()) { + fputs(item.m_CmdDesc.c_str(), stdout); + fputc('\n', stdout); } - + + 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()); + } + } + + fputc('\n', stdout); } } - void InteractiveCmd::GetCmdLine(std::string& u8cmd) { - fputs("Unvirt> ", fout); -#if defined(LIBCMO_OS_WIN32) - std::wstring wcmd; - std::getline(std::wcin, wcmd); - LibCmo::EncodingHelper::WcharToChar(wcmd, u8cmd, CP_UTF8); -#else - std::getline(std::cin, u8cmd); -#endif - } - - void InteractiveCmd::PrintHelp(void) { - fputs(UNVIRT_TERMCOL_LIGHT_YELLOW(("Allowed Subcommands: \n")), fout); - - fputs("load\n", fout); - fputs("\tDescription: Load a Virtools composition.\n", fout); - fputs("\tSyntax: load \n", fout); - - fputs("unload\n", fout); - fputs("\tDescription: Release loaded Virtools composition.\n", fout); - fputs("\tSyntax: unload\n", fout); - - fputs("info\n", fout); - fputs("\tDescription: Show the header info of loaded Virtools composition.\n", fout); - fputs("\tSyntax: info\n", fout); - - fputs("ls\n", fout); - fputs("\tDescription: List something about loaded Virtools composition.\n", fout); - fputs("\tSyntax: ls [page]\n", fout); - - fputs("items\n", fout); - fputs("\tDescription: Set up how many items should be listed in one page when using \"ls\" command.\n", fout); - fputs("\tSyntax: items \n", fout); - - fputs("encoding\n", fout); - fputs("\tDescription: Set the encoding series for CKMinContext.\n", fout); - fputs("\tSyntax: encoding [encoding name1] [encoding name2] [encoding name3] ...\n", fout); - - fputs("temp\n", fout); - fputs("\tDescription: Set the Temp path for CKMinContext.\n", fout); - fputs("\tSyntax: temp \n", fout); - - fputs("exit\n", fout); - fputs("\tDescription: Exit program\n", fout); - fputs("\tSyntax: exit\n", fout); - - } - - void InteractiveCmd::PrintArgParseError(const std::deque& cmd, size_t pos) { - if (pos >= cmd.size()) { - fprintf(fout, UNVIRT_TERMCOL_LIGHT_RED(("Lost argument at position %zu.\n")), pos); - } else { - fprintf(fout, UNVIRT_TERMCOL_LIGHT_RED(("Unexpected argument \"%s\".\n")), cmd[pos].c_str()); - } - - // arg error always print help - this->PrintHelp(); - } - - void InteractiveCmd::PrintCommonError(const char* u8_fmt, ...) { - va_list argptr; - va_start(argptr, u8_fmt); - std::fputs(UNVIRT_TERMCOLHDR_LIGHT_RED, fout); - std::vfprintf(fout, u8_fmt, argptr); - std::fputs(UNVIRT_TERMCOLTAIL, fout); - va_end(argptr); - std::fputc('\n', fout); - } - - #pragma endregion -#pragma region InteractiveCmd Processors - - void InteractiveCmd::ProcLoad(const std::deque& cmd) { - // check pre-requirement - if (HasOpenedFile()) { - this->PrintCommonError("Already have a opened file. Close it before calling \"load\"."); - return; - } - - // check requirement - size_t pos = 0u; - std::string filepath; - if (!ArgParser::ParseString(cmd, pos, filepath)) { - this->PrintArgParseError(cmd, pos); - return; - } - - // proc - m_Doc = new LibCmo::CK2::CKFileDocument(); - LibCmo::CK2::CKERROR err = m_File->DeepLoad(filepath.c_str(), &m_Doc); - if (err != LibCmo::CK2::CKERROR::CKERR_OK) { - // fail to load. release all. - this->PrintCommonError("Fail to open file. Function return: %s\n%s", - Unvirt::AccessibleValue::GetCkErrorName(err).c_str(), - Unvirt::AccessibleValue::GetCkErrorDescription(err).c_str() - ); - - this->ClearDocument(); - } - } - - void InteractiveCmd::ProcUnLoad(const std::deque& cmd) { - // check pre-requirement - if (!HasOpenedFile()) { - this->PrintCommonError("No loaded file."); - return; - } - - // free all - this->ClearDocument(); - } - - void InteractiveCmd::ProcInfo(const std::deque& cmd) { - // check pre-requirement - if (!HasOpenedFile()) { - this->PrintCommonError("No loaded file."); - return; - } - - // print - Unvirt::StructFormatter::PrintCKFileInfo(m_Doc->m_FileInfo); - } - - void InteractiveCmd::ProcLs(const std::deque& cmd) { - // static values of switches - static const std::vector c_AllowedSwitches{ - "obj", "mgr" - }; - - // check pre-requirement - if (!HasOpenedFile()) { - this->PrintCommonError("No loaded file."); - return; - } - - // check requirement - size_t pos = 0u; - std::string sw; - if (!ArgParser::ParseSwitch(cmd, pos, c_AllowedSwitches, sw)) { - this->PrintArgParseError(cmd, pos); - return; - } - ++pos; - int32_t gotten_page; - if (!ArgParser::ParseInt(cmd, pos, gotten_page)) { - gotten_page = 1; // asssume as 1 - } - if (gotten_page <= 0) { - this->PrintCommonError("Page out of range."); - return; - } - size_t page = static_cast(gotten_page) - 1; - - // show list - if (sw == c_AllowedSwitches[0]) { - // obj list - if (page * this->m_PageLen >= m_Doc->m_FileObjects.size()) { - this->PrintCommonError("Page out of range."); - return; - } - - Unvirt::StructFormatter::PrintObjectList(this->m_Doc->m_FileObjects, page, this->m_PageLen); - - } else { - // mgr list - if (page * this->m_PageLen >= m_Doc->m_FileManagersData.size()) { - this->PrintCommonError("Page out of range."); - return; - } - - Unvirt::StructFormatter::PrintManagerList(this->m_Doc->m_FileManagersData, page, this->m_PageLen); - } - - } - - void InteractiveCmd::ProcItems(const std::deque& cmd) { - // check requirement - size_t pos = 0u; - int32_t count; - if (!ArgParser::ParseInt(cmd, pos, count) || count <= 0) { - this->PrintArgParseError(cmd, pos); - return; - } - - // assign - m_PageLen = static_cast(count); - } - - void InteractiveCmd::ProcEncoding(const std::deque& cmd) { - // create list first - std::vector encoding_list; - - // get list item - size_t pos = 0u; - while (true) { - std::string pending; - if (!ArgParser::ParseString(cmd, pos, pending)) { - break; // no more encoding, break - } - - // add and move to next - ++pos; - encoding_list.push_back(std::move(pending)); - } - - // apply list - this->m_Ctx->SetEncoding(encoding_list); - } - - void InteractiveCmd::ProcTemp(const std::deque& cmd) { - // check requirement - size_t pos = 0u; - std::string temppath; - if (!ArgParser::ParseString(cmd, pos, temppath)) { - this->PrintArgParseError(cmd, pos); - return; - } - - // assign - m_Ctx->SetTempPath(temppath.c_str()); - } - -#pragma endregion } diff --git a/Unvirt/CmdHelper.hpp b/Unvirt/CmdHelper.hpp index f923fc6..77c2144 100644 --- a/Unvirt/CmdHelper.hpp +++ b/Unvirt/CmdHelper.hpp @@ -1,26 +1,19 @@ #pragma once -#include -#include +#include #include #include +#include #include +#include +#include +#include +#include namespace Unvirt::CmdHelper { class CmdSplitter { public: - CmdSplitter(); - CmdSplitter(const CmdSplitter&) = delete; - CmdSplitter& operator=(const CmdSplitter&) = delete; - ~CmdSplitter(); - - const std::deque Convert(const std::string& u8cmd); - private: - char mCmdChar; - std::string* mBuffer; - std::deque* mResult; - enum class StateType : int { SPACE, SINGLE, @@ -28,142 +21,312 @@ namespace Unvirt::CmdHelper { ESCAPE, NORMAL }; + public: + CmdSplitter() : + mCmdChar(0), mBuffer(nullptr), mResult(nullptr), + mState(StateType::NORMAL), mPreState(StateType::NORMAL) {} + ~CmdSplitter() {} + LIBCMO_DISABLE_COPY_MOVE(CmdSplitter); + + std::deque Convert(const std::string& u8cmd); + protected: + char mCmdChar; + std::string* mBuffer; + std::deque* mResult; + StateType mState, mPreState; - inline void 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; - } - } - inline void 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; - } - } - inline void 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; - } - } - inline void ProcEscape(void) { - // add itself - mBuffer->push_back(mCmdChar); - // restore state - mState = mPreState; - } - inline void 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; - } - } + void ProcSpace(void); + void ProcSingle(void); + void ProcDouble(void); + void ProcEscape(void); + void ProcNormal(void); }; - class ArgParser { + class HelpDocument { public: - ArgParser() {} - ArgParser(const ArgParser&) = delete; - ArgParser& operator=(const ArgParser&) = delete; - ~ArgParser() {} + HelpDocument() : m_Stack(), m_Results() {} + ~HelpDocument() {} + LIBCMO_DISABLE_COPY_MOVE(HelpDocument); - static bool ParseInt(const std::deque& cmd, const size_t expected_index, int32_t& result); - static bool ParseString(const std::deque& cmd, const size_t expected_index, std::string& result); - static bool ParseSwitch(const std::deque& cmd, const size_t expected_index, const std::vector& switches, std::string& gotten); + void PushLiteral(const std::string& literal_name) { + m_Stack.emplace_back(StackItem(literal_name, "")); + } + void PushArgument(const std::string& arg_name, const std::string& arg_desc) { + m_Stack.emplace_back(StackItem(arg_name, arg_desc)); + } + void Pop() { + m_Stack.pop_back(); + } + void Terminate(std::string& command_desc); + void Print(); + + protected: + struct StackItem { + StackItem() : m_Name(), m_Desc() {} + StackItem(const std::string& name, const std::string& desc) : m_Name(name), m_Desc(desc) {} + LIBCMO_DEFAULT_COPY_MOVE(StackItem); + std::string m_Name; + std::string m_Desc; + }; + std::deque m_Stack; + struct ResultItem { + ResultItem() : m_CmdDesc(), m_ArgDesc() {} + ResultItem(const std::string& desc) : m_CmdDesc(desc), m_ArgDesc() {} + LIBCMO_DEFAULT_COPY_MOVE(ResultItem); + std::string m_CmdDesc; + std::vector m_ArgDesc; + }; + std::vector m_Results; }; - class InteractiveCmd { + class AbstractArgument; + class ArgumentsMap { public: - InteractiveCmd(); - InteractiveCmd(const InteractiveCmd&) = delete; - InteractiveCmd& operator=(const InteractiveCmd&) = delete; - ~InteractiveCmd(); + ArgumentsMap() : m_Data() {} + ~ArgumentsMap() {} + LIBCMO_DISABLE_COPY_MOVE(ArgumentsMap); - void Run(void); + void Add(const std::string& k, AbstractArgument* v) { + m_Data.emplace(std::make_pair(k, v)); + } + void Remove(const std::string& k) { + m_Data.erase(k); + } + AbstractArgument* Get(const char* k) const { + auto finder = m_Data.find(k); + if (finder == m_Data.end()) throw std::invalid_argument("No such argument name."); + return finder->second; + } - private: - void GetCmdLine(std::string& u8cmd); - void PrintHelp(void); - void PrintArgParseError(const std::deque& cmd, size_t pos); - void PrintCommonError(const char* u8_fmt, ...); - - void ProcLoad(const std::deque& cmd); - void ProcUnLoad(const std::deque& cmd); - void ProcInfo(const std::deque& cmd); - void ProcLs(const std::deque& cmd); - void ProcItems(const std::deque& cmd); - void ProcEncoding(const std::deque& cmd); - void ProcTemp(const std::deque& cmd); - - bool HasOpenedFile(void); - void ClearDocument(void); - void PrintMinContextMsg(const std::string& msg); - - CmdSplitter m_CmdSplitter; - size_t m_PageLen; - LibCmo::CK2::CKMinContext* m_Ctx; - LibCmo::CK2::CKFile* m_File; - LibCmo::CK2::CKFileDocument* m_Doc; + protected: + std::unordered_map m_Data; }; -} \ No newline at end of file + class CommandRoot; + using ExecutionFct = std::function; + class AbstractNode { + friend class CommandRoot; + public: + AbstractNode() : m_Execution(nullptr), m_Nodes(), m_Comment() {} + virtual ~AbstractNode() { + for (auto& ptr : m_Nodes) { + delete ptr; + } + } + LIBCMO_DISABLE_COPY_MOVE(AbstractNode); + + AbstractNode* Then(AbstractNode*); + AbstractNode* Executes(ExecutionFct, const char*); + + protected: + void Help(HelpDocument&); + bool Consume(std::deque&, ArgumentsMap&); + virtual bool IsLiteral() = 0; + virtual bool IsConflictWith(AbstractNode*) = 0; + virtual void BeginHelp(HelpDocument&) = 0; + virtual void EndHelp(HelpDocument&) = 0; + virtual bool BeginAccept(const std::string&, ArgumentsMap&) = 0; + virtual void EndAccept(ArgumentsMap&) = 0; + + std::vector m_Nodes; + ExecutionFct m_Execution; + std::string m_Comment; + }; + + class CommandRoot : public AbstractNode { + public: + CommandRoot() : AbstractNode() {} + virtual ~CommandRoot() {} + LIBCMO_DISABLE_COPY_MOVE(CommandRoot); + + // Root use special consume and help functions. + bool RootConsume(std::deque&); + HelpDocument* RootHelp(); + + protected: + virtual bool IsLiteral() override { throw std::logic_error("Root can not be called."); } + virtual bool IsConflictWith(AbstractNode*) override { throw std::logic_error("Root can not be called."); } + virtual void BeginHelp(HelpDocument&) override { throw std::logic_error("Root can not be called."); } + virtual void EndHelp(HelpDocument&) override { throw std::logic_error("Root can not be called."); } + virtual bool BeginAccept(const std::string&, ArgumentsMap&) override { throw std::logic_error("Root can not be called."); } + virtual void EndAccept(ArgumentsMap&) override { throw std::logic_error("Root can not be called."); } + }; + + class Literal : public AbstractNode { + friend class Choice; + public: + Literal(const char* words) : AbstractNode(), m_Literal(words) { + if (m_Literal.empty() || words == nullptr) throw std::invalid_argument("Invalid literal."); + } + virtual ~Literal() {} + LIBCMO_DISABLE_COPY_MOVE(Literal); + + protected: + virtual bool IsLiteral() override { return true; } + virtual bool IsConflictWith(AbstractNode* node) override { + Literal* pliteral = dynamic_cast(node); + if (pliteral == nullptr) return false; + return pliteral->m_Literal == this->m_Literal; + } + virtual void BeginHelp(HelpDocument& doc) override { + doc.PushLiteral(m_Literal); + } + virtual void EndHelp(HelpDocument& doc) override { + doc.Pop(); + } + virtual bool BeginAccept(const std::string& strl, ArgumentsMap&) override { return strl == m_Literal; } + virtual void EndAccept(ArgumentsMap&) override {} + + std::string m_Literal; + }; + + class Choice : public AbstractNode { + public: + Choice(const char* argname, const std::initializer_list& vocabulary) : + AbstractNode(), m_ChoiceName(argname == nullptr ? "" : argname), m_Vocabulary(vocabulary) { + if (m_ChoiceName.empty() || argname == nullptr) throw std::invalid_argument("Invalid choice name."); + if (m_Vocabulary.size() < 2) throw std::invalid_argument("Too less vocabulary. At least 2 items."); + } + virtual ~Choice() {} + LIBCMO_DISABLE_COPY_MOVE(Choice) + + protected: + virtual bool IsLiteral() override { return true; } + virtual bool IsConflictWith(AbstractNode* node) override { + Literal* pliteral = dynamic_cast(node); + if (pliteral != nullptr) { + for (const auto& word : m_Vocabulary) { + if (word == pliteral->m_Literal) + return true; + } + return false; + } + + Choice* pchoice = dynamic_cast(node); + if (pchoice != nullptr) { + for (const auto& thisword : m_Vocabulary) { + for (const auto& thatword : pchoice->m_Vocabulary) { + if (thisword == thatword) + return true; + } + } + return false; + } + + return false; + } + virtual void BeginHelp(HelpDocument& doc) override { + std::string switches; + for (const auto& item : m_Vocabulary) { + if (!switches.empty()) switches += " | "; + switches += item; + } + doc.PushLiteral("[" + switches + "]"); + } + virtual void EndHelp(HelpDocument& doc) override { + doc.Pop(); + } + virtual bool BeginAccept(const std::string& strl, ArgumentsMap& amap) override { + for (const auto& item : m_Vocabulary) { + if (strl == item) { + + } + } + } + virtual void EndAccept(ArgumentsMap&) override {} + + std::string m_ChoiceName; + std::vector m_Vocabulary; + }; + + class AbstractArgument : public AbstractNode { + public: + AbstractArgument(const char* argname, const char* argdesc) : AbstractNode(), + m_ArgName(argname == nullptr ? "" : argname), m_ArgDesc(argdesc), m_Accepted(false), m_ParsedData(nullptr) { + if (m_ArgName.empty() || argname == nullptr) throw std::invalid_argument("Invalid argument name."); + } + virtual ~AbstractArgument() {} + LIBCMO_DISABLE_COPY_MOVE(AbstractArgument); + + template + T* GetData() { return reinterpret_cast(m_ParsedData); } + + protected: + virtual bool BeginParse(const std::string&) = 0; + virtual void EndParse() = 0; + + virtual bool IsLiteral() override { return false; } + virtual bool IsConflictWith(AbstractNode* node) override { + return false; + } + virtual void BeginHelp(HelpDocument& doc) override { + std::string newargname = "<"; + newargname.append(m_ArgName); + newargname.append(">"); + doc.PushArgument(newargname, m_ArgDesc); + } + virtual void EndHelp(HelpDocument& doc) override { + doc.Pop(); + } + virtual bool BeginAccept(const std::string& strl, ArgumentsMap& amap) override { + m_Accepted = BeginParse(strl); + if (m_Accepted) amap.Add(m_ArgName, this); + return m_Accepted; + } + virtual void EndAccept(ArgumentsMap& amap) override { + if (m_Accepted) { + amap.Remove(m_ArgName); + EndParse(); + m_Accepted = false; + } + } + + std::string m_ArgName; + std::string m_ArgDesc; + bool m_Accepted; + void* m_ParsedData; + }; + + /** + * @brief Return true mean this value can accept. + */ + using IntLimit = std::function; + class IntArgument : public AbstractArgument { + public: + IntArgument(const char* argname, const char* argdesc = nullptr, IntLimit limit = nullptr) : + AbstractArgument(argname, argdesc), m_IntLimit(limit) {} + virtual ~IntArgument() {} + LIBCMO_DISABLE_COPY_MOVE(IntArgument); + + protected: + virtual bool BeginParse(const std::string&) override; + virtual void EndParse() override; + + IntLimit m_IntLimit; + }; + + class StringArgument : public AbstractArgument { + public: + StringArgument(const char* argname, const char* argdesc = nullptr) : AbstractArgument(argname, argdesc) {} + virtual ~StringArgument() {} + LIBCMO_DISABLE_COPY_MOVE(StringArgument); + + protected: + virtual bool BeginParse(const std::string&) override; + virtual void EndParse() override; + }; + + class EncodingArgument : public AbstractArgument { + public: + EncodingArgument(const char* argname, const char* argdesc = nullptr) : AbstractArgument(argname, argdesc) {} + virtual ~EncodingArgument() {} + LIBCMO_DISABLE_COPY_MOVE(EncodingArgument); + + protected: + virtual bool BeginParse(const std::string&) override; + virtual void EndParse() override; + }; + + +} diff --git a/Unvirt/Unvirt.cpp b/Unvirt/Unvirt.cpp index 461ab31..d24f15b 100644 --- a/Unvirt/Unvirt.cpp +++ b/Unvirt/Unvirt.cpp @@ -1,32 +1,4 @@ -#include "AccessibleValue.hpp" -#include "TerminalHelper.hpp" -#include "StructFormatter.hpp" -#include "CmdHelper.hpp" - -#include -#include -#include - -#include -#include - -namespace Unvirt { - - class UnvirtContext { - public: - UnvirtContext() {} - ~UnvirtContext() {} - - void Run() { - Unvirt::TerminalHelper::EnsureTerminalColor(); - Unvirt::TerminalHelper::EnsureTerminalEncoding(); - - } - private: - - }; - -} +#include "UnvirtContext.hpp" int main(int argc, char* argv[]) { diff --git a/Unvirt/Unvirt.vcxproj b/Unvirt/Unvirt.vcxproj index 9831e9f..bf7b7dd 100644 --- a/Unvirt/Unvirt.vcxproj +++ b/Unvirt/Unvirt.vcxproj @@ -181,6 +181,7 @@ + @@ -188,6 +189,7 @@ + diff --git a/Unvirt/Unvirt.vcxproj.filters b/Unvirt/Unvirt.vcxproj.filters index 23cb78b..b09faff 100644 --- a/Unvirt/Unvirt.vcxproj.filters +++ b/Unvirt/Unvirt.vcxproj.filters @@ -33,6 +33,9 @@ Sources + + Sources + @@ -50,5 +53,8 @@ Headers + + Headers + \ No newline at end of file diff --git a/Unvirt/UnvirtContext.cpp b/Unvirt/UnvirtContext.cpp new file mode 100644 index 0000000..724545c --- /dev/null +++ b/Unvirt/UnvirtContext.cpp @@ -0,0 +1,298 @@ +#include "UnvirtContext.hpp" + +namespace Unvirt::Context { + + static FILE* fout = stdout; + +#pragma region Encoding Arg + + // Copy from Gamepiaynmo/BallanceModLoader + std::vector SplitString(const std::string& str, const std::string& de) { + size_t lpos, pos = 0; + std::vector res; + + lpos = str.find_first_not_of(de, pos); + while (lpos != std::string::npos) { + pos = str.find_first_of(de, lpos); + res.push_back(str.substr(lpos, pos - lpos)); + if (pos == std::string::npos) break; + + lpos = str.find_first_not_of(de, pos); + } + + if (pos != std::string::npos) + res.push_back(""); + + return res; + } + + bool EncodingArgument::BeginParse(const std::string& strl) { + // encoding always accept every text + m_ParsedData = new std::vector(SplitString(strl, ",")); + return true; + } + + void EncodingArgument::EndParse() { + delete reinterpret_cast*>(m_ParsedData); + m_ParsedData = nullptr; + } + +#pragma endregion + +#pragma region UnvirtContext Misc + + UnvirtContext::UnvirtContext() : + m_Root(), m_Splitter(), m_Help(nullptr), + m_PageLen(10u), m_Ctx(nullptr), m_FileReader(nullptr) { + + // create command root + CmdHelper::CommandRoot* root = &m_Root; + root->Then( + (new CmdHelper::Literal("deepload"))->Then( + (new CmdHelper::StringArgument("filepath", "The path to loading file."))->Executes( + std::bind(&UnvirtContext::ProcLoad, this, true, std::placeholders::_1), + "Load a Virtools composition deeply. Load file to CKObject stage." + ) + ) + ); + root->Then( + (new CmdHelper::Literal("shallowload"))->Then( + (new CmdHelper::StringArgument("filepath", "The path to loading file."))->Executes( + std::bind(&UnvirtContext::ProcLoad, this, false, std::placeholders::_1), + "Load a Virtools composition shallowly. Load file to CKStateChunk stage." + ) + ) + ); + root->Then( + (new CmdHelper::Literal("unload"))->Executes( + std::bind(&UnvirtContext::ProcUnLoad, this, std::placeholders::_1), + "Release loaded Virtools composition." + ) + ); + root->Then( + (new CmdHelper::Literal("info"))->Executes( + std::bind(&UnvirtContext::ProcInfo, this, std::placeholders::_1), + "Show the header info of loaded Virtools composition." + ) + ); + + + root->Then( + (new CmdHelper::Literal("foo"))->Then( + (new CmdHelper::Literal("bar"))->Executes([](const CmdHelper::ArgumentsMap& amap) -> void { + fprintf(stdout, "foobar!\n"); + }, "simple foobar") + )->Then( + (new CmdHelper::IntArgument("bar", "the calling target 1"))->Executes([](const CmdHelper::ArgumentsMap& amap) -> void { + fprintf(stdout, "foo%" PRIi32 "!\n", *(amap.Get("bar")->GetData())); + }, "specialized foo bar") + ) + )->Then( + (new CmdHelper::Literal("call"))->Then( + (new CmdHelper::IntArgument("bar", "the calling taget 2"))->Executes([](const CmdHelper::ArgumentsMap& amap) -> void { + fprintf(stdout, "call%" PRIi32 "!\n", *(amap.Get("bar")->GetData())); + }, "calling someone") + ) + ); + // create help + m_Help = root->RootHelp(); + + // create context + m_Ctx = new LibCmo::CK2::CKContext(); + m_Ctx->SetOutputCallback(std::bind(&UnvirtContext::PrintContextMsg, this, std::placeholders::_1)); + } + + UnvirtContext::~UnvirtContext() { + if (m_Help != nullptr) + delete m_Help; + } + + bool UnvirtContext::HasOpenedFile() { + return m_FileReader != nullptr; + } + + void UnvirtContext::ClearDocument() { + if (m_FileReader == nullptr) return; + delete m_FileReader; + m_FileReader = nullptr; + } + + void UnvirtContext::PrintContextMsg(LibCmo::CK2::CKSTRING msg) { + if (msg != nullptr) { + fprintf(fout, UNVIRT_TERMCOL_LIGHT_YELLOW(("[CKContext] ")) "%s\n", msg); + } + } + + void UnvirtContext::PrintCommonError(const char* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + std::fputs(UNVIRT_TERMCOLHDR_LIGHT_RED, fout); + std::vfprintf(fout, u8_fmt, argptr); + std::fputs(UNVIRT_TERMCOLTAIL, fout); + va_end(argptr); + std::fputc('\n', fout); + } + +#pragma endregion + +#pragma region Main Run + + void UnvirtContext::Run() { + Unvirt::TerminalHelper::EnsureTerminalColor(); + Unvirt::TerminalHelper::EnsureTerminalEncoding(); + std::string u8cmd; + + while (true) { + // get command + TerminalHelper::GetCmdLine(u8cmd); + + // split cmd and parse it + auto cmds = m_Splitter.Convert(u8cmd); + + // get sub command + if (!m_Root.RootConsume(cmds)) { + this->PrintCommonError("Command syntax error \"%s\".", u8cmd.c_str()); + m_Help->Print(); + } + + if (m_OrderExit) { + break; + } + } + } + +#pragma endregion + +#pragma region Proc Detail + + void UnvirtContext::ProcLoad(bool is_deep, const CmdHelper::ArgumentsMap& amap) { + // check pre-requirement + if (HasOpenedFile()) { + PrintCommonError("Already have a opened file. Close it before calling \"load\"."); + return; + } + + std::string filepath = *amap.Get("filepath")->GetData(); + + // proc + m_FileReader = new LibCmo::CK2::CKFileReader(m_Ctx); + LibCmo::CK2::CKERROR err; + if (is_deep) { + err = m_FileReader->DeepLoad(filepath.c_str()); + } else { + err = m_FileReader->ShallowLoad(filepath.c_str()); + } + if (err != LibCmo::CK2::CKERROR::CKERR_OK) { + // fail to load. release all. + PrintCommonError("Fail to open file. Function return: %s\n\t%s", + Unvirt::AccessibleValue::GetCkErrorName(err).c_str(), + Unvirt::AccessibleValue::GetCkErrorDescription(err).c_str() + ); + + ClearDocument(); + } + } + + void UnvirtContext::ProcUnLoad(const CmdHelper::ArgumentsMap& amap) { + // check pre-requirement + if (!HasOpenedFile()) { + this->PrintCommonError("No loaded file."); + return; + } + + // free all + ClearDocument(); + } + + void UnvirtContext::ProcInfo(const CmdHelper::ArgumentsMap& amap) { + // check pre-requirement + if (!HasOpenedFile()) { + PrintCommonError("No loaded file."); + return; + } + + // print + Unvirt::StructFormatter::PrintCKFileInfo(m_FileReader->GetFileInfo()); + } + + void UnvirtContext::ProcLs(ViewPart parts, const CmdHelper::ArgumentsMap& amap) { + // check pre-requirement + if (!HasOpenedFile()) { + this->PrintCommonError("No loaded file."); + return; + } + + // check requirement + int32_t gotten_page = *amap.Get("page")->GetData(); + if (gotten_page <= 0) { + PrintCommonError("Page out of range."); + return; + } + size_t page = static_cast(gotten_page) - 1; + + // show list + switch (parts) { + case ViewPart::Objects: + { + // obj list + if (page * m_PageLen >= m_FileReader->GetFileObjects().size()) { + PrintCommonError("Page out of range."); + return; + } + + Unvirt::StructFormatter::PrintObjectList(m_FileReader->GetFileObjects(), page, this->m_PageLen); + break; + } + case ViewPart::Managers: + { + // mgr list + if (page * m_PageLen >= m_FileReader->GetManagersData().size()) { + PrintCommonError("Page out of range."); + return; + } + + Unvirt::StructFormatter::PrintManagerList(m_FileReader->GetManagersData(), page, this->m_PageLen); + break; + } + } + } + + void UnvirtContext::ProcData(ViewPart parts, const CmdHelper::ArgumentsMap& amap) { + + } + + void UnvirtContext::ProcChunk(ViewPart parts, const CmdHelper::ArgumentsMap& amap) { + + } + + void UnvirtContext::ProcItems(const CmdHelper::ArgumentsMap& amap) { + // check requirement + int32_t count = *amap.Get("count")->GetData(); + if (count <= 0) { + PrintCommonError("Value out of range."); + return; + } + + // assign + m_PageLen = static_cast(count); + } + + void UnvirtContext::ProcEncoding(const CmdHelper::ArgumentsMap& amap) { + + } + + void UnvirtContext::ProcTemp(const CmdHelper::ArgumentsMap& amap) { + // check requirement + std::string temppath = *amap.Get("temppath")->GetData(); + + // assign + m_Ctx->SetTempPath(temppath.c_str()); + } + + void UnvirtContext::ProcExit(const CmdHelper::ArgumentsMap& amap) { + m_OrderExit = true; + } + +#pragma endregion + +} diff --git a/Unvirt/UnvirtContext.hpp b/Unvirt/UnvirtContext.hpp new file mode 100644 index 0000000..5889683 --- /dev/null +++ b/Unvirt/UnvirtContext.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "AccessibleValue.hpp" +#include "TerminalHelper.hpp" +#include "StructFormatter.hpp" +#include "CmdHelper.hpp" + +#include +#include +#include + +#include +#include + +namespace Unvirt::Context { + + class UnvirtContext { + public: + UnvirtContext(); + ~UnvirtContext(); + LIBCMO_DISABLE_COPY_MOVE(UnvirtContext); + + void Run(); + + protected: + enum class ViewPart { + Objects, Managers + }; + void PrintCommonError(const char* u8_fmt, ...); + + void ProcLoad(bool is_deep, const CmdHelper::ArgumentsMap& amap); + void ProcUnLoad(const CmdHelper::ArgumentsMap& amap); + void ProcInfo(const CmdHelper::ArgumentsMap& amap); + void ProcLs(ViewPart parts, const CmdHelper::ArgumentsMap& amap); + void ProcData(ViewPart parts, const CmdHelper::ArgumentsMap& amap); + void ProcChunk(ViewPart parts, const CmdHelper::ArgumentsMap& amap); + void ProcItems(const CmdHelper::ArgumentsMap& amap); + void ProcEncoding(const CmdHelper::ArgumentsMap& amap); + void ProcTemp(const CmdHelper::ArgumentsMap& amap); + void ProcExit(const CmdHelper::ArgumentsMap& amap); + + protected: + bool HasOpenedFile(); + void ClearDocument(); + void PrintContextMsg(LibCmo::CK2::CKSTRING msg); + + CmdHelper::CommandRoot m_Root; + CmdHelper::HelpDocument* m_Help; + CmdHelper::CmdSplitter m_Splitter; + + size_t m_PageLen; + bool m_OrderExit; + LibCmo::CK2::CKContext* m_Ctx; + LibCmo::CK2::CKFileReader* m_FileReader; + }; + + +}