350 lines
9.2 KiB
C++
350 lines
9.2 KiB
C++
/**
|
|
@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<String>* 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<String>;
|
|
auto split = StringUtils::Split(name, '\1');
|
|
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(new_cmd[start] != ' ' && start < len)
|
|
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;
|
|
}
|