-1

I played around with boost::spirit recently and wanted to use it to parse file input. What i got is this: defining some semantic actions:

data = ifstream("herpderp", ios::in);
std::string line;
auto pri = [&](auto &ctx){cout << "got this" << endl;};
auto bri = [&](auto &ctx){cout << "got that" << endl;};

and the actual reading happens like this:

while(getline(data, line, '\n'))                                        
{                                                                       
    bool r = phrase_parse(line.begin(), line.end(), (int_ >> char_ >> int_ >> double_)[pri] | (int_ >> char_ >> int_)[bri], space);
}

However the problem is - I have no idea how to access the contents of _attr(ctx) inside the lambdas pri and bri. I know they work as intended, depending on the contents of the file because of the cout prints (they alternate) - they are however compound type as one can tell from the parsing rules. If anyone can shed some light on this, I'd be grateful.

Edit: Got this to work the way I wanted it to. It required another import

#include <boost/mpl/int.hpp>

And each of the lambdas looks like this:


auto bri = [&](auto &ctx)
{
    int    firstIntFromMatch  = at<boost::mpl::int_<0>>(_attr(ctx));
    char   charFromMatch      = at<boost::mpl::int_<1>>(_attr(ctx));
    int    secondIntFromMatch = at<boost::mpl::int_<2>>(_attr(ctx));

    doSomething(firstIntFromMatch, charFromMatch, secondIntFromMatch);
};
auto pri = [&](auto &ctx)
{
    int    firstIntFromMatch  = at<boost::mpl::int_<0>>(_attr(ctx));
    char   charFromMatch      = at<boost::mpl::int_<1>>(_attr(ctx));
    int    secondIntFromMatch = at<boost::mpl::int_<2>>(_attr(ctx));
    double doubleFromMatch    = at<boost::mpl::int_<3>>(_attr(ctx));

    doSomething(firstIntFromMatch, charFromMatch, secondIntFromMatch);
    doSomethingElse(doubleFromMatch);
};
drinker
  • 41
  • 7
  • If you are going to use lambdas, you would pass in the target `[&my_var]`. But, don't use semantic action unless you absolutely must. Parse into your object directly. [an example](https://github.com/lakeweb/BXL_Reader) – lakeweb Aug 15 '19 at 16:00
  • Could you elaborate a bit on why not to use this approach? Is it slow? Anything wrong with it? – drinker Aug 16 '19 at 01:10
  • Hi @drinker The trouble with semantic actions is that as your parser evolves, it makes it more difficult to maintain. I did it the first time I used spirit to propagate data on 'the back end'. What a nightmare. As long as your data can be placed directly from the parser to your objects, the only way to go. sehe addresses this. – lakeweb Aug 19 '19 at 16:06

1 Answers1

3

I'm with @lakeweb, see also http://stackoverflow.com/questions/8259440/boost-spirit-semantic-actions-are-evil

However to answer your specific question: the attributes are fusion sequences. Including fusion/include/io.hpp enables you to just print them:

    auto pri = [&](auto &ctx){std::cout << "got this: " << _attr(ctx) << std::endl;};
    auto bri = [&](auto &ctx){std::cout << "got that: " << _attr(ctx) << std::endl;};

Prints

Live On Coliru

got this: (321 a 321 3.14)
Parsed
got that: (432 b 432)
Parsed

Doing Useful Stuff

Doing useful stuff is always more exciting. You could manually take apart these fusion sequences. Defining the simplest data struct I can think of to receive our data:

struct MyData {
    int a = 0;
    char b = 0;
    int c = 0;
    double d = 0;

    friend std::ostream& operator<<(std::ostream& os, MyData const& md) {
        return os << "MyData{" << md.a << "," << md.b << "," << md.c << "," << md.d << "}";
    }
};

Now, we can "enhance" (read: complicate) stuff to parse into it:

