0

For long while now I have been struggling with creating the input method for my program.

I want the program to accept input in forms like this:

function entity(number,text,text)
function entity(number)
function entity(int, text, int)

I want the program to operate based on the commands inputted by the user in the form shown above. They chose the function e.g. add entity e.g. students and fill the data about those entities in the brackets. Now what I want to achieve is split up this string into chunks for add/entity and all values in the brackets so I can the operate based on them.

What I managed to achieve until now is completely wrong and since I am fairly new to the whole c++ thing it does make it awfully difficult to figure it all out in semi decent amount of time

string function, entity;
    char values[1024];
    char command[1024];
    cin.getline(command, 1024);


    stringstream t;
    t << command;
    int  number[5];
    char parameter1[20];
    char parameter2[20];
    t >> funtion >> entity >> values;
    sscanf_s(values, "%s %s %s ", number, _countof(number), parameter1, _countof(parameter1), parameter2, _countof(parameter2));

In simple words I want to cut the string/char (I tried different things with both of those) from function entity(parameter,parameter,parameter) into small chunks that I can use elsewhere in my program. Need to get rid of brackets, commas and take each word separately.

Krzysztof Kosiński
  • 4,225
  • 2
  • 18
  • 23
user3026386
  • 55
  • 1
  • 2
  • 8
  • `sscanf_s` might work but it's not very C++ as you point out. – tadman Dec 04 '13 at 22:09
  • 1
    Why aren't you using [std::string](http://www.cplusplus.com/reference/string/string/), [find_first_of](http://www.cplusplus.com/reference/string/string/find_first_of/), [substr](http://www.cplusplus.com/reference/string/string/substr/), and so on? – David Schwartz Dec 04 '13 at 22:11
  • Possible duplicate: http://stackoverflow.com/questions/236129/how-to-split-a-string-in-c? – ApproachingDarknessFish Dec 04 '13 at 22:11
  • Could you give me an example of how to use those functions correctly?, I am aware the topic similar to mine already took place but I kind of struggled to implement the method used in the link you posted. I tried to do it through stringstream but could not find luck. :( – user3026386 Dec 04 '13 at 22:16

3 Answers3

0

The method you've chosen is a very poor fit for the problem, especially if you later want to add more command forms to the program.

The proper way to handle this kind of input is to write a parser using a parser generator such as GNU Bison, but in your case that might be overkill, would take too much space to explain, and requires some knowledge about languages and grammars (in the computer science sense). Search for "GNU Bison" or "parser generator" on Google if you want to learn more about that.

A more limited but easier to understand solution is to use C++11 regular expressions. First match the entire line to find the function, entity and parameter list, and then match the parameter list depending on the entity or function type. The code would look something like that:

// This regular expression matches one line of input (one command).
// A line has some text starting with a letter (the function name),
// then one or more spaces, another piece of text,
// and then a string of arbitrary characters inside parentheses.
// Parentheses not preceded by \\ denote capture groups.
// Any characters matched by them are later accessible through
// the std::smatch object.
std::regex rx_line("([A-Za-z][A-Za-z0-9]*)\\s+"
                   "([A-Za-z][A-Za-z0-9]*)\\s*"
                   "\\((.*)\\)\\s*");

// Retrieve one non-empty line of input, skipping whitespace.
std::string line;
std::getline(std::cin >> std::ws, line);

// Match the line regex to the line, capturing the parts of text which we want.
std::smatch line_match;
if (!std::regex_match(line, line_match, rx_line)) {
    // Report error: the command has incorrect format.
}

// Retrieve parts of the input matched by capture groups.
std::string function = line_match[1];
std::string entity = line_match[2];
std::string params = line_match[3];

// Depending on which entity was mentioned in the command,
// match the parameters to a suitable regex.
if (entity == "student") {
    // number, then comma, then text in quotes, then comma,
    // and finally a number.
    std::regex rx_student("([1-9][0-9]*)\\s*,"
                          "\\s*\"([^\"]*)\"\\s*,"
                          "\\s*([1-9][0-9]*)");
    std::smatch student_match;
    if (!std::regex_match(params, student_match, rx_student)) {
        // report error
    }

    // Retrieve parameters from capture groups.
    int int1 = std::atoi(student_match[1].str().c_str());
    std::string text = student_match[2];
    int int2 = std::atoi(student_match[3].str().c_str());

    // ...
    // do something with the data
    // ...
} else if (entity == "account") {
    // and so on
} else {
    // error - unknown entity
}

See e.g. http://www.cplusplus.com/reference/regex/ECMAScript/ for a description of regular expression syntax.

Krzysztof Kosiński
  • 4,225
  • 2
  • 18
  • 23
0

I think it is worth starting at the beginning: although it works to have a fixed size buffer to read into, I think it is easier to use a std::string to capture each line. Further, you should always test after reading if the read operation was successful. That is, I would write an outer loop processing individual lines:

for (std::string line; std::getline(std::cin >> std::ws, line); ) {
    // process each individual line
}

The use of the manipulator std::ws will make thing inside the loop a bit easier: it skips all whitespace, e.g., leading spaces on a line and empty lines. That is, if std::getline() managed to read a line, it is already known that this line isn't empty.

Based on what you wrote about your functions, it seems they get different parameters. So I would create a string stream to process an individual line, read the first word as a command and depending on that split read further parameters:

std::istringstream sin(line);
std::string        command;
if (sin >> command) {
    if (command == "add") {
        int         number;
        std::string name;
        if (sin >> number >> name) {
            entity(number, name);
        }
        else {
            std::cout << "ERROR: failed to read add command from '" << line << "'\n";
        }
    else if (command == "???") {
        // ...
    }
    else {
        std::cout << "ERROR: unrecognized command on line '" << line << "'\n";
    } 
}

It would be possible to write a function which determines the parameters from a function pointer passed to it, reads those, and calls the function but this would require a few advanced techniques. Based on your question providing that answer would provide you with a solution which is probably a bit too complicated.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
0

What your trying to achieve is parsing, not just raw splitting. Here is an example that will work for you using boost::spirit. I would strongly advice you using such tools because it allows you a bit of flexibility that raw litteral parsing with scanf won't provide you. This is not the only tool available, but it is worth the time you'll spent learning it.

So here my small commented example, I hope it does help you:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/qi_as.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <iterator>

// Parameters can be either string or int
typedef boost::variant<std::string, double> Parameter;

// Here is the function where we read the command and retrieve entity/function
// and parameters
bool read_command(const std::string& str)
{
    // Just to ease the parser writing
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phoenix = boost::phoenix;

    typedef qi::rule<std::string::const_iterator, std::string()> rule;

    // Variables where we will store the results
    std::string entity;
    std::string function;    
    std::vector<Parameter> arguments;

    // An identifier 
    rule identifier = qi::alpha >> *(qi::alnum | qi::char_("_"));

    // A parameter is either a number or an identifier, there is a issue if we
    // use "rule" type here, double are not read correctly anymore...
    auto parameter = qi::double_ | identifier;

    // The whole command  
    bool r = qi::phrase_parse(str.begin(), str.end(),
        ( identifier >> identifier >> '(' >> (parameter % ',' ) >> ')' ), 
        qi::space, entity, function, arguments); // Use qi "magic" to store result

    // Print everything
    std::cout 
    << "Parsing result:" << std::endl
    << " - Entity: " << entity << std::endl
    << " - Function: " << function << std::endl
    << " - " << arguments.size() << " parameter(s): ";

    // Use copy to print all the arguments
    std::copy(arguments.begin(), arguments.end(),
              std::ostream_iterator<Parameter>(std::cout, ", "));
    std::cout << std::endl;
    return r;
}

int main()
{
    std::string str;

    // Read the line   
    while (getline(std::cin, str))
    {
        // Check that this is a line we do want to parse
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
        {
            break;
        }

        // Read the command and output the result
        if (read_command(str))
        {
            std::cout << "Parsing succeeded! " << std::endl;
        }
        else
        {
            std::cout << "Parsing failed." << std::endl;
        }
    }

    return 0;
}

Output example:

$ ./a.exe
test f(1)
Parsing result:
 - Entity: test
 - Function: f
 - 1 parameter(s): 1,
Parsing succeeded!
entity function(1, a, b)
Parsing result:
 - Entity: entity
 - Function: function
 - 3 parameter(s): 1, a, b,
Parsing succeeded!
er1_ ft_(1, r_)
Parsing result:
 - Entity: er1_
 - Function: ft_
 - 2 parameter(s): 1, r_,
Parsing succeeded!
Johan
  • 3,728
  • 16
  • 25