391 lines
12 KiB
C++
391 lines
12 KiB
C++
/**
|
|
@file command.cpp
|
|
|
|
@brief Implements the command class.
|
|
*/
|
|
|
|
#include "command.h"
|
|
#include "value.h"
|
|
#include "console.h"
|
|
#include "debugger.h"
|
|
#include "math.h"
|
|
#include "commandparser.h"
|
|
|
|
/**
|
|
\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(COMMAND* command_list, const char* name, COMMAND** link)
|
|
{
|
|
COMMAND* cur = command_list;
|
|
if(!cur->name)
|
|
return 0;
|
|
COMMAND* prev = 0;
|
|
while(cur)
|
|
{
|
|
if(arraycontains(cur->name, name))
|
|
{
|
|
if(link)
|
|
*link = prev;
|
|
return cur;
|
|
}
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
\brief Initialize a command list.
|
|
\return a ::COMMAND*
|
|
*/
|
|
COMMAND* cmdinit()
|
|
{
|
|
COMMAND* cmd = (COMMAND*)emalloc(sizeof(COMMAND), "cmdinit:cmd");
|
|
memset(cmd, 0, sizeof(COMMAND));
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
\brief Clear a command list.
|
|
\param [in] cmd_list Command list to clear.
|
|
*/
|
|
void cmdfree(COMMAND* cmd_list)
|
|
{
|
|
COMMAND* cur = cmd_list;
|
|
while(cur)
|
|
{
|
|
efree(cur->name, "cmdfree:cur->name");
|
|
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(COMMAND* command_list, const char* name, CBCOMMAND cbCommand, bool debugonly)
|
|
{
|
|
if(!command_list or !cbCommand or !name or !*name or cmdfind(command_list, name, 0))
|
|
return false;
|
|
COMMAND* cmd;
|
|
bool nonext = false;
|
|
if(!command_list->name)
|
|
{
|
|
cmd = command_list;
|
|
nonext = true;
|
|
}
|
|
else
|
|
cmd = (COMMAND*)emalloc(sizeof(COMMAND), "cmdnew:cmd");
|
|
memset(cmd, 0, sizeof(COMMAND));
|
|
cmd->name = (char*)emalloc(strlen(name) + 1, "cmdnew:cmd->name");
|
|
strcpy(cmd->name, name);
|
|
cmd->cbCommand = cbCommand;
|
|
cmd->debugonly = debugonly;
|
|
COMMAND* cur = command_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(COMMAND* command_list, 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] != ' ' and start < len)
|
|
start++;
|
|
new_cmd[start] = 0;
|
|
COMMAND* found = cmdfind(command_list, 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(COMMAND* command_list, const char* name, CBCOMMAND cbCommand, bool debugonly)
|
|
{
|
|
if(!cbCommand)
|
|
return 0;
|
|
COMMAND* found = cmdfind(command_list, 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(COMMAND* command_list, const char* name)
|
|
{
|
|
COMMAND* prev = 0;
|
|
COMMAND* found = cmdfind(command_list, name, &prev);
|
|
if(!found)
|
|
return false;
|
|
efree(found->name, "cmddel:found->name");
|
|
if(found == command_list)
|
|
{
|
|
COMMAND* next = command_list->next;
|
|
if(next)
|
|
{
|
|
memcpy(command_list, command_list->next, sizeof(COMMAND));
|
|
command_list->next = next->next;
|
|
efree(next, "cmddel:next");
|
|
}
|
|
else
|
|
memset(command_list, 0, sizeof(COMMAND));
|
|
}
|
|
else
|
|
{
|
|
prev->next = found->next;
|
|
efree(found, "cmddel:found");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
command_list: command list
|
|
cbUnknownCommand: function to execute when an unknown command was found
|
|
cbCommandProvider: function that provides commands (fgets for example), does not return until a command was found
|
|
cbCommandFinder: non-default command finder
|
|
error_is_fatal: error return of a command callback stops the command processing
|
|
*/
|
|
|
|
/**
|
|
\brief Initiates a command loop. This function will not return until a command returns ::STATUS_EXIT.
|
|
\param [in] command_list Command list to use for the command lookups.
|
|
\param cbUnknownCommand The unknown command callback.
|
|
\param cbCommandProvider The command provider callback.
|
|
\param cbCommandFinder The command finder callback.
|
|
\param error_is_fatal true if commands that return ::STATUS_ERROR terminate the command loop.
|
|
\return A CMDRESULT, will always be ::STATUS_EXIT.
|
|
*/
|
|
CMDRESULT cmdloop(COMMAND* command_list, CBCOMMAND cbUnknownCommand, CBCOMMANDPROVIDER cbCommandProvider, CBCOMMANDFINDER cbCommandFinder, bool error_is_fatal)
|
|
{
|
|
if(!cbUnknownCommand or !cbCommandProvider)
|
|
return STATUS_ERROR;
|
|
char command[deflen] = "";
|
|
bool bLoop = true;
|
|
while(bLoop)
|
|
{
|
|
if(!cbCommandProvider(command, deflen))
|
|
break;
|
|
if(strlen(command))
|
|
{
|
|
strcpy_s(command, StringUtils::Trim(command).c_str());
|
|
COMMAND* cmd;
|
|
if(!cbCommandFinder) //'clean' command processing
|
|
cmd = cmdget(command_list, command);
|
|
else //'dirty' command processing
|
|
cmd = cbCommandFinder(command_list, command);
|
|
|
|
if(!cmd or !cmd->cbCommand) //unknown command
|
|
{
|
|
char* argv[1];
|
|
*argv = command;
|
|
CMDRESULT res = cbUnknownCommand(1, argv);
|
|
if((error_is_fatal and res == STATUS_ERROR) or res == STATUS_EXIT)
|
|
bLoop = false;
|
|
}
|
|
else
|
|
{
|
|
if(cmd->debugonly and !DbgIsDebugging())
|
|
{
|
|
dputs("this command is debug-only");
|
|
if(error_is_fatal)
|
|
bLoop = false;
|
|
}
|
|
else
|
|
{
|
|
Command commandParsed(command);
|
|
int argcount = commandParsed.GetArgCount();
|
|
char** argv = (char**)emalloc((argcount + 1) * sizeof(char*), "cmdloop:argv");
|
|
argv[0] = command;
|
|
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());
|
|
}
|
|
CMDRESULT 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");
|
|
if((error_is_fatal and res == STATUS_ERROR) or res == STATUS_EXIT)
|
|
bLoop = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return STATUS_EXIT;
|
|
}
|
|
|
|
/*
|
|
- custom command formatting rules
|
|
*/
|
|
|
|
/**
|
|
\brief Query if a string is a valid expression.
|
|
\param expression The expression to check.
|
|
\return true if the string is a valid expression.
|
|
*/
|
|
static bool isvalidexpression(const char* expression)
|
|
{
|
|
uint value;
|
|
return valfromstring(expression, &value);
|
|
}
|
|
|
|
/**
|
|
\brief Special formats a given command. Used as a little hack to support stuff like 'x++' and 'x=y'
|
|
\param [in,out] string String to format.
|
|
*/
|
|
static void specialformat(char* string)
|
|
{
|
|
int len = (int)strlen(string);
|
|
char* found = strstr(string, "=");
|
|
char* str = (char*)emalloc(len * 2, "specialformat:str");
|
|
char* backup = (char*)emalloc(len + 1, "specialformat:backup");
|
|
strcpy(backup, string); //create a backup of the string
|
|
memset(str, 0, len * 2);
|
|
if(found) //contains =
|
|
{
|
|
char* a = (found - 1);
|
|
*found = 0;
|
|
found++;
|
|
if(!*found)
|
|
{
|
|
*found = '=';
|
|
efree(str, "specialformat:str");
|
|
efree(backup, "specialformat:backup");
|
|
return;
|
|
}
|
|
int flen = (int)strlen(found); //n(+)=n++
|
|
if((found[flen - 1] == '+' and found[flen - 2] == '+') or (found[flen - 1] == '-' and found[flen - 2] == '-')) //eax++/eax--
|
|
{
|
|
found[flen - 2] = 0;
|
|
char op = found[flen - 1];
|
|
sprintf(str, "%s%c1", found, op);
|
|
strcpy(found, str);
|
|
}
|
|
if(mathisoperator(*a) > 2) //x*=3 -> x=x*3
|
|
{
|
|
char op = *a;
|
|
*a = 0;
|
|
if(isvalidexpression(string))
|
|
sprintf(str, "mov %s,%s%c%s", string, string, op, found);
|
|
else
|
|
strcpy(str, backup);
|
|
}
|
|
else
|
|
{
|
|
if(isvalidexpression(found))
|
|
sprintf(str, "mov %s,%s", string, found);
|
|
else
|
|
strcpy(str, backup);
|
|
}
|
|
strcpy(string, str);
|
|
}
|
|
else if((string[len - 1] == '+' and string[len - 2] == '+') or (string[len - 1] == '-' and string[len - 2] == '-')) //eax++/eax--
|
|
{
|
|
string[len - 2] = 0;
|
|
char op = string[len - 1];
|
|
if(isvalidexpression(string))
|
|
sprintf(str, "mov %s,%s%c1", string, string, op);
|
|
else
|
|
strcpy(str, backup);
|
|
strcpy(string, str);
|
|
}
|
|
efree(str, "specialformat:str");
|
|
efree(backup, "specialformat:backup");
|
|
}
|
|
|
|
/*
|
|
- 'default' command finder, with some custom rules
|
|
*/
|
|
|
|
/**
|
|
\brief Default command finder. It uses specialformat() and mathformat() to make sure the command is optimally checked.
|
|
\param [in] cmd_list Command list.
|
|
\param [in] command Command name.
|
|
\return null if it fails, else a COMMAND*.
|
|
*/
|
|
COMMAND* cmdfindmain(COMMAND* cmd_list, char* command)
|
|
{
|
|
COMMAND* cmd = cmdfind(cmd_list, command, 0);
|
|
if(!cmd)
|
|
{
|
|
specialformat(command);
|
|
cmd = cmdget(cmd_list, command);
|
|
}
|
|
if(!cmd or !cmd->cbCommand)
|
|
mathformat(command);
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
\brief Directly execute a command.
|
|
\param [in,out] cmd_list Command list.
|
|
\param cmd The command to execute.
|
|
\return A CMDRESULT.
|
|
*/
|
|
CMDRESULT cmddirectexec(COMMAND* cmd_list, const char* cmd)
|
|
{
|
|
if(!cmd or !strlen(cmd))
|
|
return STATUS_ERROR;
|
|
char command[deflen] = "";
|
|
strcpy_s(command, StringUtils::Trim(cmd).c_str());
|
|
COMMAND* found = cmdfindmain(cmd_list, command);
|
|
if(!found or !found->cbCommand)
|
|
return STATUS_ERROR;
|
|
if(found->debugonly and !DbgIsDebugging())
|
|
return STATUS_ERROR;
|
|
Command cmdParsed(command);
|
|
int argcount = cmdParsed.GetArgCount();
|
|
char** argv = (char**)emalloc((argcount + 1) * sizeof(char*), "cmddirectexec:argv");
|
|
argv[0] = command;
|
|
for(int i = 0; i < argcount; i++)
|
|
{
|
|
argv[i + 1] = (char*)emalloc(deflen, "cmddirectexec:argv[i+1]");
|
|
*argv[i + 1] = 0;
|
|
strcpy_s(argv[i + 1], deflen, cmdParsed.GetArg(i).c_str());
|
|
}
|
|
CMDRESULT res = found->cbCommand(argcount + 1, argv);
|
|
for(int i = 0; i < argcount; i++)
|
|
efree(argv[i + 1], "cmddirectexec:argv[i+1]");
|
|
efree(argv, "cmddirectexec:argv");
|
|
return res;
|
|
}
|