Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boost.Spirit.x3 avoid collapsing two consecutive attributes of the same type into a vector

I am trying to learn Boost.Spirit, but I have found a difficulty.

I am trying to parse a string into the following structure:

struct employee {
    std::string name;
    std::string location;
};

And it seems that when two attributes with the same type are back to back, they collapse (logically) into a std::vector of that type. Because of that rule, the following parser

+x3::ascii::alnum >>
    +x3::space >>
    +x3::ascii::alnum

would have the attribute of std::vector<std::string>.

But I am trying to parse this into that struct, which means that the ideal attribute for me would be a boost::fusion::tuple<std::string, std::string>, so I can adapt my struct to it.

The complete version of the not working code (referenced above):

// Example program
#include <iostream>
#include <string>

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>


struct employee {
    std::string name;
    std::string location;
};

BOOST_FUSION_ADAPT_STRUCT(employee, 
    (std::string, name),
    (std::string, location)
)

namespace x3 = boost::spirit::x3;

x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser";
auto parse_emp_def = 
    +x3::ascii::alnum >>
    +x3::space >>
    +x3::ascii::alnum
    ;
BOOST_SPIRIT_DEFINE(parse_emp);

int main()
{
    std::string input = "Joe Fairbanks";
    
    employee ret;
    
    x3::parse(input.begin(), input.end(), parse_emp, ret);
    
    std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << std::endl;
}

See it live

This code triggers a static_assert telling me that my attribute isn't correct:

error: static_assert failed "Attribute does not have the expected size."

With the command of

clang++ -std=c++14 test.cpp

(it also fails under GCC).

What I have tried

I have found a workaround to this problem, but it is messy, and I can't believe that this is the cleanest way:

// Example program
#include <iostream>
#include <string>

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>


struct employee {
    std::string name;
    std::string location;
};

namespace x3 = boost::spirit::x3;

x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser";
auto parse_emp_def = 
    x3::eps [
    ([](auto& ctx) {
        x3::_val(ctx) = employee{};
    })
    ]>>
    (+x3::ascii::alnum)[
    ([](auto& ctx) {
        x3::_val(ctx).name = x3::_attr(ctx);
    })
    ]>>
    +x3::space >>
    (+x3::ascii::alnum)[
    ([](auto& ctx) {
        x3::_val(ctx).location = x3::_attr(ctx);
    })
    ]
    ;
BOOST_SPIRIT_DEFINE(parse_emp);

int main()
{
    std::string input = "Joe Fairbanks";
    
    employee ret;
    
    x3::parse(input.begin(), input.end(), parse_emp, ret);
    
    std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << std::endl;
}

See it live

I really don't like that solution: it kinda ruins the amazing expressiveness of spirit and makes it super ugly, also if I want to add new fields into the employee struct, then I have to add an extra lambda, instead of just updating my BOOST_FUSION_ADAPT_STRUCT, which is much easier.

So the question is: Is there some way to (hopefully) cleanly split two consecutive attributes of the same type from the std::vector and into a boost::fusion::vector?

Thank you in advance for getting this far ;).

like image 741
Russell Greene Avatar asked May 10 '16 03:05

Russell Greene


1 Answers

The problem is that unlike character literals, x3::space has an attribute. So you don't have an attribute of two separate character sequences separated by whitespace, but rather an attribute of one big character sequence which includes the whitespace.

The omit directive is what you're after, and with that single addition your 'not working code' works. :-]

// Example program
#include <string>
#include <iostream>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;

struct employee {
    std::string name;
    std::string location;
};
BOOST_FUSION_ADAPT_STRUCT(employee, name, location)

x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser";
auto parse_emp_def
    =      +x3::ascii::alnum
        >>  x3::omit[+x3::space]
        >> +x3::ascii::alnum
    ;
BOOST_SPIRIT_DEFINE(parse_emp)

int main()
{
    std::string const input = "Joe Fairbanks";

    employee ret;
    x3::parse(input.begin(), input.end(), parse_emp, ret);

    std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << '\n';
}

Online Demo

like image 117
ildjarn Avatar answered Sep 22 '22 06:09

ildjarn