Boost Spirit Qi Custom Syntesized Attribute (Set a specific member of a struct attribute via a semantic action)
Asked Answered
M

1

6

Suppose I have a structure that I want to parse into with Spirit Qi, that is defined as such:

struct data_
{
    bool export;
    std::wstring name;

    data_() : export(false) {}
};

Also, suppose the struct has been adapted to fusion like this:

BOOST_FUSION_ADAPT_STRUCT(
    data_,
    (bool, export)
    (std::wstring, name)
)

And the associated rule is:

qi::rule<Iterator, data_(), skipper<Iterator> > rule_data;

rule_data = -lexeme["SpecialText" >> !(alnum | '_')] [ boost::phoenix::at_c<0> = true ] // If this string is found, , set "export" to true
            > lexeme["Name" >> !(alnum | '_')] // this is supposed to go into the "name" member

This compiles, so far, so good. However, "name" stays empty now!

So essentially, I am asking: Given that "SpecialText" precedes "Name", how would I synthesize a boolean attribute for "export" properly, rather than a string?

EDIT After pulling my hair out on this, I randomly stumbled upon the "matches[]" parser, which seems to do what I want.

Nonetheless, the question still exists in the general form, for example, if I wanted to return a certain string or other data type instead of a bool. Essentially, how to set a specific member of a struct attribute via a semantic action.

Maebashi answered 3/9, 2013 at 23:2 Comment(6)
You need to use %= operator if you want to combine semantic actions and auto attribute synthesis rule_data %= ...Cloy
See this: boost.org/doc/libs/1_54_0/libs/spirit/doc/html/spirit/qi/…Cloy
Check this for how to set values in semantic actions: boost.org/doc/libs/1_53_0/libs/spirit/phoenix/doc/html/phoenix/…Cloy
Thanks! I was just about to ask where you found the %=, since it's not listed here: boost.org/doc/libs/1_46_1/libs/spirit/doc/html/spirit/qi/…Maebashi
@Maebashi it's a bit of a FAQ... sadly e.g. https://mcmap.net/q/1771313/-boost-spirit-parser-returns-empty-vectorLighthearted
Another alternative that doesn't use semantic actions can be found here.Usm
L
12

How to set a struct member.

Option 1 (phx::bind)

Given a struct S

struct S
{
    int         field1;
    std::string field2;
    int         target_field;
    bool        field3;
};

You can assign to a field (e.g. target_field) like so:

rule<It, S()> p = int_ [ phx::bind(&S::target_field, _val) = _1 ];

Now, you can make the bind more readable, by doing something like:

auto target_field_ = phx::bind(&S::target_field, _val);

p = int_ [ target_field_ = _1 ];

Proof of concept: live on Coliru

#include "boost/spirit/include/qi.hpp"
#include "boost/spirit/include/phoenix.hpp"

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;
typedef std::string::const_iterator It;

struct S
{
    int         field1;
    std::string field2;
    int         target_field;
    bool        field3;
};

int main()
{
    const std::string input("42");
    It f(begin(input)), l(end(input));

    S instance;

    using namespace qi;
    rule<It, S()> p = int_ [ phx::bind(&S::target_field, _val) = _1 ];

    // or, alternatively:
    auto target_field_ = phx::bind(&S::target_field, _val);
    p = int_ [ target_field_ = _1 ];

    if (parse(f, l, p, instance))
        std::cout << "Parsed: " << instance.target_field;
}

Option 2 (fusion sequences)

You can treat a struct as a fusion sequence by using adaptation:

#include "boost/fusion/adapted/struct.hpp"

BOOST_FUSION_ADAPT_STRUCT(S, (int, field1)(std::string, field2)(int, target_field)(bool, field3))

Now you can use phoenix lazy functions on these sequences in your semantic action:

rule<It, S()> p = int_ [ phx::at_c<2>(_val) = _1 ];

I don't prefer this style (because it 'degrades' an expressive struct to ... a tuple of sorts), but it might come in handy. Live on Coliru

Lighthearted answered 4/9, 2013 at 0:9 Comment(7)
Forgive me, but isn't Option 2 exactly what I had above?Maebashi
Ok, I see in option 1 we're binding the constructor of target_field to _val and assigning the attribute. So, [ phx::bind(&S::field2, _val) = "lalaland" ] should be a valid semantic action, then, too, correct? Would this interfere with automatic attribute propagation over the other fields, then?Maebashi
@Maebashi Erm, yes, it would appear so :/ Lol. That's a strong case for formatting your code for SO then (it was past the "right margin"). Anyways, I reckoned "auto rules" answered your real question, so I just quickly picked up the "How to set a struct member" loose end...Lighthearted
@Maebashi Yes, semantic actions always interfere with attribute propagation. But you can simultaneously use %= for auto-rule behaviour and _assign to qi::_1 in the semantic action instead...?Lighthearted
Ok, that is great to know. I will use the auto-rule from now on whenever a semantic action is present in the rule (and I wish to keep the normal attribute propagation). I get it now why only the field set with at_c<>() was filled, and nothing else!Maebashi
At least the mental trigger to think about it. Honestly, most often when I use a semantic action I don't want automatic attribute propagation (either because there isn't an attribute or because the construction is non trivial). I tend to avoid semantic action on the whole.Lighthearted
Yes, that's why I was excited to find the matches[] parser. But then I became curious about how to do it in general, short of a custom parser, which I saw is possible, too.Maebashi

© 2022 - 2024 — McMap. All rights reserved.