Sign in to follow this  

Simple command parser?

This topic is 3290 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

How would I implement a simple command parser? I was planning on starting out simple by just implementing commands and then later adding things like parameters. How would I do this? Would I have a list of commands that execute when the parser matches the command to something user types? Does that mean I'd have to treat parameters as separate commands?

Share this post


Link to post
Share on other sites
Well, if you were using Python, you could just take strings from the user with something like raw_input() and dumping them in eval(). If not using Python, then please specify what language you are using, and possibly other pertinent information like how you want user input to occur with your application.

Share this post


Link to post
Share on other sites
Boost Spirit, or older, battle-tested Lex/Yacc and descendants (Flex/Bison, etc.).

Another option more aimed at actually solving the task rather than exploring techniques is exposing your functions via Lua, possibly by using a choice of wrapper/interface libraries.

There's also Boost program options, which is specifically aimed at GNU-like command-line parsing.

For trivial parsing, simple string tokenization might work as well.

Share this post


Link to post
Share on other sites
It really depends on how simple of commands you're want to use. I have one application with a main loop that looks like:

for (;;) {
std::cout << ">>> " << std::flush;
std::string command_line;
getline(std::cin, command_line);

std::stringstream sstr(command_line);
std::string command;
sstr >> command;

if (command == "scan") {
std::string name;
int value;
sstr >> name >> value;
// do stuff for scan
} else if (command == "filter") {
std::string name;
int value;
sstr >> name >> value;
// do stuff for filter
} else if (command == "display") {
std::string name;
sstr >> name;
// do stuff for display
} // and more commands

Hardly award winning code, but it works fine for the very simple commands the program was supposed to work with.

Share this post


Link to post
Share on other sites
If I may, what exactly is all of this?

std::cout << ">>> " << std::flush;
std::string command_line;
getline(std::cin, command_line);

std::stringstream sstr(command_line);
std::string command;
sstr >> command;


Pardon the very novice question, but why not just use cin >> command? (I'm sure there's a good reason, I'm just curious.)

Share this post


Link to post
Share on other sites
Im not to sure if this is what you want, but in my systems parser I have a base class declared like this (A little modified to better suit C++ library) :

//! internal command
class internal_cmd {

public:

std::string m_name;
int m_flags;

virtual void desc () = 0;
virtual char* desc_short () = 0;
virtual int callback (char*) = 0;
};


You can store a list of all of the commands in an std::vector<> or std::list<> and create them dynamically (i.e., internal_cmd* cmd = new specific_cmd). Create a class for a new command and execute the command like this (Not tested) :
//! this is command to test
std::string inputbuf;

std::vector <internal_cmd*>::iterator it = cmds.begin();
for (; it != cmds.end(); it++)

if (*it)
if ((*it)->m_name == inputbuf) {

(*it)->callback (command_parms);
break;
}

This allows your command parser to treat each command as the same base class and execute the command by calling the commands callback method. This also provides a separation between your command parser and the actual commands themselves.

Share this post


Link to post
Share on other sites
Quote:
Original post by codemonkey423
Pardon the very novice question, but why not just use cin >> command? (I'm sure there's a good reason, I'm just curious.)


The primary reason is that it makes error handling easier. Take a look at the "scan" command. It's syntax is "scan [string] [number]". So a valid command would be "scan fred 5". Now what happens if the user entered "scan fred 5t"? If I had just std::cin >> command >> name >> value;, the "t" at the end would be left in the buffer and would get interpreted as the next command. The user would probably then be rather confused why entering his next command didn't work. Similarly if the user entered "scan fred barney" the std::cin object would be put into an error state on the barney input, which would then require that std::cin be cleared and probably the buffer flushed to the next newline so the next command would be read properly. By reading a line at a time and dumping into a stringstream, I get a lot of error handling without explicitly needing to write error handling code.

Share this post


Link to post
Share on other sites
I have command parsing in my 2d engine also. It can execute single lines (so that user can implement a console) and whole script files (used mainly for simple initialization scripting and GUI scripting, as conditionals and loops are not supported).

The code is too large to include in this post in its entirety, but you can download my source code here and look at ThunderCommand.h/ThunderCommand.cpp:

http://sourceforge.net/project/showfiles.php?group_id=206665

Here are all the main parts:


BOOL CThunderCommandMap::ExecuteScript(LPCWSTR pszText, LPCWSTR pszLabel)
{
int nLabelLen = (pszLabel && *pszLabel) ? wcslen(pszLabel) : 0;

while(pszText)
{
// Skip any white space and comments

while(EatWhiteSpaceNewline(&pszText) || EatComment(&pszText));

// If we're out of text, break out

if(!*pszText) break;

// Execute the current statement

if(nLabelLen)
{
// If looking for label and we found it, mark it as found

if(*pszText == L':' && !wcsncmp(pszText + 1, pszLabel, nLabelLen))
nLabelLen = 0;
}
else
{
if(*pszText != L':')
{
// Ignore all labels when we are not looking for one

if(!wcsncmp(L"return", pszText, 6))
{
// This is a return statement - return immediately

break;
}
else
{
// This is a command call or a variable get/set

ExecuteStatement(pszText);
}
}
}

// Go to the next line

pszText = wcschr(pszText, L'\n');
}

return TRUE;
}

int CThunderCommandMap::ExecuteStatement(LPCWSTR pszLine)
{
// Echo this action if so specified

if(m_rEngine.GetOption(THU_OPTION_ENABLEECHO))
{
LPCWSTR pszEndLine = wcschr(pszLine, L'\n');

if(!pszEndLine)
{
pszEndLine = pszLine + wcslen(pszLine);
}
else
{
if(L'\r' == *pszEndLine) pszEndLine--;
}

if(L'\n' == *pszEndLine) pszEndLine--;

int nLineLen = pszEndLine - pszLine;

CThunderString strEcho;

if(!strEcho.Allocate(nLineLen))
{
m_rEngine.GetErrors().Push(THU_ERR_MEMALLOC, __FUNCTIONW__, nLineLen);
return -1;
}

strEcho.CopyToBuffer(nLineLen, pszLine, nLineLen);

m_rEngine.Print(strEcho, THU_PRINT_ECHO);
}

// Find the end of name string

LPCWSTR pszNameEnd = pszLine;

while(IsValidNameChar(*pszNameEnd)) pszNameEnd++;

// Copy the name string

int nNameLen = pszNameEnd - pszLine;

// If name string length is invalid, exit

if(!nNameLen) return -1;

CThunderString strName;

if(!strName.Allocate(nNameLen))
{
m_rEngine.GetErrors().Push(THU_ERR_MEMALLOC, __FUNCTIONW__, nNameLen);
return -1;
}

strName.CopyToBuffer(nNameLen, pszLine, nNameLen);

// Start looking for the first param after the end of name string

LPCWSTR pszFirstParam = pszNameEnd;

// If what follows is end of string, execute command without params and exit

if(!*pszFirstParam) return ExecuteStatement(strName, NULL);

// Otherwise, skip any white space

EatWhiteSpace(&pszFirstParam);

// If we encounter a comment, execute command without params and exit

if(EatComment(&pszFirstParam)) return ExecuteStatement(strName, NULL);

// Execute with this name and the parameter string

return ExecuteStatement(strName, pszFirstParam);
}

int CThunderCommandMap::ExecuteStatement(LPCWSTR pszName, LPCWSTR pszParams)
{
// First, find a command by this name

CThunderCommand* pCmd = Find(pszName);

if(pCmd)
{
// Execute command if found

return pCmd-&gt;Execute(m_rEngine, pszParams);
}
else
{
// If can't find the command, try to find a global (engine) variable

CThunderVariable* pVar = m_rEngine.GetVariables().Find(pszName);

if(pszParams)
{
// If variable is not found in global scope, try searching current map

if(!pVar && m_rEngine.GetCurrentMapConst())
{
pVar = m_rEngine.GetCurrentMap()-&gt;GetVariables().Find(pszName);
}

// If variable is not found and parameters are specified, create and set a global one from parameters

if(!pVar)
{
pVar = m_rEngine.GetVariables().Add(pszName, THU_VAR_UNDEFINED);

if(!pVar) return -1;
}

pVar-&gt;FromString(pszParams);
}
else
{
// If variable not found and no parameters specified, exit with failure

if(!pVar)
{
m_rEngine.Print(L"variable or command not found.", THU_PRINT_ERROR);
return -1;
}

// If variable is found, print its value

CThunderString str;
pVar-&gt;ToString(str);

m_rEngine.Print(str);
}
}

return 0;
}

int CThunderCommand::Execute(CThunderEngine& rEngine, LPCWSTR pszParams)
{
vector&lt;CThunderVariable&gt; arParams;

// If no params, just execute without them

if(!pszParams) return (*m_pfnCallback)(rEngine, arParams);

// Allow 8 params before having to resize

arParams.reserve(8);

// Start reading parameters

for(;;)
{
// Attempt to read this param

CThunderVariable varCurParam;
int nReadLen = varCurParam.FromString(pszParams);

// If invalid, stop reading params

if(!nReadLen) break;

// Otherwise, add this param to the param list

arParams.push_back(varCurParam);

// Skip the data for the param just read

pszParams += nReadLen;

// Skip any white space

EatWhiteSpace(&pszParams);

// Examine what follows

if(L',' == *pszParams)
{
// If parameter separator follows, skip it and any space that follows

pszParams++;
EatWhiteSpace(&pszParams);
}
else if(L'\r' == *pszParams || L'\n' == *pszParams)
{
// If this is the end of line, break out

break;
}
else if(L'\0' == *pszParams)
{
// If this is the end of string, break out

break;
}
else if(EatComment(&pszParams))
{
// If this is a comment, break out

break;
}
}

// Call the command

return (*m_pfnCallback)(rEngine, arParams);
}

Share this post


Link to post
Share on other sites

This topic is 3290 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this