I have a program that take commands from user and it will process different commands differently. For example:
ADD_STUDENT ALEX 5.11 175
ADD_TEACHER MERY 5.4
Well, it's too late for much chance at upvotes, but you guys got me thinking about this...
For robustness, you could split parsing into two stages: the first stage gets lines, and the second stage takes a line and does something with it.
For the first stage, you can use the getline
:
#include <string>
#include <sstream>
void ParseLines(std::istream& source)
{
while(source)
{
// Get a line from the source.
std::string inputLine;
std::getline(source, inputLine);
// Make a stream out of it.
std::istringstream inputStream(inputLine);
std::string command;
inputStream >> command;
if(inputStream) // Empty or bad line: skip
HandleCommand(command, inputStream);
}
}
The second stage handles the command. It could be something direct like this:
void HandleCommand(const std::string& command, std::istringstream& params)
{
if(command == "ADD_STUDENT")
{
float someFloat;
int someInt;
params >> someFloat >> someInt;
// add the student.
}
// etc.
}
But I have no shame and would implement a Factory paradigm:
#include <map>
typedef void (*CommandHandler)(const std::string&, std::istringstream&);
typedef std::map<std::string, CommandHandler> CommandTable;
static CommandTable gCommands; // Yep. A global. Refactor however you see fit.
void HandleCommand(const std::string& command, std::istringstream& params)
{
CommandTable::iterator handler = gCommands.find(command);
if(handler == gCommands.end())
{
// Handle "command not found" error.
return;
}
(*(handler->second))(command, params);
}
void AddStudent(const std::string& command, std::istringstream& params)
{
float someFloat;
int someInt;
params >> someFloat >> someInt;
// add the student.
}
// Other command handling functions here...
void RegisterCommands()
// Call this once prior to parsing anything,
// usually one of the first things in main().
{
gCommands["ADD_STUDENT"] = &AddStudent;
// ... other commands follow...
)
Haven't tested any of this, but it should be mostly there. Note any bugs in the comments.
P.S. This is highly inefficient and will run slower than a properly designed command parser, however, it should be good enough for most jobs.
Your problem is that using the >>
operator reads and clears a token from the stream.
if(iss >> command >> name >> height >> weight)
This (above) tries to read 4 tokens from the stream, and for every successful read, it clears the read data from the stream.
else if(iss >> command >> name >> height >> weight >> salary)
When you get to this (above) it means that some token could not be read and cast into the appropriate type, however it is likely that at least the command token is already stripped from the stream.
You could technically tokenize the entire input line, but that seems a little too far out from your level. If you did want to go into it, there is a nice page and tutorial here that will help you use strtok().
If you do not want to go that method, you can individually parse through your list of commands. Say you have read into a string named "command".
if (command == "ADD_STUDENT")
{
int weight, height, otherfield;
cout << ">" << flush;
cin >> weight >> height >> otherfield;
//do something, like add them to the database
}
That seems like your best bet, although it is a lot of coding, it is probably easier for you to accomplish. You could get really into it and use format strings like this:
scanf("%s, %s %d, %f", lastname, firstname, age, height);
This way, the input would look like this:
ADD_STUDENT Doe, John 30, 5.6
Your problem was deliciously underspecified. This always prompts me to supply an overblown example implementation using Boost Spirit.
Note: just don't hand this in as your homework assignment, please.
See it Live on Coliru with the following sample input:
ADD_STUDENT ALEX 5.11 175
ADD_STUDENT PUFF 6 7
ADD_STUDENT MAGIC 7 8
ADD_STUDENT DRAGON 8 9
ADD_TEACHER MERY 5.4 120 70000
PRINT MERY
ADD_TEACHER DUPLO 5.4 120 140000
PRINTALL 10
REMOVE ALEX
PRINT TEACHER SALARY
PRINT MERY PUFF MAGIC DRAGON
REMOVE MERY PUFF MAGIC DRAGON
PRINT TEACHER SALARY
Full code:
Update When including make_visitor.hpp
as shown here you can write the visitor code more elegantly:
auto print_salary = [&] ()
{
for(auto& p : names)
boost::apply_visitor(make_visitor(
[](Teacher const& v) { std::cout << "Teacher salary: " << v.salary << "\n"; },
[](Student const& v) {}),
p.second);
};
See adapted example Live on Coliru
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;
struct Person
{
std::string name;
double height, weight;
friend std::ostream& operator<<(std::ostream& os, Person const& s) {
return os << "Person { name:" << s.name << ", height:" << s.height << ", weight:" << s.weight << " }";
}
};
struct Student : Person
{
Student() = default;
Student(std::string n, double h, double w) : Person {n,h,w} {}
};
struct Teacher : Person
{
Teacher() = default;
Teacher(std::string n, double h, double w, double s) : Person {n,h,w}, salary(s) {}
double salary;
};
int main()
{
std::stringstream ss;
ss << std::cin.rdbuf();
std::map<std::string, boost::variant<Student, Teacher> > names;
using namespace qi;
auto add_student = phx::ref(names)[_1] = phx::construct<Student>(_1, _2, _3);
auto add_teacher = phx::ref(names)[_1] = phx::construct<Teacher>(_1, _2, _3, _4);
auto remove = phx::erase(phx::ref(names), _1);
auto print_all = [&] (int i) { for(auto& p : names) { std::cout << p.second << "\n"; if (--i==0) break; } };
auto print_salary = [&] ()
{
struct _ : boost::static_visitor<> {
void operator()(Teacher const& v) const { std::cout << "Teacher salary: " << v.salary << "\n"; }
void operator()(Student const& v) const { }
} v_;
for(auto& p : names) boost::apply_visitor(v_, p.second);
};
auto name_ = as_string[lexeme[+graph]];
if (phrase_parse(begin(ss.str()), end(ss.str()),
(
("ADD_STUDENT" >> name_ >> double_ >> double_) [ add_student ]
| ("ADD_TEACHER" >> name_ >> double_ >> double_ >> double_) [ add_teacher ]
| (eps >> "PRINT" >> "TEACHER" >> "SALARY") [ print_salary ]
| ("PRINTALL" >> int_) [ phx::bind(print_all, _1) ]
| ("PRINT" >> +name_ [ std::cout << phx::ref(names)[_1] << std::endl ])
| ("REMOVE" >> +name_ [ remove ])
) % +eol,
qi::blank))
{
std::cout << "Success";
}
else
{
std::cout << "Parse failure";
}
}
Output:
Person { name:MERY, height:5.4, weight:120 }
Person { name:ALEX, height:5.11, weight:175 }
Person { name:DRAGON, height:8, weight:9 }
Person { name:DUPLO, height:5.4, weight:120 }
Person { name:MAGIC, height:7, weight:8 }
Person { name:MERY, height:5.4, weight:120 }
Person { name:PUFF, height:6, weight:7 }
Teacher salary: 140000
Teacher salary: 70000
Person { name:MERY, height:5.4, weight:120 }
Person { name:PUFF, height:6, weight:7 }
Person { name:MAGIC, height:7, weight:8 }
Person { name:DRAGON, height:8, weight:9 }
Teacher salary: 140000
Success
Do it like so:
iss >> command;
if (!iss)
cout << "error: can not read command\n";
else if (command == "ADD_STUDENT")
iss >> name >> height >> weight;
else if (command == "ADD_TEACHER")
iss >> name >> height >> weight >> salary;
else if ...