Parsing a simple csv table with boosting spirit
I tried to use a boost-spirit
fairly simple cvs file format for parsing.
My csv file looks like this:
Test.txt
2
5. 3. 2.
6. 3. 6.
The first integer represents the number of lines to read, and each line consists of exactly three double values.
This is what I got so far.
main.cpp
#include <vector>
#include <fstream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/phoenix/object/construct.hpp>
std::vector<std::vector<double>> parseFile(std::string& content)
{
std::vector<std::vector<double>> ret;
using namespace boost::phoenix;
using namespace boost::spirit::qi;
using ascii::space;
int no;
phrase_parse(content.begin(), content.end(),
int_[ref(no) = _1] >> repeat(ref(no))[(double_ >> double_ >> double_)[
std::cout << _1 << _2 << _3
]], space
);
return ret;
}
int main(int arg, char **args) {
auto ifs = std::ifstream("Test.txt");
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
parseFile(content);
}
Now, instead of a string, std::cout << _1 << _2 << _3
I need something appending std::vector<double>
containing three values.
I've already tried _val=construct<std::vector<double>>({_1,_2,_3})
it but it doesn't work. So what am I doing wrong?
source to share
It's much easier than you thinkยน
std::vector<std::vector<double>> parseFile(std::string const& content) {
namespace px = boost::phoenix;
using namespace boost::spirit::qi;
int no;
std::vector<std::vector<double>> data;
bool ok = phrase_parse(content.begin(), content.end(),
int_ [ px::ref(no) = _1 ] >> eol
>> repeat(px::ref(no)) [ repeat(3) [double_] >> (eol|eoi)],
blank,
data
);
if (!ok)
throw std::runtime_error("Parse failure");
return data;
}
Watch Live On Coliru . It uses automatic propagation of attributes - Spirit's single most useful feature to begin with - and skipper instead , so we can still make out blank
space
eol
Now I suggest making it even easier:
bool ok = phrase_parse(
content.begin(), content.end(),
int_ >> eol >> *(+double_ >> (eol|eoi)) >> *eol >> eoi,
blank,
no, data
);
if (!ok && (no == data.size()))
throw std::runtime_error("Parse failure");
Or, in fact, it's even easier to use just the standard library:
#include <vector>
#include <iostream>
#include <fstream>
#include <iterator>
std::vector<std::vector<double>> parseFile(std::string const& fname) {
std::vector<std::vector<double>> data;
auto ifs = std::ifstream(fname);
size_t no = -1;
if (ifs >> no && ifs.ignore(1024, '\n')) {
double a, b, c;
while (ifs >> a >> b >> c)
data.push_back({a, b, c});
}
if (!(ifs.eof() && (no == data.size())))
throw std::runtime_error("Parse failure");
return data;
}
int main() {
for (auto& row : parseFile("input.txt"))
std::copy(row.begin(), row.end(), std::ostream_iterator<double>(std::cout << "\n", " "));
}
All of them are successfully disassembled and printed:
5 3 2
6 3 6
ยน Boost Spirit: "Semantic actions of evil",
Bonus Take: Auto Custom Attribute Types
Instead of opaque vectors, why don't you use a structure like
struct Point {
double x,y,z;
};
And analyze. As a bonus, you get an exit and a parser basically for free:
BOOST_FUSION_ADAPT_STRUCT(Point, x, y, z)
// parser:
auto_ >> eol >> *(auto_ >> (+eol|eoi)) >> eoi,
See Live On Coliru
source to share