From 77b71c01440d73791849aaee87dedbe9be2fb92f Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Wed, 20 Mar 2019 01:27:34 +0800 Subject: [PATCH] Make command interpreter detect invalid argument counts and add support for variable substitution --- src/debug/console.cpp | 53 ++++++++++++++++-- src/debug/console.hpp | 37 +++++++++++-- src/game.cpp | 122 ++++++++++++++++++++++++++++++------------ src/game.hpp | 16 ++++-- 4 files changed, 182 insertions(+), 46 deletions(-) diff --git a/src/debug/console.cpp b/src/debug/console.cpp index 7e6e86f..e8cce02 100644 --- a/src/debug/console.cpp +++ b/src/debug/console.cpp @@ -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& CommandInterpreter::help() const +{ + return helpStrings; +} + std::tuple, std::function> CommandInterpreter::interpret(const std::string& line) { // Split line into arguments @@ -78,22 +97,48 @@ std::tuple, std::function> Command return {std::string(), std::vector(), 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 call = linker->second(arguments); - return {name, arguments, call}; + return {commandName, arguments, call}; } diff --git a/src/debug/console.hpp b/src/debug/console.hpp index a00d9ac..d13b4eb 100644 --- a/src/debug/console.hpp +++ b/src/debug/console.hpp @@ -21,6 +21,8 @@ #define CONSOLE_HPP #include +#include +#include #include #include #include @@ -60,6 +62,11 @@ public: template static std::tuple parse(const std::vector& arguments) { + if (arguments.size() != sizeof...(Args)) + { + throw std::invalid_argument("Argument vector size doesn't match function parameter count."); + } + return parse(arguments, std::make_index_sequence{}); } @@ -107,14 +114,35 @@ public: * @param function Function associated with the command. */ template - void registerCommand(const std::string& name, const std::function& function); + void registerCommand(const std::string& name, const std::function& function, const std::string& helpString = std::string()); template 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& 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::function> interpret(const std::string& line); @@ -124,12 +152,15 @@ private: // A command name-keyed map of command linkers std::unordered_map(const std::vector&)>> linkers; + std::map helpStrings; + std::unordered_map variables; }; template -void CommandInterpreter::registerCommand(const std::string& name, const std::function& function) +void CommandInterpreter::registerCommand(const std::string& name, const std::function& function, const std::string& helpString) { addCommandLinker(name, function, CommandLinker::link); + helpStrings[name] = helpString; } template diff --git a/src/game.cpp b/src/game.cpp index 045d3cb..0f6b201 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -210,25 +210,6 @@ Game::Game(int argc, char* argv[]): toggleFullscreenDisabled = false; sandboxState = new SandboxState(this); - - - cli = new CommandInterpreter(); - - std::function exitCommand = std::bind(std::exit, EXIT_SUCCESS); - std::function setScaleCommand = [this](int id, float x, float y, float z) { - setScale(id, {x, y, z}); - }; - std::function toggleWireframeCommand = std::bind(&Game::toggleWireframe, this); - std::function 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 setCommand = std::bind(&CommandInterpreter::set, cli, std::placeholders::_1, std::placeholders::_2); + std::function unsetCommand = std::bind(&CommandInterpreter::unset, cli, std::placeholders::_1); + std::function exitCommand = std::bind(std::exit, EXIT_SUCCESS); + std::function setScaleCommand = [this](int id, float x, float y, float z) { + setScale(id, {x, y, z}); + }; + std::function createInstanceOfCommand = std::bind(&Game::createInstanceOf, this, std::placeholders::_1); + std::function toggleWireframeCommand = [this](float width){ lightingPass->setWireframeLineWidth(width); }; + std::function shCommand = std::bind(&Game::executeShellScript, this, std::placeholders::_1); + std::function 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 "; + std::string unsetHelp = "unset "; + std::string createInstanceOfHelp = "createinstanceof