/** @file command.cpp @brief Implements the command class. */ #include "command.h" #include "value.h" #include "console.h" #include "commandparser.h" #include "expressionparser.h" #include "variable.h" #include "cmd-undocumented.h" COMMAND* cmd_list = 0; static bool vecContains(std::vector* names, const char* name) { for(const auto & cmd : *names) if(!_stricmp(cmd.c_str(), name)) return true; return false; } /** \brief Finds a ::COMMAND in a command list. \param [in] command list. \param name The name of the command to find. \param [out] Link to the command. \return null if it fails, else a ::COMMAND*. */ COMMAND* cmdfind(const char* name, COMMAND** link) { COMMAND* cur = cmd_list; if(!cur->names) return 0; COMMAND* prev = 0; while(cur) { if(vecContains(cur->names, name)) { if(link) *link = prev; return cur; } prev = cur; cur = cur->next; } return 0; } bool IsArgumentsLessThan(int argc, int minimumCount) { if(argc < minimumCount) { dprintf(QT_TRANSLATE_NOOP("DBG", "Not enough arguments! At least %d arguments must be specified.\n"), minimumCount - 1); return true; } return false; } /** \brief Initialize a command list. \return a ::COMMAND* */ COMMAND* cmdinit() { cmd_list = (COMMAND*)emalloc(sizeof(COMMAND), "cmdinit:cmd"); memset(cmd_list, 0, sizeof(COMMAND)); return cmd_list; } /** \brief Clear a command list. \param [in] cmd_list Command list to clear. */ void cmdfree() { COMMAND* cur = cmd_list; while(cur) { delete cur->names; COMMAND* next = cur->next; efree(cur, "cmdfree:cur"); cur = next; } } /** \brief Creates a new command and adds it to the list. \param [in,out] command_list Command list. Cannot be null. \param name The command name. \param cbCommand The command callback. \param debugonly true if the command can only be executed in a debugging context. \return true if the command was successfully added to the list. */ bool cmdnew(const char* name, CBCOMMAND cbCommand, bool debugonly) { if(!cmd_list || !cbCommand || !name || !*name || cmdfind(name, 0)) return false; COMMAND* cmd; bool nonext = false; if(!cmd_list->names) { cmd = cmd_list; nonext = true; } else cmd = (COMMAND*)emalloc(sizeof(COMMAND), "cmdnew:cmd"); memset(cmd, 0, sizeof(COMMAND)); cmd->names = new std::vector; auto split = StringUtils::Split(name, ','); for(const auto & s : split) { auto trimmed = StringUtils::Trim(s); if(trimmed.length()) cmd->names->push_back(trimmed); } cmd->cbCommand = cbCommand; cmd->debugonly = debugonly; COMMAND* cur = cmd_list; if(!nonext) { while(cur->next) cur = cur->next; cur->next = cmd; } return true; } /** \brief Gets a ::COMMAND from the command list. \param [in] command_list Command list. \param cmd The command to get from the list. \return null if the command was not found. Otherwise a ::COMMAND*. */ COMMAND* cmdget(const char* cmd) { char new_cmd[deflen] = ""; strcpy_s(new_cmd, deflen, cmd); int len = (int)strlen(new_cmd); int start = 0; while(start < len && new_cmd[start] != ' ') start++; new_cmd[start] = 0; COMMAND* found = cmdfind(new_cmd, 0); if(!found) return 0; return found; } /** \brief Sets a new command callback and debugonly property in a command list. \param [in] command_list Command list. \param name The name of the command to change. \param cbCommand The new command callback. \param debugonly The new debugonly value. \return The old command callback. */ CBCOMMAND cmdset(const char* name, CBCOMMAND cbCommand, bool debugonly) { if(!cbCommand) return 0; COMMAND* found = cmdfind(name, 0); if(!found) return 0; CBCOMMAND old = found->cbCommand; found->cbCommand = cbCommand; found->debugonly = debugonly; return old; } /** \brief Deletes a command from a command list. \param [in] command_list Command list. \param name The name of the command to delete. \return true if the command was deleted. */ bool cmddel(const char* name) { COMMAND* prev = 0; COMMAND* found = cmdfind(name, &prev); if(!found) return false; delete found->names; if(found == cmd_list) { COMMAND* next = cmd_list->next; if(next) { memcpy(cmd_list, cmd_list->next, sizeof(COMMAND)); cmd_list->next = next->next; efree(next, "cmddel:next"); } else memset(cmd_list, 0, sizeof(COMMAND)); } else { prev->next = found->next; efree(found, "cmddel:found"); } return true; } bool cbCommandProvider(char* cmd, int maxlen); void cmdsplit(const char* cmd, StringList & commands) { commands.clear(); auto len = strlen(cmd); auto inquote = false; auto inescape = false; std::string split; split.reserve(len); for(size_t i = 0; i < len; i++) { auto ch = cmd[i]; switch(ch) //simple state machine to determine if the ';' separator is in quotes { case '\"': if(!inescape) inquote = !inquote; inescape = false; break; case '\\': inescape = !inescape; break; default: inescape = false; } if(ch == ';' && !inquote) { if(!split.empty()) commands.push_back(split); split.clear(); } else split.push_back(ch); } if(!split.empty()) commands.push_back(split); } bool cmdexeccallback(COMMAND* cmd, const std::string & command) { Command commandParsed(command); int argcount = commandParsed.GetArgCount(); char** argv = (char**)emalloc((argcount + 1) * sizeof(char*), "cmdloop:argv"); argv[0] = (char*)command.c_str(); for(int i = 0; i < argcount; i++) { argv[i + 1] = (char*)emalloc(deflen, "cmdloop:argv[i+1]"); *argv[i + 1] = 0; strcpy_s(argv[i + 1], deflen, commandParsed.GetArg(i).c_str()); } auto res = cmd->cbCommand(argcount + 1, argv); for(int i = 0; i < argcount; i++) efree(argv[i + 1], "cmdloop:argv[i+1]"); efree(argv, "cmdloop:argv"); return res; } /** \brief Initiates the command loop. This function will not return until a command returns ::STATUS_EXIT. \return A bool, will always be ::STATUS_EXIT. */ void cmdloop() { char command_[deflen] = ""; StringList commands; commands.reserve(100); while(true) { if(!cbCommandProvider(command_, deflen)) break; cmdsplit(command_, commands); for(auto & command : commands) { command = StringUtils::Trim(command); if(command.empty()) //skip empty commands continue; COMMAND* found = cmdget(command.c_str()); if(!found || !found->cbCommand) //unknown command { char* argv[1]; *argv = (char*)command.c_str(); if(!cbBadCmd(1, argv)) //stop processing on non-value commands break; continue; } if(found->debugonly && !DbgIsDebugging()) //stop processing on debug-only commands { dprintf(QT_TRANSLATE_NOOP("DBG", "The command \"%s\" is debug-only\n"), command.c_str()); break; } //execute command callback if(!cmdexeccallback(found, command)) break; } } } /** \brief Directly execute a command. \param [in,out] cmd_list Command list. \param cmd The command to execute. \return A bool. */ bool cmddirectexec(const char* cmd) { // Don't allow anyone to send in empty strings if(!cmd) return false; StringList commands; cmdsplit(cmd, commands); for(auto & command : commands) { command = StringUtils::Trim(command); if(command.empty()) //skip empty commands continue; COMMAND* found = cmdget(command.c_str()); if(!found || !found->cbCommand) //unknown command { ExpressionParser parser(command); duint result; if(!parser.Calculate(result, valuesignedcalc(), true, false)) //stop processing on non-value commands return false; varset("$ans", result, true); continue; } if(found->debugonly && !DbgIsDebugging()) //stop processing on debug-only commands { dprintf(QT_TRANSLATE_NOOP("DBG", "The command \"%s\" is debug-only\n"), command.c_str()); return false; } //execute command callback if(!cmdexeccallback(found, command)) return false; } return true; }