5
\$\begingroup\$

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... ***/

    }

}
\$\endgroup\$
  • \$\begingroup\$ Are these your actual variable and function names? The comments indicate a lot of code has been snipped. That's often unnecessarily hard on the reviewers and sometimes renders the question off-topic. \$\endgroup\$ – Mast Dec 20 '15 at 16:38
  • \$\begingroup\$ @Mast: Yes, this is how the code actually was written. I've read here on another question that one shouldn't use long names when you can write it in shortcut. Func - function, cmd - command, arg - argument, pos - position, len - length etc. Is it generally considered bad practice? \$\endgroup\$ – RippeR Dec 20 '15 at 16:47
  • \$\begingroup\$ I also posted it on Github - its 100% working code. Comments are to explain what given function/part of code does (or to help me remember what was the idea behind this code ;)). \$\endgroup\$ – RippeR Dec 20 '15 at 16:52
  • 2
    \$\begingroup\$ Where did you find that advice? Shortened names are ok as long as they aren't ambiguous, but I see a lot of 1-character variables in your code which are definitely bad practice. This will probably be pointed out in one of the answers. \$\endgroup\$ – Mast Dec 20 '15 at 16:53
  • \$\begingroup\$ It's C++, not JavaScript :-) \$\endgroup\$ – Mast Dec 20 '15 at 16:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.