I've created a console/terminal command handling module that allows the programmer to bind functions to a command name and later execute them from std::string. What's new is that it handles most stuff automatically and allows for usage such as:
void func1_ptr(const std::string& name) {
std::cout << "Hello " << name << "!" << std::endl;
}
std::function<void(float)> func2_wrapper = [](float x) {
std::cout << x << "^2 = " << x*x << std::endl;
}
auto func3_lambda = [](int x, float y, const std::string& z) {
std::cout << x << y << z << std::endl;
}
/* ... */
CommandConsole cmd;
cmd.bind("func1", func_ptr); // default parser, types are infered from function
cmd.bind("func2", func2_wrapper); // default parser, types are infered from function
// custom parser (needed for lambdas)
cmd.bind("func3", func3_lambda, Parser::BasicParser::parse<int, float, std::string>);
cmd.execute("func1 John"); // func1_ptr("John")
cmd.execute("func2 11.0"); // func2_wrapper(11.0f)
cmd.execute("func3 0 3.14 \"hello world\""); // func3_lambda(0, 3.14f, "hello world")
Under the hood it uses tuples, variadic templates and metaprogramming magic to deduce types, parse input according to types of function's arguments and invoke function with parsed parameteres. Any advices/tips would be welcome. :)
Code
CommandConsole.hpp
Command console object. It stores binded functions and executes them using parsers. Parser is a function p
that takes two arguments - function f
and std::string
containing arguments for that function to be invoked with (just like char* argv[]
from main()
). It parses given string to get arguments for f
and invokes it with them. Generic multi-purpouse parser is available (Parser::BasicParser
template).
class CommandConsole {
public:
// Bind command with function using custom parser
template<typename Func, typename FuncParser>
void bind(const std::string& cmdName, const Func& func, const FuncParser& funcParser) {
_command_parsers[cmdName] = std::bind(funcParser, func, std::placeholders::_1);
}
// Bind command with function using default (basic) parser - std::function wrapper
template<typename... FuncArgs>
void bind(const std::string& cmdName, const std::function<void(FuncArgs...)>& func) {
bind(cmdName, func, Parser::BasicParser::parse<FuncArgs...>);
}
// Bind command with function using default (basic) parser - function pointer
template<typename... FuncArgs>
void bind(const std::string& cmdName, void(*func)(FuncArgs...)) {
bind(cmdName, func, Parser::BasicParser::parse<FuncArgs...>);
}
// Execute command with given arguments
void execute(const std::string& command) {
// Command name
std::size_t name_pos = command.find_first_not_of(" \t\r\n");
std::size_t name_end = command.find_first_of(" \t\r\n", name_pos);
std::size_t name_len =
(name_pos != std::string::npos) ?
((name_end == std::string::npos ? command.length() : name_end) - name_pos) : 0;
// Arguments
std::size_t args_pos = std::min(name_pos + name_len + 1, command.length());
std::size_t args_end = command.find_last_not_of(" \t\r\n");
std::size_t args_len = std::max(args_pos, args_end) - args_pos + 1;
// Values
std::string cmd_name = (name_len > 0) ? command.substr(name_pos, name_len) : "";
std::string cmd_args = (name_len > 0) ? command.substr(args_pos, args_len) : "";
// Execution
if(_command_parsers.count(cmd_name) > 0)
_command_parsers[cmd_name](cmd_args);
}
private:
std::map<std::string, std::function<void(const std::string&)>> _command_parsers;
};
BasicParser
Template function Parser::BasicParser::parse<Ts...>(Function f, string args)
. Parses args
to std::tuple<Ts...>
and then unwrapes it to use as arguments for f
. Parser::BasicParser::parseFirstArg(string args)
return pair of string - first parsed argument from args
and rest of args
without first argument bo parsed next.
namespace Parser
{
namespace BasicParser
{
/**
* Forward declaration
*/
namespace detail
{
template<typename T, typename U, typename... Args>
std::tuple<T, U, Args...> ArgToTupleSplitter(const std::string& str);
template<typename T>
std::tuple<T> ArgToTupleSplitter(const std::string& str);
}
/**
* Basic Parser
*/
template<typename... Ts>
void parse(const std::function<void(Ts...)>& func, const std::string& cmdArgs) {
// Remove references in types to avoid conversion problems
std::tuple<typename std::remove_reference<Ts>::type...> tupledArgs =
detail::ArgToTupleSplitter<typename std::remove_reference<Ts>::type...>(cmdArgs);
CmdArgApplyer::apply(func, tupledArgs);
}
/**
* Basic parser - Specialization for commands with 0 arguments.
* Any passed arguments are ignored.
*/
template<>
void parse<>(const std::function<void()>& func, const std::string& cmdArgs) {
(void) cmdArgs; // unused parameter warning
func();
}
/**
* Basic argument parsing - returns tuple <arg0, rest>
*/
std::tuple<std::string, std::string> parseFirstArg(const std::string& args) {
std::size_t i = 0, j = 0, k; // first argument is substring of range [i, j] in 'args'
// Skip whitespaces before argument
while(i < args.length() && std::isspace(args[i]))
++i;
// If all characters are whitespaces, return empty argument
if(i == args.length())
return std::make_tuple(std::string(""), std::string(""));
if(args[i] == '\"') {
// If argument starts with opening ", then look for closing "
j = ++i;
while(j < args.length() && args[j] != '\"')
++j;
} else {
// If argument starts from anything else, it ends upon first whitespace
j = i + 1;
while(j < args.length() && !std::isspace(args[j]))
++j;
}
// Verify that k is in [0, args.length()] range and remove whitespaces from left side
k = std::min(j + 1, args.length());
while(k < args.length() && isspace(args[k]))
++k;
return std::make_tuple(args.substr(i, j - i), args.substr(k));
}
/***************************************************************************************/
namespace detail
{
/**
* ArgToTupleSplitter<T, U, Args...> - splits arguments in std::string to std::tuple<T, U, Args...> between whitespaces.
* Last argument uses what's left in argument list.
*
* T - current argument type
* U - next argument type
* Args... - possible next argument types
*/
template<typename T, typename U, typename... Args>
std::tuple<T, U, Args...> ArgToTupleSplitter(const std::string& str) {
std::tuple<std::string, std::string> parsedArgs = parseFirstArg(str);
std::string& this_arg = std::get<0>(parsedArgs);
std::string& rest_args = std::get<1>(parsedArgs);
return std::tuple_cat(
std::make_tuple(Parser::detail::CmdArgConvert<std::string, T>{}(this_arg)), // this argument
ArgToTupleSplitter<U, Args...>(rest_args) // rest of arguments
);
}
/**
* ArgToTupleSplitter<T> - specialization to ArgToTupleSplitter<T, U, Args...> which handles last argument.
* This method uses as argument's source whole string, not only till first whitespace!
*
* T - current argument type
*/
template<typename T>
std::tuple<T> ArgToTupleSplitter(const std::string& str) {
return std::make_tuple(Parser::detail::CmdArgConvert<std::string, T>{}(str));
}
}
}
}
CmdArgApplier
Helper module that takes as arguments function and tuple, unwraps tuple and invokes function with tuple's values as arguments.
namespace Parser
{
namespace CmdArgApplier
{
/**
* Forward declaration
*/
namespace detail
{
template <std::size_t N>
struct CmdArgApplier_Helper;
}
/**
* CmdArgApplier
*/
template<typename Func, typename... Args>
void apply(const Func& func, const std::tuple<Args...>& args) {
detail::CmdArgApplier_Helper< std::tuple_size<std::tuple<Func, Args...>>::value - 1 >::apply(func, args);
}
/************************************************************/
namespace detail
{
template <std::size_t N>
struct CmdArgApplier_Helper
{
template <typename Func, typename Tuple, typename... Args>
static void apply(const Func& func, const Tuple& tuple, const Args&... args) {
CmdArgApplier_Helper<N - 1>::apply(func, tuple, std::get<N - 1>(tuple), args...);
}
};
template <>
struct CmdArgApplier_Helper<0>
{
template <typename Func, typename Tuple, typename... Args>
static void apply(const Func& func, const Tuple& tuple, const Args&... args) {
(void) tuple; // unused argument warning
func(args...);
}
};
}
}
}
CmdArgConverter
Template <F, T>
that helps convert value of type F
to type T
. Unified interface.
namespace Parser
{
namespace detail
{
/**
* CmdArgConverter - template functors used to convert current argument from std::string to given type
* In default version, it uses constructor with FROM-type argument.
*
* FROM - type from which current argument will be converted, usually std::string
* TO - type to which you current argument will be converted
*/
template<typename FROM, typename TO>
class CmdArgConvert {
public:
TO operator()(const FROM& from) { return TO(from); }
};
/**
* String to int converter
*/
template<>
class CmdArgConvert<std::string, int> {
public:
int operator()(const std::string& from) { return std::stoi(from); }
};
/*** a lot more... ***/
}
}
;)
). – RippeR Dec 20 '15 at 16:52