Browse Source

Make command interpreter detect invalid argument counts and add support for variable substitution

master
C. J. Howard 5 years ago
parent
commit
77b71c0144
Signed by: cjhoward GPG Key ID: 03E1FABA9C3EC195
4 changed files with 182 additions and 46 deletions
  1. +49
    -4
      src/debug/console.cpp
  2. +34
    -3
      src/debug/console.hpp
  3. +88
    -34
      src/game.cpp
  4. +11
    -5
      src/game.hpp

+ 49
- 4
src/debug/console.cpp View File

@ -62,6 +62,25 @@ std::string ArgumentParser::parse(const std::string& argument)
return argument;
}
void CommandInterpreter::set(const std::string& name, const std::string& value)
{
variables[name] = value;
}
void CommandInterpreter::unset(const std::string& name)
{
auto it = variables.find(name);
if (it != variables.end())
{
variables.erase(it);
}
}
const std::map<std::string, std::string>& CommandInterpreter::help() const
{
return helpStrings;
}
std::tuple<std::string, std::vector<std::string>, std::function<void()>> CommandInterpreter::interpret(const std::string& line)
{
// Split line into arguments
@ -78,22 +97,48 @@ std::tuple, std::function> Command
return {std::string(), std::vector<std::string>(), nullptr};
}
// Perform variable substitution/expansion on '$' operators
for (std::string& argument: arguments)
{
if (!argument.empty() && argument[0] == '$')
{
std::string variableName = argument.substr(1);
std::string variableValue = variables[variableName];
argument = variableValue;
}
}
// Get command name
std::string name = arguments[0];
std::string commandName = arguments[0];
// Remove command name from arguments
arguments.erase(arguments.begin());
// Check command name for member access operator '.'
std::size_t dotOperatorPosition = commandName.find('.');
if (dotOperatorPosition != std::string::npos)
{
// Get variable name and lookup value
std::string variableName = commandName.substr(0, dotOperatorPosition);
std::string variableValue = variables[variableName];
// Insert variable value at front of the argument vector
arguments.insert(arguments.begin(), variableValue);
// Remove variable name from command name
commandName = commandName.substr(dotOperatorPosition + 1);
}
// Find command linker for this command
auto linker = linkers.find(name);
auto linker = linkers.find(commandName);
if (linker == linkers.end())
{
return {name, arguments, nullptr};
return {commandName, arguments, nullptr};
}
// Link command function and its arguments into a callable object
std::function<void()> call = linker->second(arguments);
return {name, arguments, call};
return {commandName, arguments, call};
}

+ 34
- 3
src/debug/console.hpp View File

@ -21,6 +21,8 @@
#define CONSOLE_HPP
#include <functional>
#include <map>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
@ -60,6 +62,11 @@ public:
template<class... Args>
static std::tuple<Args...> parse(const std::vector<std::string>& arguments)
{
if (arguments.size() != sizeof...(Args))
{
throw std::invalid_argument("Argument vector size doesn't match function parameter count.");
}
return parse<Args...>(arguments, std::make_index_sequence<sizeof...(Args)>{});
}
@ -107,14 +114,35 @@ public:
* @param function Function associated with the command.
*/
template <class... Args>
void registerCommand(const std::string& name, const std::function<void(Args...)>& function);
void registerCommand(const std::string& name, const std::function<void(Args...)>& function, const std::string& helpString = std::string());
template <class... Args>
void registerCommand(const std::string& name, void(*function)(Args...));
/**
* Sets the value of an interpreter variable.
*
* @param name Interpreter variable name.
* @param value Interpreter variable value.
*/
void set(const std::string& name, const std::string& value);
/**
* Unsets an interpreter variable.
*
* @param name Interpreter variable name.
*/
void unset(const std::string& name);
/**
* Returns the help strings for all commands.
*/
const std::map<std::string, std::string>& help() const;
/**
* Interprets a line of text as a function call, returning the interpreted command name, argument vector, and callable function object.
*
* @param line Line of text to be interpreted.
* @param line Line of text to be interpreted. Arguments are delimeted by spaces, with the first argument as the command name. Command names containing the '.' operator will have the post-dot string substituted for its console variable value, then the string will be transposed around the dot, and the dot will be replaced by a space, such that the command "object.setValue 10" would become "setValue x 10" if that console variable "object" was set to "x". Arguments beginning with substitution operator '$' will interpreted as variables and substituted with their values.
*/
std::tuple<std::string, std::vector<std::string>, std::function<void()>> interpret(const std::string& line);
@ -124,12 +152,15 @@ private:
// A command name-keyed map of command linkers
std::unordered_map<std::string, std::function<std::function<void()>(const std::vector<std::string>&)>> linkers;
std::map<std::string, std::string> helpStrings;
std::unordered_map<std::string, std::string> variables;
};
template <class... Args>
void CommandInterpreter::registerCommand(const std::string& name, const std::function<void(Args...)>& function)
void CommandInterpreter::registerCommand(const std::string& name, const std::function<void(Args...)>& function, const std::string& helpString)
{
addCommandLinker(name, function, CommandLinker::link<Args...>);
helpStrings[name] = helpString;
}
template <class... Args>

+ 88
- 34
src/game.cpp View File

@ -210,25 +210,6 @@ Game::Game(int argc, char* argv[]):
toggleFullscreenDisabled = false;
sandboxState = new SandboxState(this);
cli = new CommandInterpreter();
std::function<void()> exitCommand = std::bind(std::exit, EXIT_SUCCESS);
std::function<void(int, float, float, float)> setScaleCommand = [this](int id, float x, float y, float z) {
setScale(id, {x, y, z});
};
std::function<void()> toggleWireframeCommand = std::bind(&Game::toggleWireframe, this);
std::function<void(std::string)> shCommand = std::bind(&Game::executeShellScript, this, std::placeholders::_1);
cli->registerCommand("q", exitCommand);
cli->registerCommand("setScale", setScaleCommand);
cli->registerCommand("wireframe", toggleWireframeCommand);
cli->registerCommand("sh", shCommand);
// Start CLI thread
std::thread cliThread(&Game::interpretCommands, this);
cliThread.detach();
}
Game::~Game()
@ -821,8 +802,54 @@ void Game::setupDebugging()
// Setup performance sampling
performanceSampler.setSampleSize(30);
// Disable wireframe drawing
wireframe = false;
// Create CLI
cli = new CommandInterpreter();
// Register CLI commands
std::function<void(std::string, std::string)> setCommand = std::bind(&CommandInterpreter::set, cli, std::placeholders::_1, std::placeholders::_2);
std::function<void(std::string)> unsetCommand = std::bind(&CommandInterpreter::unset, cli, std::placeholders::_1);
std::function<void()> exitCommand = std::bind(std::exit, EXIT_SUCCESS);
std::function<void(int, float, float, float)> setScaleCommand = [this](int id, float x, float y, float z) {
setScale(id, {x, y, z});
};
std::function<void(std::string)> createInstanceOfCommand = std::bind(&Game::createInstanceOf, this, std::placeholders::_1);
std::function<void(float)> toggleWireframeCommand = [this](float width){ lightingPass->setWireframeLineWidth(width); };
std::function<void(std::string)> shCommand = std::bind(&Game::executeShellScript, this, std::placeholders::_1);
std::function<void()> helpCommand = [this]()
{
auto& helpStrings = cli->help();
for (auto it = helpStrings.begin(); it != helpStrings.end(); ++it)
{
if (it->second.empty())
{
continue;
}
std::cout << it->second << std::endl;
}
};
std::string exitHelp = "exit";
std::string setHelp = "set <name> <value>";
std::string unsetHelp = "unset <name>";
std::string createInstanceOfHelp = "createinstanceof <template name>";
std::string setScaleHelp = "setscale <id> <sx> <sy> <sz>";
std::string wireframeHelp = "wireframe <width>";
std::string shHelp = "sh <filename>";
cli->registerCommand("exit", exitCommand, exitHelp);
cli->registerCommand("set", setCommand, setHelp);
cli->registerCommand("unset", setCommand, unsetHelp);
cli->registerCommand("createinstanceof", createInstanceOfCommand, createInstanceOfHelp);
cli->registerCommand("setscale", setScaleCommand, setScaleHelp);
cli->registerCommand("wireframe", toggleWireframeCommand, wireframeHelp);
cli->registerCommand("sh", shCommand, shHelp);
cli->registerCommand("help", helpCommand);
// Start CLI thread
std::thread cliThread(&Game::interpretCommands, this);
cliThread.detach();
}
void Game::setupLocalization()
@ -2943,14 +2970,6 @@ void Game::setTimeOfDay(float time)
sunlightCamera.lookAt(Vector3(0, 0, 0), sunlight.getDirection(), up);
}
void Game::toggleWireframe()
{
wireframe = !wireframe;
float width = (wireframe) ? 1.0f : 0.0f;
lightingPass->setWireframeLineWidth(width);
}
void Game::queueScreenshot()
{
screenshotQueued = true;
@ -3257,14 +3276,49 @@ void Game::interpretCommands()
std::string line;
std::getline(std::cin, line);
auto [name, arguments, call] = cli->interpret(line);
if (call)
bool invalid = false;
std::string commandName;
std::vector<std::string> arguments;
std::function<void()> call;
try
{
std::tie(commandName, arguments, call) = cli->interpret(line);
}
catch (const std::invalid_argument& e)
{
call();
invalid = true;
}
if (!invalid)
{
if (call)
{
call();
}
else
{
if (!commandName.empty())
{
std::cout << "Unknown command " << commandName << std::endl;
}
}
}
else
{
std::cout << "ant: Unknown command " << name << std::endl;
commandName = line.substr(0, line.find(' '));
auto& helpStrings = cli->help();
if (auto it = helpStrings.find(commandName); it != helpStrings.end())
{
std::cout << "Usage: " << it->second << std::endl;
}
else
{
std::cout << commandName << ": Invalid arguments" << std::endl;
}
}
}
}
@ -3475,7 +3529,7 @@ void Game::executeShellScript(const std::string& string)
}
else
{
std::cout << "ant: Unknown command " << name << std::endl;
std::cout << "Unknown command " << name << std::endl;
}
}
}

