Search code examples
c++command-pattern

What are good practices to parse tokens from a users input?


I am implementing a system where the user should feed my program a command. I then split up the string of their input into tokens, delineated by spaces, stored in a vector.

My initial idea was to use a bunch of if-else statements to determine which command should be triggered based on their input. Something like so:

vector<string> userInput;

if (userInput[0] == "help") {
    //do something
} else if (userInput[0] == "exit") {
    //do something else
} else if (....) {

and so on. But this seems clumsy. Are there better practices for approaching a problem like this? I've already looked into implementing a Command pattern, and even while using it, it appears like I'll run into the same problem of parsing user input to instantiate/execute a specific command.

Thanks in advance!


Solution

  • You can use an std::unordered_map to store a mapping from the command name to its handler.

    Example:

    #include <unordered_map>
    #include <vector>
    #include <string>
    
    // The command handlers.
    void help(std::vector<std::string> const&);
    void exit(std::vector<std::string> const&);
    
    // CommandHandler is a pointer to a function.
    using CommandHandler = void(*)(std::vector<std::string> const&);
    
    // Maps a string to a function pointer.
    using CommandHandlers = std::unordered_map<char const*, CommandHandler>;
    
    // Associate command names with handlers. 
    CommandHandlers const command_handlers{
        {"help", help},
        {"exit", exit},
        {"abort", [](auto&) { std::abort(); }} // Can use a stateless lambda.
    };
    
    void handle(std::vector<std::string> const& userInput) {
        auto found = command_handlers.find(userInput[0].c_str());
        if(found == command_handlers.end())
            ; // Handle invalid command.
        else
            found->second(userInput); // Invoke the command handler.
    }
    

    A plain array with linear or binary search can be used instead of std::unordered_map if the number of commands is small.

    std::function<void(std::vector<std::string> const&)> can be used instead of plain function pointer to allow stateful lambdas or member functions as command handlers.