auto pri = [&](auto &ctx) {
    auto& attr = _attr(ctx);
    std::cout << "got this: " << attr << std::endl;
    using boost::fusion::at_c;
    _val(ctx) = { at_c<0>(attr), at_c<1>(attr), at_c<2>(attr), at_c<3>(attr) };
};
auto bri = [&](auto &ctx)
{ 
    auto& attr = _attr(ctx);
    std::cout << "got that: " << attr << std::endl;
    using boost::fusion::at_c;
    _val(ctx) = { at_c<0>(attr), at_c<1>(attr), at_c<2>(attr), std::numeric_limits<double>::infinity()};
};

auto const pri_rule = x3::rule<struct _pri, MyData> {"pri_rule"} = 
        (x3::int_ >> x3::char_ >> x3::int_ >> x3::double_)[pri];
auto const bri_rule = x3::rule<struct _bri, MyData> {"bri_rule"} = 
        (x3::int_ >> x3::char_ >> x3::int_)[bri];

And yes, this "works":

Live On Coliru

for(std::string const line : {
        "321 a 321 3.14",
        "432 b 432"
    })
{
    MyData data;

    bool r = x3::phrase_parse(
            line.begin(), line.end(),
            pri_rule | bri_rule,
            x3::space,
            data);

    if (r)
        std::cout << "Parsed " << data << "\n";
    else
        std::cout << "Failed\n";
}

Prints

got this: (321 a 321 3.14)
Parsed MyData{321,a,321,3.14}
got that: (432 b 432)
Parsed MyData{432,b,432,inf}

However this seems horribly complicated.

SIMPLIFY!!!

It seems you merely have an optional trailing double_. With a little bit of help:

BOOST_FUSION_ADAPT_STRUCT(MyData, a,b,c,d);

You can have the same effect without any of the mess:

bool r = x3::phrase_parse(
        line.begin(), line.end(),
        x3::int_ >> x3::char_ >> x3::int_ >> (x3::double_ | x3::attr(9999)),
        x3::space, data);

Which would print Live On Coliru

Parsed MyData{321,a,321,3.14}
Parsed MyData{432,b,432,9999}

Optional: Optionality

If you don't have a valid default for the double you could make it an optional:

            x3::int_ >> x3::char_ >> x3::int_ >> -x3::double_,

And could still parse it:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/optional/optional_io.hpp>
#include <iostream>

namespace x3 = boost::spirit::x3;

struct MyData {
    int a = 0;
    char b = 0;
    int c = 0;
    boost::optional<double> d;

    friend std::ostream& operator<<(std::ostream& os, MyData const& md) {
        return os << "MyData{" << md.a << "," << md.b << "," << md.c << "," << md.d << "}";
    }
};

BOOST_FUSION_ADAPT_STRUCT(MyData, a,b,c,d)

int main() {
    for(std::string const line : { "321 a 321 3.14", "432 b 432" }) {
        MyData data;

        bool r = x3::phrase_parse(
                line.begin(), line.end(),
                x3::int_ >> x3::char_ >> x3::int_ >> -x3::double_,
                x3::space, data);

        if (r)
            std::cout << "Parsed " << data << "\n";
        else
            std::cout << "Failed\n";
    }
}

Prints:

Parsed MyData{321,a,321, 3.14}
Parsed MyData{432,b,432,--}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for the great answer. Just fyi I was not looking for a way to read the data in a uniform way but rather to take specific actions depending on the provided data - that's why the lambdas and not optionals. – drinker Aug 15 '19 at 21:22
  • Great, You might be interested in this one: https://stackoverflow.com/questions/57048008/spirit-x3-is-this-error-handling-approach-useful/57067207#57067207 Specifically, you can expand on the `on_success` idea and use that instead of synthesizing into attributes. (As that example shows, it jells well with Fusion Adapation, just for the duration of the rule/on_success handler) – sehe Aug 15 '19 at 23:13