+ 11
- 5
src/game.hpp View File

@ -158,7 +158,9 @@ private:
virtual void handleEvent(const GamepadDisconnectedEvent& event);
virtual void handleEvent(const ScheduledFunctionEvent& event);
void setupDebugging();
#if defined(DEBUG)
void setupDebugging();
#endif
void setupLocalization();
void setupWindow();
void setupGraphics();
@ -185,7 +187,6 @@ private:
void resizeRenderTargets();
void setTimeOfDay(float time);
void toggleWireframe();
void queueScreenshot();
void screenshot();
@ -212,6 +213,9 @@ private:
public:
EntityID createInstance();
EntityID createInstanceOf(const std::string& templateName);
void selectInstance(EntityID entity);
void selectNamedInstance(const std::string& instanceName);
void destroyInstance(EntityID entity);
void addComponent(EntityID entity, ComponentBase* component);
void removeComponent(EntityID entity, ComponentType type);
@ -540,9 +544,11 @@ public:
bool toggleFullscreenDisabled;
// Debugging
CommandInterpreter* cli;
std::ofstream logFileStream;
bool wireframe;
#if defined(DEBUG)
CommandInterpreter* cli;
std::map<std::string, std::string> helpStrings;
std::ofstream logFileStream;
#endif
private:
static void saveScreenshot(const std::string& filename, unsigned int width, unsigned int height, unsigned char* pixels);

Loading…
Cancel
Save