Last active
August 20, 2020 06:54
-
-
Save VirtuosoChris/25d3bb686c297923415b88bcb2fc38ed to your computer and use it in GitHub Desktop.
QuakeStyleConsole.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef QuakeStyleConsole_h | |
#define QuakeStyleConsole_h | |
/// | |
/// QuakeStyleConsole.h | |
/// Created by VirtuosoChris on 8/19/20. | |
/// | |
/// This is the backend of a console interface for games, to help with debugging, cheats, etc | |
/// This is intended to be lightweight and efficient, rather than a general purpose scripting solution. | |
/// It is intended to perform two main functions: printing and setting variables as the game executes, and executing compiled C++ code from within the game. | |
/// This is just the functionality, which is kept separate from a GUI implementation. See ImGuiQuakeConsole.h in this repo for a GUI frontend written in the Dear IMGUI library (https://github.com/ocornut/imgui) | |
/// For example programs see the demos folder in this repo. | |
/** | |
CONSOLE MANUAL : BUILT IN COMMANDS | |
(For API Usage, see the class documentation immediately below) | |
-- help : When using the console, type help to get general information, or help <topic> to print the help string on a given topic | |
-- listCmd lists the commands bound to the console. | |
--listCVars lists all the variables bound to the console. | |
--listHelp lists all the topics available for the help command | |
--var : You can create new variables in the console itself (in addition to from client code, see below) by using var : | |
eg, "var health 100" | |
-- echo : eg. echo health - prints the value of the variable to the console | |
-- set : eg. set health 25 - sets the value of a variable in the console | |
-- runFile <filename> - execute all the commands in a file as if the user typed them in sequence. | |
--$: Strings prefixed with $ are interpreted as variable names to dereference, and the identifiers will be replaced in the input with the variable value - eg. | |
var x listCmd | |
help $x # will be processed as "help listCmd" | |
--Lines starting with # are counted as comments and ignored | |
**/ | |
#include <iostream> | |
#include <fstream> | |
#include <map> | |
#include <queue> | |
#include <functional> | |
#define PUBLIC_INTERFACE public // pay attention to everything in the public_interface section, it's what you use in your code | |
#define IMPLEMENTATION // Everything below this notation is what's needed to make the console work internally | |
namespace Virtuoso | |
{ | |
class QuakeStyleConsole | |
{ | |
PUBLIC_INTERFACE: // the methods in this section are what you should use in your code | |
/// Constructor binds the default commands to the command table & initializes history buffer | |
QuakeStyleConsole(); | |
// --------------------------------------// | |
/* --------- COMMAND EXECUTION --------- */ | |
// --------------------------------------// | |
// call one of these functions to make the console do things - eg. the first one when you press enter | |
// and your GUI console widget gives you a string | |
// You can execute all commands in an input stream until EOF with executeUntilEOF(), | |
// or run every line in a file (eg. a startup or debug playback file) with executeFile() | |
/// Execute command line passed in as string. Console output goes to "output" | |
void commandExecute(const std::string& str, std::ostream& output); | |
/// Get a command line from the input stream and execute it. Console output goes to "output" | |
void commandExecute(std::istream& input, std::ostream& output); | |
/// execute commands from an istream until EOF | |
void executeUntilEOF(std::istream& f, std::ostream& output); | |
/// execute commands from a file (named by the input string 'f') until EOF | |
void executeFile(const std::string& f, std::ostream& output); | |
//------------------------------------// | |
/*----------- ADDING CVARS -----------*/ | |
//-------------------------------------// | |
// Call one of these to add variables (eg, Player.health) from your c++ code to the console. | |
// This makes them available to built in commands using the given "varname" - dereference them with $, print them with echo, and set them with "set" | |
/// function which takes in a string and a variable from client code we want to associate with it in the console. Takes in help string to describe the variable to the user. | |
template <class T> | |
void bindCVar(const std::string& varname, T& var, const std::string& help); | |
/// function which takes in a string and a variable from client code we want to associate with it in the console. | |
template <class T> | |
void bindCVar(const std::string& varname, T& var); | |
// ------------------------------------// | |
/* --------- ADDING COMMANDS ----------*/ | |
// ------------------------------------// | |
// You should call console.bindCommand() with the command name you want and a C++ function from your code with an arbitrary signature. Optionally provide a help string for the command as a third argument | |
// You shouldn't need to worry about which overload or template arguments to use - it should resolve automatically. | |
// If your function has arguments, so long as there is an istream operator to read them from the input, | |
// the console will automatically parse them with variadic template and lambda magic, so you never have to write a parser | |
// IF YOU ARE USING A MEMBER FUNCTION - we need to wrap the "this" pointer too, so call bindMemberCommand() which takes the object reference as the first argument after the command name | |
/// add a command to the console using a function pointer that takes no arguments. set the associated help string to the "help" argument | |
void bindCommand(const std::string& commandName, void (*fptr)(void) , const std::string& help = ""); | |
///add a command to the console using a function pointer that takes arbitrary arguments, then set the associated help string to the "help" argument | |
template<typename ...Args> | |
void bindCommand( const std::string& commandName, void (*fptr)(Args...) , const std::string& help = ""); | |
///add a command to the console using a function object with arbitrary arguments, then set the associated help string to the optional "help" argument | |
template <typename ...Args> | |
void bindCommand(const std::string& commandName, std::function<void(Args...)> fun, const std::string& help = ""); | |
/// same as bindCommand, but for a member function of an object. Object instance is first argument after the command name | |
template<typename O, typename ...Args> | |
void bindMemberCommand(const std::string& commandName, O& obj, void (O::*fptr)(Args...), const std::string& help = ""); | |
typedef std::function< void (std::istream& is, std::ostream& os)> ConsoleFunc; | |
/// the bindCommand that actually does the work of adding commands to the table AFTER they've been coerced to a ConsoleFunc that takes the input/output from executeCommand. Takes optional help string. | |
void bindCommand(const std::string& commandName, ConsoleFunc f, const std::string& help = ""); | |
// ------------------------------------// | |
/* --------- HISTORY FILES ----------- */ | |
// ------------------------------------// | |
/* Saving and loading the history buffer of previously executed commands to file */ | |
/// populate the command buffer from an input file named by string inFile | |
bool loadHistoryBuffer(const std::string& inFile); | |
/// write the history buffer to file named by string outFile | |
void saveHistoryBuffer(const std::string& outFile); | |
/// populate the command buffer from an input file named by istream inFile | |
void loadHistoryBuffer(std::istream& inFile); | |
/// write the history buffer to file named by ostream outFile | |
void saveHistoryBuffer(std::ofstream& outfile); | |
// ------------------------------------// | |
/* --------- OTHER ------------------- */ | |
// ------------------------------------// | |
/// sets the help string (see built in 'help' command) for a given topic | |
void setHelpTopic(const std::string& topic, const std::string& data); | |
template <typename t> | |
class WindowedQueue; | |
typedef WindowedQueue<std::string> ConsoleHistoryBuffer; | |
const ConsoleHistoryBuffer& historyBuffer() const; | |
IMPLEMENTATION | |
/// WindowedQueue - We implement a ring buffer for the command history as a queue | |
template<class T> | |
class WindowedQueue: protected std::queue<T> | |
{ | |
void fix_size() | |
{ | |
while (size() > m_capacity) | |
{ | |
std::queue<T>::pop(); | |
} | |
} | |
std::size_t m_capacity; | |
public: | |
WindowedQueue(std::size_t maxCapacity) : m_capacity(maxCapacity) | |
{ | |
} | |
void capacity(std::size_t newCapacity) | |
{ | |
m_capacity = newCapacity; | |
fix_size(); | |
} | |
std::size_t capacity() const | |
{ | |
return m_capacity; | |
} | |
using std::queue<T>::operator=; | |
using std::queue<T>::front; | |
using std::queue<T>::back; | |
using std::queue<T>::empty; | |
using std::queue<T>::size; | |
using std::queue<T>::pop; | |
using std::queue<T>::swap; | |
T& operator[](size_t idx) | |
{ | |
return this->c[idx]; | |
} | |
const T& operator[](size_t idx) const | |
{ | |
return this->c[idx]; | |
} | |
template<class... Args> | |
void emplace(Args&&... args) | |
{ | |
std::queue<T>::emplace(std::forward<Args>(args)...); | |
fix_size(); | |
} | |
void push( const T& value ) | |
{ | |
std::queue<T>::push(value); | |
fix_size(); | |
} | |
void push( T&& value ) | |
{ | |
std::queue<T>::push(value); | |
fix_size(); | |
} | |
}; | |
protected: | |
static const unsigned int defaultHistorySize = 10u; ///size of the history file | |
ConsoleHistoryBuffer history_buffer; ///history buffer of previous commands | |
typedef std::map < std::string, ConsoleFunc > CommandTable; | |
typedef std::map < std::string, ConsoleFunc > CVarReadTable; | |
typedef std::map < std::string, ConsoleFunc > CVarPrintTable; | |
typedef std::map < std::string, std::string > HelpTable; | |
/// maps strings naming cVars to functions which read them from a std::istream. | |
/// This allows the console to parse variables of any type representable as text without modifying the console code or adding custom parsing code. | |
CVarReadTable cvarReadFTable; | |
/// maps strings naming cVars to functions which write the variables of arbitrary type out to a std::ostream | |
CVarPrintTable cvarPrintFTable; | |
///maps strings to std function objects, representing the available commands to the user. eg, quit, set, etc | |
CommandTable commandTable; | |
/// maps names of functions or cvars to string literals containing helpful information on their use | |
HelpTable helpTable; | |
///function which simply sets the value of an arbitrary type based on what's in the input stream | |
///the arguments are "eaten" by std bind, allowing it to be stored as type void (*x)(void) in the cvarReadFTable | |
template <class T> | |
void setCvar(std::istream& is, std::ostream& os, T* var); | |
///function which simply prints the value of a variable to an output stream | |
///the arguments are "eaten" by std bind, allowing it to be stored as type void (*x)(void) in the cvarPrintFTable | |
template <class T> | |
void printCvar(std::ostream& os, T* var); | |
///dumps a list of available commands to the output stream | |
void listCmd(std::ostream& os) const; | |
///dumps a list of bound cvars to the output stream | |
void listCVars(std::ostream& os) const; | |
///dumps a list of available help topics to the output stream | |
void listHelp(std::ostream& os) const; | |
///The function associated with the built in command "set" which parses the name of a cvar, and if it is bound, sets the value based on | |
///the value in the input stream | |
void commandSet(std::istream& is, std::ostream& os); | |
///the function associated with built in command "echo", which prints the value of a cvar if it is bound. if not, reports an error. | |
void commandEcho(std::istream& is, std::ostream& os); | |
///prints help on a topic if the user types help < topic >, or a generic help message if the user just types help | |
void commandHelp(std::istream&, std::ostream&); | |
///creates a string variable from the console | |
void commandVar(); | |
///wrapper function which parses arguments to a function object of arbitrary type from the console's input stream then executes the function if the parsing was successful | |
template<typename... Args> | |
void parse(std::istream& is, std::ostream& os, std::function<void(Args...)> f); | |
///This function is called by populateAndExecute, and only executes the bound function if the parsing succeeds | |
///if parsing failed we do not want to pass in uninitialized garbage to the c++ function we bound | |
template <typename... Args> | |
void conditionalExecute(std::istream& is, std::ostream& os, std::function< void(Args...) > f, const Args&... args); | |
///adds the built-in commands to the command table | |
void bindBasicCommands(); | |
///helper function to return a default-constructed temp variable of a particular type. Used in parsing arguments for C++ functions bound to the console | |
template <class T> | |
T makeTemp(); | |
///for parsing arguments to C++ functions bound to the console. variadic template that recursively parses our function arguments in order | |
template <typename FirstType, typename... Args> | |
void populateTemps(std::istream& is, FirstType& in, Args&... Temps); | |
///for parsing arguments to C++ functions bound to the console. variadic template that recursively parses our function arguments in order. base case | |
template <typename FirstType> | |
void populateTemps(std::istream& is, FirstType& in); | |
///for parsing arguments to C++ functions bound to the console. variadic template that recursively parses our function arguments in order. base case | |
void populateTemps(std::istream& is) {} | |
///for parsing arguments to C++ functions bound to the console. call starts populating temp variables | |
template <typename... Args> | |
void goPopulateTemps(std::istream& is, Args&... temps); | |
///for parsing arguments to C++ functions bound to the console. Populates the temp variables using the istream, then calls the function with them | |
template<typename... Args> | |
void populateAndExecute(std::istream& is, std::ostream& os, std::function<void(Args...)> f, | |
typename std::remove_const<typename std::remove_reference<Args>::type >::type... | |
temps); | |
///helper function that takes an input line containing commands and dereferences any console variables whose names appear prefixed by the $ symbol | |
void dereferenceVariables(std::istream& is, std::ostream& os, std::string& str); | |
///assigns the value of a dynamically created console variable using the console's input stream | |
template<class T> | |
void assignDynamicVariable(std::istream& is, std::shared_ptr<T> var); | |
///writes a dynamically created console variable to the console output | |
template <class T> | |
void writeDynamicVariable(std::ostream& os, std::shared_ptr<T> var); | |
///bind dynamically created console variable to the variable table and set the associated help string | |
template<class T> | |
void bindDynamicCVar(const std::string& var, const T& value, const std::string& help); | |
///bind dynamically created console variable to the variable table. | |
template<class T> | |
void bindDynamicCVar(const std::string& var, const T& valueIn); | |
public: | |
///Dynamic variables are string-based variables that can be created, assigned, and dereferenced from the console itself rather than C++ code | |
struct DynamicVariable : public std::string | |
{ | |
}; | |
}; | |
} | |
template <class T> | |
inline T Virtuoso::QuakeStyleConsole::makeTemp() | |
{ | |
T temp{}; | |
return temp; | |
} | |
template <typename... Args> | |
inline void Virtuoso::QuakeStyleConsole::conditionalExecute(std::istream& is, std::ostream& os, std::function<void(Args...)> f, const Args&... args) | |
{ | |
if (is.fail()) | |
{ | |
os << "[error] : SYNTAX ERROR IN FUNCTION ARGUMENTS" << std::endl; | |
is.clear(); | |
} | |
else | |
{ | |
f(args...); | |
} | |
} | |
template <typename FirstType> | |
inline void Virtuoso::QuakeStyleConsole::populateTemps(std::istream& is, FirstType& in) | |
{ | |
is >> in; | |
} | |
//variadic template that recursively parses our function arguments in order | |
template <typename FirstType, typename... Args> | |
inline void Virtuoso::QuakeStyleConsole::populateTemps(std::istream& is, FirstType& in, Args&... Temps) | |
{ | |
is >> in; | |
populateTemps(is, Temps...); | |
} | |
template <typename... Args> | |
inline void Virtuoso::QuakeStyleConsole::goPopulateTemps(std::istream& is, Args&... temps) | |
{ | |
populateTemps(is, temps...); | |
} | |
template<typename... Args> | |
inline void Virtuoso::QuakeStyleConsole::populateAndExecute(std::istream& is, std::ostream& os, std::function<void(Args...)> f, | |
typename std::remove_const<typename std::remove_reference<Args>::type >::type... | |
temps) | |
{ | |
goPopulateTemps< typename std::remove_const<typename std::remove_reference<Args>::type >::type...>(is, temps...); | |
conditionalExecute<Args...>(is, os, f, temps...); | |
} | |
//function that gets bound as type void to the console with a second function object that may have multiple arguments of various types | |
//this function gets called when the user enters the function name into the console. | |
template<typename... Args> | |
inline void Virtuoso::QuakeStyleConsole::parse(std::istream& is, std::ostream& os, std::function<void(Args...)> f) | |
{ | |
//first we have to create a bunch of temp variables and pass them into the populateAndExecute function | |
//the temp variables are needed to store the result. There's no guarantee in c++ for argument evaluation order, so we can't just | |
//skip the intermediate step of passing constructed temps into a second function | |
populateAndExecute<Args...>( is, os, f, (makeTemp< typename std::remove_const<typename std::remove_reference< Args>::type >::type > () )... ); | |
} | |
inline void Virtuoso::QuakeStyleConsole::bindCommand(const std::string& str, void (*fptr)(void), const std::string& help) | |
{ | |
commandTable[str] = [fptr](std::istream&, std::ostream&){fptr();}; | |
if (help.length()) setHelpTopic(str, help); | |
} | |
template<typename ...Args> | |
inline void Virtuoso::QuakeStyleConsole::bindCommand( const std::string& str, void (*fptr)(Args...) , const std::string& help) | |
{ | |
commandTable[str] = | |
[this, fptr](std::istream& is, std::ostream& os) | |
{ | |
this->parse<Args...>(is, os, fptr); | |
}; | |
if (help.length()) setHelpTopic(str, help); | |
} | |
template <typename ...Args> | |
inline void Virtuoso::QuakeStyleConsole::bindCommand(const std::string& str, std::function<void(Args...)> fun, const std::string& help) | |
{ | |
commandTable[str] = | |
[this, fun](std::istream& is, std::ostream& os) | |
{ | |
this->parse<Args...>(is, os, fun); | |
}; | |
if (help.length()) setHelpTopic(str, help); | |
} | |
inline void Virtuoso::QuakeStyleConsole::bindCommand(const std::string& str, ConsoleFunc fun, const std::string& help) | |
{ | |
if (help.length()) setHelpTopic(str, help); | |
commandTable[str] = fun; | |
} | |
inline void Virtuoso::QuakeStyleConsole::setHelpTopic(const std::string& str, const std::string& data) | |
{ | |
helpTable[str] = data; | |
} | |
template <class T> | |
inline void Virtuoso::QuakeStyleConsole::bindCVar(const std::string& str, T& var) | |
{ | |
cvarReadFTable[str] = | |
[this, &var](std::istream& is, std::ostream& os) | |
{ | |
this->setCvar<T>(is, os, &var); | |
}; | |
cvarPrintFTable[str] = | |
[this, &var](std::istream& is, std::ostream& os) | |
{ | |
this->printCvar<T>(os, &var); | |
}; | |
} | |
template <class T> | |
inline void Virtuoso::QuakeStyleConsole::bindCVar(const std::string& str, T& var, const std::string& help) | |
{ | |
bindCVar(str, var); | |
setHelpTopic(str, help); | |
} | |
template <class T> | |
void Virtuoso::QuakeStyleConsole::setCvar(std::istream& is, std::ostream& os, T* var) | |
{ | |
T tmp; ///temp argument is a necessity; without it we risk corruption of our variable value if there is a parse error. Should be no issue unless someone is using this to parse a ginormous structure or copy construction invokes a state change. | |
is >> tmp; | |
if (is.fail()) | |
{ | |
os << "[error] : SYNTAX ERROR IN VARIABLE PARSER" << std::endl; | |
is.clear(); | |
} | |
else | |
{ | |
*var = tmp; | |
} | |
} | |
template <class T> | |
inline void Virtuoso::QuakeStyleConsole::printCvar(std::ostream& os, T* var) | |
{ | |
os << *var <<std::endl; | |
} | |
template<class T> | |
inline void Virtuoso::QuakeStyleConsole::assignDynamicVariable(std::istream& is, std::shared_ptr<T> var) | |
{ | |
is >> (*var); | |
} | |
template <class T> | |
inline void Virtuoso::QuakeStyleConsole::writeDynamicVariable(std::ostream& os, std::shared_ptr<T> var) | |
{ | |
const T& deref = *(var); | |
os << deref; | |
} | |
template<class T> | |
inline void Virtuoso::QuakeStyleConsole::bindDynamicCVar(const std::string& var, const T& value, const std::string& help) | |
{ | |
helpTable[var] = help; | |
bindDynamicCVar(var, value); | |
} | |
template<class T> | |
inline void Virtuoso::QuakeStyleConsole::bindDynamicCVar(const std::string& var, const T& valueIn) | |
{ | |
std::shared_ptr<T> ptr(new T(valueIn)); | |
cvarReadFTable[var] = | |
[this, ptr](std::istream& is, std::ostream& os) | |
{ | |
this->assignDynamicVariable<T>(is, ptr); | |
}; | |
cvarPrintFTable[var] = | |
[this, ptr](std::istream& is, std::ostream& os) | |
{ | |
this->writeDynamicVariable<T>(os, ptr); | |
}; | |
} | |
inline void Virtuoso::QuakeStyleConsole::executeUntilEOF(std::istream& f, std::ostream& output) | |
{ | |
while(!f.eof()) | |
{ | |
commandExecute(f, output); | |
} | |
} | |
inline void Virtuoso::QuakeStyleConsole::executeFile(const std::string& x, std::ostream& output) | |
{ | |
std::ifstream f(x); | |
if(!f.is_open()) | |
{ | |
output << "[error] : unable to open file!" << std::endl; | |
} | |
else | |
{ | |
executeUntilEOF(f, output); | |
} | |
f.close(); | |
} | |
inline void Virtuoso::QuakeStyleConsole::commandHelp(std::istream& is, std::ostream& os) | |
{ | |
const char* genericHelp= "Type 'help' followed by the name of a command or variable to get help on that topic if available." | |
"\nType listCmd, listCVars, and listHelp to print lists of the available commands, variables, and help topics." | |
"\nUse $<varname> to dereference a variable in a command argument list and use # to comment the rest of a line"; | |
std::string x; | |
if (is >> x) | |
{ | |
HelpTable::const_iterator it = helpTable.find(x); | |
if (it != helpTable.end()) | |
{ | |
os << (it->second) << std::endl; | |
} | |
else | |
{ | |
os << "[error] : No help available on " << x << std::endl; | |
} | |
} | |
else | |
{ | |
os << genericHelp << std::endl; | |
} | |
} | |
inline void Virtuoso::QuakeStyleConsole::listHelp(std::ostream& os) const | |
{ | |
os << "\nAvailable help topics:"; | |
for (HelpTable::const_iterator it = helpTable.begin(); it != helpTable.end(); it++) | |
{ | |
os << "\n" << it->first; | |
} | |
os << std::endl; | |
} | |
inline void Virtuoso::QuakeStyleConsole::listCmd(std::ostream& os) const | |
{ | |
os << "\nAvailable commands:"; | |
for (CommandTable::const_iterator it = commandTable.begin(); it != commandTable.end(); it++) | |
{ | |
os << "\n" << it->first; | |
} | |
os << std::endl; | |
} | |
inline void Virtuoso::QuakeStyleConsole::listCVars(std::ostream& os) const | |
{ | |
os << "\nBound console variables:"; | |
for (CVarReadTable::const_iterator it = cvarReadFTable.begin(); it != cvarReadFTable.end(); it++) | |
{ | |
os << "\n" << it->first; | |
} | |
os << std::endl; | |
} | |
inline void Virtuoso::QuakeStyleConsole::commandSet(std::istream& is, std::ostream& os) | |
{ | |
std::string x; | |
if (!(is>>x)) | |
{ | |
os << "[error] : Syntax error parsing argument" << std::endl; | |
return; | |
} | |
CVarReadTable::iterator it = cvarReadFTable.find(x); | |
if (it != cvarReadFTable.end()) | |
{ | |
it->second(is, os); | |
} | |
else | |
{ | |
os << "[error] : Variable " << x << " unknown." << std::endl; | |
} | |
} | |
/// the function associated with built in command "echo", which prints the value of a cvar if it is bound. if not, reports an error. | |
inline void Virtuoso::QuakeStyleConsole::commandEcho(std::istream& is, std::ostream& os) | |
{ | |
std::string x; | |
if(!(is >> x)) | |
{ | |
os << "[error] : Syntax error parsing argument" << std::endl; | |
return; | |
} | |
CVarPrintTable::iterator it = cvarPrintFTable.find(x); | |
if (it!= cvarPrintFTable.end()) | |
{ | |
(it->second)(is, os); | |
} | |
else | |
{ | |
os << "[error] : Variable "<<x<<" unknown." << std::endl; | |
} | |
} | |
inline void Virtuoso::QuakeStyleConsole::commandExecute(const std::string& str, std::ostream& output) | |
{ | |
std::stringstream lineStream; | |
lineStream.str(str); | |
commandExecute(lineStream, output); | |
} | |
///reads a string from the input stream and executes the command associated with it, if there is one. if not, reports an error. | |
inline void Virtuoso::QuakeStyleConsole::commandExecute(std::istream& is, std::ostream& os) | |
{ | |
char ch; | |
while(!is.eof()) | |
{ | |
ch= is.peek(); | |
if(ch == '#') | |
{ | |
std::string tmp; | |
getline(is, tmp); | |
return; | |
} //if newline we will not parse anything else | |
else if(std::isspace(ch)) | |
{ | |
is.get(ch); | |
continue; | |
} | |
else | |
{ | |
break; | |
} | |
} | |
std::stringstream lineStream; | |
{ | |
std::string lineTemp; | |
getline( is, lineTemp); | |
history_buffer.push(lineTemp); | |
os<<"> "<<lineTemp<<std::endl; | |
dereferenceVariables(is, os, lineTemp); | |
lineStream.str(lineTemp);///\todo this constrains us to a single line. way to go later might be to require user or | |
///generated command parser to return string that was parsed | |
} | |
std::string x; | |
while (lineStream >> x) | |
{ | |
CommandTable::const_iterator it = commandTable.find(x); | |
if (it == commandTable.end()) | |
{ | |
os << "[error] : command " << x << " unknown" << std::endl; | |
} | |
else | |
{ | |
(it->second)(lineStream, os); //execute the command | |
} | |
os << '\n'; | |
} | |
} | |
inline void Virtuoso::QuakeStyleConsole::bindBasicCommands() | |
{ | |
std::function< void(const std::string&, const DynamicVariable& ) > f1 = | |
std::bind( | |
static_cast< void(QuakeStyleConsole::*)(const std::string&, const DynamicVariable&) > | |
( &QuakeStyleConsole::bindDynamicCVar<DynamicVariable>), | |
this, std::placeholders::_1, std::placeholders::_2 | |
); | |
bindCommand("var",f1, | |
"Type var <varname> <value> to declare a dynamic variable with name <varname> and value <value>." | |
"\nVariable names are any space delimited string and variable value is set to the remainder of the line." | |
); | |
bindCommand("listCmd", [this](std::istream& is, std::ostream& os){this->listCmd(os);}, "lists the available console commands"); | |
bindCommand("set", [this](std::istream& is, std::ostream& os){this->commandSet(is,os);}, "type set <identifier> <val> to change the value of a cvar"); | |
bindCommand("echo", [this](std::istream& is, std::ostream& os){this->commandEcho(is,os);}, "type echo <identifier> to print the value of a cvar"); | |
bindCommand("listCVars", [this](std::istream& is, std::ostream& os){listCVars(os);}, "lists the bound cvars"); | |
bindCommand("help", [this](std::istream& is, std::ostream& os){this->commandHelp(is, os);}, "you're a smartass"); | |
bindCommand("listHelp", [this](std::istream& is, std::ostream& os){this->listHelp(os);}, "lists the available help topics"); | |
bindCommand("runFile", [this](std::istream& is, std::ostream& os) | |
{ | |
std::string f; | |
is>>f; | |
this->executeFile(f, os); | |
}, | |
"runs the commands in a text file named by the argument"); | |
} | |
inline bool Virtuoso::QuakeStyleConsole::loadHistoryBuffer(const std::string& inFile) | |
{ | |
std::ifstream hfi(inFile); | |
if(hfi.is_open()) | |
{ | |
loadHistoryBuffer(hfi); | |
hfi.close(); | |
return true; | |
} | |
return false; | |
} | |
inline void Virtuoso::QuakeStyleConsole::saveHistoryBuffer(const std::string& outFile) | |
{ | |
if(history_buffer.size()) | |
{ | |
std::ofstream hfo(outFile); | |
saveHistoryBuffer(hfo); | |
hfo.close(); | |
} | |
} | |
inline void Virtuoso::QuakeStyleConsole::loadHistoryBuffer(std::istream& inFile) | |
{ | |
while(!inFile.eof()) | |
{ | |
std::string tmp; | |
std::getline(inFile,tmp); | |
if(tmp.length()) | |
{ | |
history_buffer.push(tmp); | |
} | |
} | |
} | |
inline void Virtuoso::QuakeStyleConsole::saveHistoryBuffer(std::ofstream& outfile) | |
{ | |
for(unsigned int i = 0; i < history_buffer.size(); i++) | |
{ | |
outfile << history_buffer[i] << std::endl; | |
} | |
} | |
inline const Virtuoso::QuakeStyleConsole::ConsoleHistoryBuffer& Virtuoso::QuakeStyleConsole::historyBuffer() const | |
{ | |
return history_buffer; | |
} | |
inline void Virtuoso::QuakeStyleConsole::dereferenceVariables(std::istream& is, std::ostream& os, std::string& str) | |
{ | |
size_t varBase = 0; | |
int n=0; | |
while((varBase = str.find('$', varBase)) != str.npos) | |
{ | |
size_t substrEnd = varBase; | |
size_t dollar = varBase; | |
varBase++; | |
n++; | |
for ( ; ((substrEnd < str.size()) && (!isspace(str[substrEnd]))); substrEnd++); | |
if(substrEnd == varBase) | |
{ | |
os << "[error] : EXPECTED IDENTIFIER AT $" << std::endl; | |
} | |
else | |
{ | |
std::string substr(str.substr(varBase, substrEnd-varBase)); | |
std::stringstream sstr; | |
CVarPrintTable::iterator it = cvarPrintFTable.find(substr); | |
// check that variable exists | |
if (it != cvarPrintFTable.end()) | |
{ | |
it->second(is, sstr); | |
str.replace( dollar, substrEnd, sstr.str()); | |
} | |
else | |
{ | |
os << "[error] : Variable " << substr << " not found" << std::endl; | |
} | |
} | |
} | |
} | |
inline Virtuoso::QuakeStyleConsole::QuakeStyleConsole() | |
: history_buffer(defaultHistorySize) | |
{ | |
bindBasicCommands(); | |
} | |
template<typename O, typename ...Args> | |
void Virtuoso::QuakeStyleConsole::bindMemberCommand(const std::string& commandName, O& obj, void (O::*fptr)(Args...), const std::string& help) | |
{ | |
auto mf = std::mem_fn(fptr); | |
std::function <void (const Args...)> fp = | |
[mf, &obj](const Args&... args) | |
{ | |
mf(obj, args...); | |
}; | |
bindCommand(commandName, fp); | |
if (help.length()) | |
{ | |
setHelpTopic(commandName, help); | |
} | |
} | |
/// here we just overload the iostream operators. The only real difference is that on input a dynamic variable takes a full line from input | |
inline std::ostream& operator<<(std::ostream& instream, const Virtuoso::QuakeStyleConsole::DynamicVariable& var) | |
{ | |
const std::string& str = var; | |
return instream << str; | |
} | |
/// dynamic variable definitions take a full line from the input stream | |
inline std::istream& operator>>(std::istream& istream, Virtuoso::QuakeStyleConsole::DynamicVariable& var) | |
{ | |
return getline(istream, var); | |
} | |
#endif /* QuakeStyleConsole_h */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment