Boost.Spirit.Qi: Take a rule's attribute and set it as a field of an enclosing rule's struct attribute?
Asked Answered
C

1

8

Like, many of these other questions, I'm trying to parse a simple grammar into a tree of structs using Boost.Spirit.Qi.

I'll try to distill what I'm trying to do to the simplest possible case. I have:

struct Integer {
  int value;
};
BOOST_FUSION_ADAPT_STRUCT(Integer, (int, value))

Later, inside of a grammar struct, I have the following member variable:

qi::rule<Iterator, Integer> integer;

which I am defining with

integer = qi::int_;

When I try to actually parse an integer, however, using

qi::phrase_parse(iter, end, g, space, myInteger);

myInteger.value is always uninitialized after a successful parse. Similarly, I have tried the following definitions (obviously the ones that don't compile are wrong):

integer = qi::int_[qi::_val = qi::_1]; //compiles, uninitialized value
integer = qi::int_[qi::_r1 = qi::_1]; //doesn't compile
integer = qi::int_[phoenix::bind(&Integer::value, qi::_val) = qi::_1]; //doesn't
integer = qi::int_[phoenix::at_c<0>(qi::_val) = qi::_1]; //doesn't

Clearly I am misunderstanding something about Spirit, Phoenix, or something else. My understanding is that qi::_1 is the first attribute of qi::int_, here, and should represent the parsed integer, when the part in the square brackets gets executed as a function object. I am then assuming that the function object will take the enclosing integer attribute qi::_val and try and assign the parsed integer to it. My guess was that because of my BOOST_FUSION_ADAPT_STRUCT call, the two would be compatible, and that certainly seems to be the case from a static analysis perspective, but the data is not being preserved.

Is there a reference(&) designation I am missing somewhere or something?

Cartesian answered 6/1, 2011 at 21:23 Comment(2)
I just found another combination that compiles, though it doesn't result in initialized data: I added a constructor to Integer that takes a value for a value, then defined my integer parser as integer = qi::long_long[qi::_val = phx::construct<Integer>(qi::_1)];Cartesian
more debugging notes. in my full code, I actually have qi::rule<Iterator, Integer, ascii::space_type> integer;, which looks like it breaks if I replace Integer with Integer(), and all the examples have the trailing (), which I neglected. So perhaps the template arguments to the rule were getting screwed up. Digging.Cartesian
T
17

If Integer is supposed to be the attribute exposed by the rule, you need to declare it as:

qi::rule<Iterator, Integer()> integer; 

(note the parenthesis). Spirit requires to use the function declaration syntax to describe the rule's 'interface'. It is used not only in Spirit but by several other libraries as well (see boost::function for instance).

The main reason for this is that it's a nice concise way of specifying a function interface. If you think about what a rule is, you quickly realize that it is like a function: it may return a value (the parsed result, i.e. synthesized attribute). Additionally it may take one or more arguments (the inherited attributes).

A second, but minor reason is that Spirit needs to be able to distinguish the different template parameters of a rule. The template parameters can be specified in any order (except for the iterator), so it needs some means of figuring out what's what. The function declaration syntax is sufficiently different from the skipper or the encoding (the other two possible template parameters) to allow it to be recognized at compile time.

Let's have a look at your different attempts:

This can be made to work if you change the rule definition as outlined above.

integer = qi::int_[qi::_val = qi::_1]; 

The _val refers to your Integer, while the _1 refers to an int. Therefore, you need to define an assignment operator from int to make this work:

struct Integer {
    int value;
    Integer& operator=(int) {...}
};                    

You don't need to adapt your type as a Fusion sequence in this case.

But you can write it even easier:

integer = qi::int_ >> qi::eps;

which is 100% equivalent (the eps is a trick used to convert the right hand side into a parser sequence, which allows to utilize the built-in attribute propagation mapping the elements of your adapted Fusion sequence to the attributes of the elements of the sequence).

This:

integer = qi::int_[qi::_r1 = qi::_1]; 

will not work as _r1 refers to the first inherited attribute of a rule. However, your rule has no inherted attributes.

This will work:

integer = qi::int_[phoenix::bind(&Integer::value, qi::_val) = qi::_1];

and it does not require to adapt your type as a Fusion sequence.

This will work too:

integer = qi::int_[phoenix::at_c<0>(qi::_val) = qi::_1]; 
Typography answered 6/1, 2011 at 22:15 Comment(2)
oh beautiful, thank you. so it looks like i just had a template typo all along.Cartesian
Very nice answer. Personally I think upvotes on the boost tag should be worth +20.Piper

© 2022 - 2024 — McMap. All rights reserved.