Parse into a vector<vector<double>> with boost::spirit
Asked Answered
A

1

7

My intent is to parse a comma separated list of values into a nested vector. This list is two-dimensional. The basic question is:

is it possible to parse into a vector of vector with boost::spirit?

Something like the table under "Traction":

' 
' RPM
0,5000,10000,15000,20000,25000
'
' Temp
'
-40.,0.,20.,40.
'
' Traction
200.,175.,170.,165.,160.,150.
200.,175.,170.,165.,160.,150.
165.,165.,160.,155.,145.,145.
160.,155.,150.,145.,145.,140.
'

In a further step, I would like to read in 4-dimensional data, but for now I'm struggling with the second dimension. The data structure is like this:

struct table {
    std::vector<double>  index;
    std::vector<double>  index2;
    std::vector<std::vector<double> > base;
};

The grammar is IMHO pretty straightforward as follows:

comment %= qi::lexeme[ '\'' >> *(qi::standard::char_ - qi::eol)] >> qi::eol;
commentblock = comment >> *(comment);
doublevector = qi::double_ % ',' >> qi::eol ;
vectorblock = *doublevector;
start = commentblock            >>
        doublevector            >>
        commentblock            >>
        doublevector            >>
        commentblock            >>
        vectorblock             >>
        commentblock            >>
        qi::eoi
        ;

So far, I have no problem parsing the two vectors index and index2. But the problem starts at the base. I think that the crucial section is where I define the vectorblock:

vectorblock = *doublevector;

I have tried several variants of that statement. Also the %= operator from this question did not change anything. Although attribute propagation might be the correct direction.

If I follow the boost documentation example "with style", the result is exactly the same:

vectorblock = doublevector % qi::eps;

The List Redux samples that use push_back():

vectorblock = doublevector[phoenix::push_back(qi::_val, qi::_1)] % qi::eps;

provoke a torrent of compile errors, starting with:

error C2039: 'push_back' : is not a member of 'boost::spirit::unused_type'

Update: The problem was at the declaration of the vectorblock. I forgot the () after the attribute type. So, the definition should look like this:

qi::rule<Iterator, std::vector<std::vector<double> >(), Skipper> vectorblock;

The (updated) working example is here:

#include <iostream>
#include <string>
#include <vector>

#define BOOST_SPIRIT_DEBUG

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/adapted.hpp>

struct table {
    std::vector<double>  index;
    std::vector<double>  index2;
    std::vector<std::vector<double> > base;
};

BOOST_FUSION_ADAPT_STRUCT(
        table,
        (std::vector<double> , index)
        (std::vector<double> , index2)
        (std::vector<std::vector<double> >, base)
)

const std::string contents =
        "'\n"
        "' RPM\n"
        "'\n"
        "0,5010,10000,15000,20000,25000\n"
        "'\n"
        "' Temp\n"
        "'\n"
        "-40.,0.,20.,40.\n"
        "'\n"
        "' Traction\n"
        "200.,175.,170.,165.,160.,150.\n"
        "200.,175.,170.,165.,160.,150.\n"
        "165.,165.,160.,155.,145.,145.\n"
        "160.,155.,150.,145.,145.,140.\n"
        "'\n"
;


int main()
{
    namespace qi = boost::spirit::qi;
    namespace phoenix = boost::phoenix;

    typedef std::string::const_iterator Iterator;
    typedef boost::spirit::ascii::blank_type Skipper;

    qi::rule<Iterator, std::string(), Skipper> comment;
    qi::rule<Iterator, Skipper> commentblock;
    qi::rule<Iterator, std::vector<double>(), Skipper> doublevector;
    qi::rule<Iterator, std::vector<std::vector<double> >, Skipper> vectorblock;
    qi::rule<Iterator, table(), Skipper> start;

    comment %= qi::lexeme[ '\'' >> *(qi::standard::char_ - qi::eol)] >> qi::eol;
    commentblock = comment >> *(comment);

    doublevector = qi::double_ % ',' >> qi::eol ;
    vectorblock = *doublevector;

    start = commentblock            >>
            doublevector            >>
            commentblock            >>
            doublevector            >>
            commentblock            >>
            vectorblock             >>
            commentblock            >>
            qi::eoi
            ;

    BOOST_SPIRIT_DEBUG_NODES((start)(doublevector)(vectorblock));

    table tref;

    bool rv =  qi::phrase_parse(
                     std::begin(contents), std::end(contents),
                     start,
                     boost::spirit::ascii::blank,
                     tref
                 );

    std::cout << "parse " << ((char *)rv?"success":"failure") << ".\n";

    for (auto i : tref.index)
        std::cout << i << ", ";
    std::cout << "\n";
    for (auto i : tref.index2)
        std::cout << i << ", ";
    std::cout << "\nBase:\n";
    for (auto & i : tref.base)
    {
        for(auto & j : i)
            std::cout << j << ", ";
        std::cout << "\n";
    }
    std::cout << std::endl;

}
Alarm answered 24/4, 2015 at 13:6 Comment(2)
You forgot the () in the attribute type of the rule declaration for vectorblock. After I put it there, it worked for me with *doublevector.Bega
you're correct. silly me!Alarm
A
3

The answer is yes. It is actually quite trivial to parse into vector<vector<double> >

The rule definition requires a function type, not the type directly. This is simply explained here. A more thorough explanation is probably found in the documentation of boost::phoenix

The output of the program above is now showing nicely the parsed values:

parse success.
0, 5011, 10000, 15000, 20000, 25000, 
-40, 0, 20, 40, 
Base:
200, 175, 170, 165, 160, 150, 
200, 175, 170, 165, 160, 150, 
165, 165, 160, 155, 145, 145, 
160, 155, 150, 145, 145, 140, 
Alarm answered 24/4, 2015 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.