Search code examples
c++parsingboost-spiritboost-fusionboost-spirit-x3

spirit x3 how to add a vector to an AST


I'm trying to parse a file and have the data copied into a vector within a class object. I've taken the employee example and modified it to what I'm trying to do. The file being parsed looks like this (but more lines) ...

1 0.2 0.3 0.4

I've added a vector to struct employee and am getting assertion failures on the phrase_parse line.

static assertion failed: Attribute does not have the expected size.
static_assert(
^

I'm kind of thinking the expected size has something to do with the vector. Thoughts on where I'm going wrong?

namespace client {
    namespace ast {

    struct employee
    {
        int id;
        std::vector<double> coords;
    };

    using boost::fusion::operator<<;
}}

BOOST_FUSION_ADAPT_STRUCT(
    client::ast::employee,
    (int, id)
    (std::vector<double>, coords)
)

namespace client
{
    namespace parser
    {
        namespace x3 = boost::spirit::x3;
        namespace ascii = boost::spirit::x3::ascii;

        using x3::int_;
        using x3::double_;

        x3::rule<class employee, ast::employee> const employee = "employee";
        auto const employee_def =
             int_ >> double_ >> double_ >> double_;
        BOOST_SPIRIT_DEFINE(employee)
    }
}

int main()
{
    using boost::spirit::x3::ascii::space;
    using client::parser::employee;

    string fil("test-file.in");

    mapped_file_source map(fil);
    istringstream iss(map.data());
    map.close();

    client::ast::employee emp;

    boost::spirit::istream_iterator iter(iss >> noskipws), eof;

    phrase_parse(iter, eof, employee, space, emp);
    // failure on above line
}

Solution

  • According to the documentation, double_ >> double_ >> double_ synthesizes a Fusion sequence of double, double, double (so fusion::tuple<double, double, double> or fusion::list<double, double, double> etc).

    You want a vector, so you need a repeating parser (operator)

    • the repeat directive will do repeat(3) [double_]
    • the Kleene star (operator *) or plus (operator +) are interesting (but unbounded)
    • the list operator (operator %) is also unbounded but accepts delimiters (e.g. double_ % ','

    In this case I'd go the other way: use a proper AST for the grammar:

    Live On Coliru

    struct coord {
        double x,y,z;
    };
    
    struct employee
    {
        int id;
        coord coords;
    };
    

    Adapting them is simpler than the old fashioned method you used:

    BOOST_FUSION_ADAPT_STRUCT(client::ast::coord, x, y, z)
    BOOST_FUSION_ADAPT_STRUCT(client::ast::employee, id, coords)
    

    The parser is a clean

    auto const coord_def = double_ >> double_ >> double_;
    auto const employee_def = int_ >> coord;
    

    Full demo:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <boost/spirit/include/support_istream_iterator.hpp>
    #include <boost/fusion/adapted/struct.hpp>
    #include <iostream>
    
    namespace client {
        namespace ast {
    
        struct coord {
            double x,y,z;
        };
    
        struct employee
        {
            int id;
            coord coords;
        };
    
        using boost::fusion::operator<<;
    }}
    
    BOOST_FUSION_ADAPT_STRUCT(client::ast::coord, x, y, z)
    BOOST_FUSION_ADAPT_STRUCT(client::ast::employee, id, coords)
    
    namespace client
    {
        namespace parser
        {
            namespace x3 = boost::spirit::x3;
            namespace ascii = boost::spirit::x3::ascii;
    
            using x3::int_;
            using x3::double_;
    
            x3::rule<class employee, ast::coord>    const coord    = "coord";
            x3::rule<class employee, ast::employee> const employee = "employee";
    
            auto const coord_def = double_ >> double_ >> double_;
            auto const employee_def = int_ >> coord;
    
            BOOST_SPIRIT_DEFINE(employee, coord);
        }
    }
    
    int main()
    {
        using boost::spirit::x3::ascii::space;
        using client::parser::employee;
    
        std::istringstream iss("1 0.2 0.3 0.4");
    
        client::ast::employee emp;
    
        boost::spirit::istream_iterator iter(iss >> std::noskipws), eof;
    
        bool ok = phrase_parse(iter, eof, employee, space, emp);
        if (ok)
            std::cout << "parsed: " 
                 << emp.id       << " "
                 << emp.coords.x << " "
                 << emp.coords.y << " "
                 << emp.coords.z << "\n";
    }