Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spirit Qi attribute propagation issue with single-member struct

I have an compilation issue with Spirit Qi where it complains that value_type is not a member of identifier. For some reason, Qi's attribute system considers identifier to be a container type, and tries to enumerate it's value type.

This is a similar issue as in this question, however, I believe the cause is the single member struct and may be related to this bug.

#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

using namespace boost::spirit::qi; 

struct identifier
{
    std::wstring name;
};

struct problem
{
    identifier _1;
    identifier _2;
    identifier _3;
};

BOOST_FUSION_ADAPT_STRUCT(
    identifier,
    (std::wstring, name)
)

BOOST_FUSION_ADAPT_STRUCT(
    problem,
    (identifier, _1)
    (identifier, _2)
    (identifier, _3)
)



int main(int argc, char* argv[])
{
    rule<std::wstring::const_iterator, identifier()> gr_identifier = eps >> raw[lexeme[(alpha | '_') >> *(alnum | '_')]];

    // Ok, compiles
    /*rule<std::wstring::const_iterator, problem()> gr_problem =       gr_identifier
                                                                  >> gr_identifier
                                                                  >> '('
                                                                  >>   gr_identifier
                                                                  >>   ')';*/
    // Fails
    rule<std::wstring::const_iterator, problem()> gr_problem =       gr_identifier
                                                                  >> gr_identifier
                                                                  >> '('
                                                                  >   gr_identifier
                                                                  >   ')';

    std::wstring input = L"foo goo(hoo)";
    /*bool dummy = phrase_parse(
            input.begin(), input.end(), 
            gr_problem,
            space);*/

    return EXIT_SUCCESS;
}

Intriguingly, this only occurs when using expectation parser (see definition 2 in the example). Definition 1, using only the sequence parser, compiles (and executes) properly.

Does anybody know the proper solution to this?

Please also check out the live example

like image 873
namezero Avatar asked Nov 06 '13 21:11

namezero


1 Answers

This is a quite infamous edge-case in Spirit. The problem is, special-case handling of single-element Fusion Sequences in Spirit breaks some abstractions.

The usual workaround is to adapt the exposed-attribute side to be less-trivial:

rule<It, single_member_struct()> r = eps >> XXX; 
// the `eps` is there to break the spell

However, here that won't work, because your (a > XXX > b) subexpression results in another vector1<decltype(member_type)> and this time, no amount of smart parenthesizing or eps-ing will save you.[1]

To cut the long story short, I have three workarounds:


1. #define KEEP_STRING_WORKAROUND

See it Live On Coliru

In which you'll simply allow gr_identifier to return a std::wstring[2]:

rule<It, std::string()> gr_identifier = 
    (alpha | '_') >> *(alnum | '_');

This effectively just postpones the magic attribute transformation that uses the Fusion adaptation if identifier, and thereby breaks the spell:

rule<It, problem(), qi::space_type> gr_problem = 
       gr_identifier
    >> gr_identifier
    >> ('(' > gr_identifier > ')')
    ;

Just works. I think this is probably the least intrusive workaround


2. #define DUMMY_WORKAROUND

See it Live On Coliru

Whereby you unjinx the magix by ... making the identifier struct not fusion adapted to a single-element fusion sequence. Yes. This involves the EvilHack™ of adding a dummy field. To minimize confusion, lets' make it qi::unused_type though:

struct identifier
{
    std::string    name;
    qi::unused_type dummy;
};

BOOST_FUSION_ADAPT_STRUCT(
    identifier,
    (std::string,    name)
    (qi::unused_type, dummy)
)

And now:

rule<It, identifier()> gr_identifier = 
    (alpha | '_') >> *(alnum | '_') >> attr(42);   // that's hacky

Works


3. #define NO_ADAPT_WORKAROUND

See it Live On Coliru

The final workaround might be the most obvious: don't adapt the struct as a fusion sequence in the first place, and profit:

struct identifier
{
    std::string name;

    identifier() = default;

    explicit identifier(std::string name) 
        : name(std::move(name))
    {}
};

Note that to allow attribute propagation, now you will need suitable conversion constructors to be present. Also, the default constructor is required for exposed attributes in Spirit.

Now,

rule<It, identifier()> gr_identifier = 
    as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion

works. This might be more intuitive if you didn't need Fusion of the type for other purposes.

Note: this variant could well be the most efficient in compile-time

Summary

I think for your code, there are 2 perfectly viable workarounds (#1 and #3), and one less-than-stellar (the one with the dummy field), but I included it for documentary purposes.

Full Code

For future reference

#define BOOST_SPIRIT_DEBUG
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace qi = boost::spirit::qi;

//////////////////////////////////////////
// Select workaround to demonstrate
#define KEEP_STRING_WORKAROUND
// #define DUMMY_WORKAROUND™
// #define NO_ADAPT_WORKAROUND
//////////////////////////////////////////

#if defined(KEEP_STRING_WORKAROUND)
    struct identifier
    {
        std::string    name;
    };

    BOOST_FUSION_ADAPT_STRUCT(
        identifier,
        (std::string,    name)
    )
#elif defined(DUMMY_WORKAROUND)
    struct identifier
    {
        std::string    name;
        qi::unused_type dummy;
    };

    BOOST_FUSION_ADAPT_STRUCT(
        identifier,
        (std::string,    name)
        (qi::unused_type, dummy)
    )
#elif defined(NO_ADAPT_WORKAROUND)
    struct identifier
    {
        std::string name;

        identifier() = default;

        explicit identifier(std::string name) 
            : name(std::move(name))
        {}
    };
#endif

struct problem
{
    identifier _1;
    identifier _2;
    identifier _3;
};

BOOST_FUSION_ADAPT_STRUCT(
    problem,
    (identifier, _1)
    (identifier, _2)
    (identifier, _3)
)

//////////////////////////////////////////
// For BOOST_SPIRIT_DEBUG only:
static inline std::ostream& operator<<(std::ostream& os, identifier const& id) {
    return os << id.name;
}
//////////////////////////////////////////

int main()
{
    using namespace qi;
    typedef std::string::const_iterator It;
#if defined(KEEP_STRING_WORKAROUND)
    rule<It, std::string()> gr_identifier = 
        (alpha | '_') >> *(alnum | '_');
#elif defined(DUMMY_WORKAROUND)
    rule<It, identifier()> gr_identifier = 
        (alpha | '_') >> *(alnum | '_') >> attr(42);   // that's hacky
#elif defined(NO_ADAPT_WORKAROUND)
    rule<It, identifier()> gr_identifier = 
        as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion
#endif

    rule<It, problem(), qi::space_type> gr_problem = 
           gr_identifier
        >> gr_identifier
        >> ('(' > gr_identifier > ')')
        ;

    std::string input = "foo goo(hoo)";

    BOOST_SPIRIT_DEBUG_NODES((gr_problem)(gr_identifier));

    It f(begin(input)), l(end(input));
    bool dummy = phrase_parse(f, l, gr_problem, qi::space);

    return dummy? 0 : 255;
}

[1] Believe me, I tried, even while inserting qi::unused_type "fake" attributes, and/or using attr_cast<> or helper rules to coerce the type of the subexpression.

[2] For demo purposes, I used std::string because I believe it mixes better with BOOST_SPIRIT_DEBUG

like image 164
sehe Avatar answered Nov 15 '22 22:11

sehe