2

Assuming you have a parser using boost spirit that sets this field with the exception of the id field. Is it possible to use semantic actions to generate and set the id field? Or is there a better way to achieve this.

struct employee
{

    std::string id;
    int age;
    std::string surname;
    std::string forename;
    double salary;
};
BOOST_FUSION_ADAPT_STRUCT(
    client::employee,
    (int, age)
    (std::string, id)
    (std::string, surname)
    (std::string, forename)
    (double, salary)
)
user2987773
  • 407
  • 5
  • 18

1 Answers1

1

Yes it's possible.

One pitfall is that presence of semantic actions usually supresses automatic attribute propagation. Since you'd like to have both, you will want to assign the parser expression using %= instead of = (see docs).

Alternatively you can generate a value on the fly and use the adaptation that you showed.

Proof Of Concept: SA + Adaptation

Here I'd simply exclude id from the adaptation. Note also that you don't need to repeat types since c++11:

BOOST_FUSION_ADAPT_STRUCT(
    client::employee, age, /*id, */ surname, forename, salary)

I'd prefer to write the SA with some phoenix function helpers:

    auto _id = px::function { std::mem_fn(&client::employee::id) };
    auto _gen = px::function { client::employee::generate_id };

    start %= skip(space) [
        age >> name >> name >> salary >> eps 
            [ _id(_val) = _gen() ]
    ];

Live On Coliru

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
namespace fus = boost::fusion;

namespace client {
    struct employee {
        std::string id;
        int         age;
        std::string surname;
        std::string forename;
        double      salary;

        static std::string generate_id() {
            static int _next{0};
            return "autoid#" + std::to_string(_next++);
        }
    };
    using fus::operator<<;
}

BOOST_FUSION_ADAPT_STRUCT(
    client::employee, age, /*id, */ surname, forename, salary)

template <typename It>
struct Parser : qi::grammar<It, client::employee()> {
    Parser() : Parser::base_type(start) {
        using namespace qi;
        name   = +graph;
        age    = uint_ >> eps(_val < 110 && _val > 16);
        salary = double_;

        auto _id = px::function { std::mem_fn(&client::employee::id) };
        auto _gen = px::function { client::employee::generate_id };

        start %= skip(space) [
            age >> name >> name >> salary >> eps 
                [ _id(_val) = _gen() ]
        ];

        BOOST_SPIRIT_DEBUG_NODES((start)(name)(age)(salary))
    }
  private:
    qi::rule<It, client::employee()> start;
    qi::rule<It, unsigned()>         age;
    qi::rule<It, std::string()>      name;
    qi::rule<It, double()>           salary;
};

static auto qview(auto f, auto l) {
    return std::quoted(
        std::string_view(std::addressof(*f), std::distance(f, l)));
}

int main() {
    Parser<std::string::const_iterator> p;
    std::cout << fus::tuple_delimiter(',');

    for (std::string const& input: {
            //age surname forename salary
            "55 Astley Rick 7232.88",
            "23 Black Rebecca 0.00",
            "77 Waters Roger 24815.07",
        })
    {
        auto f = begin(input), l = end(input);

        client::employee emp;
        if (parse(f, l, p, emp)) {
            std::cout << "Parsed: " << emp.id << " " << emp << "\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f != l) {
            std::cout << "Remaining unput: " << qview(f,l) << "\n";
        }
    }
}

Prints

Parsed: autoid#0 (55,Astley,Rick,7232.88)
Parsed: autoid#1 (23,Black,Rebecca,0)
Parsed: autoid#2 (77,Waters,Roger,24815.1)

Alternative: Inline Generation

You'd keep th full adaptation:

BOOST_FUSION_ADAPT_STRUCT(
    client::employee, age, id,  surname, forename, salary)

And respell the rule using qi::attr() on the right spot:

    auto _gen = px::function { client::employee::generate_id };

    start %= skip(space) [
        age >> attr(_gen()) >> name >> name >> salary
    ];

Live On Coliru (omitting rest of unchanged listing)

Printing (again):

Parsed: autoid#0 (55,autoid#0,Astley,Rick,7232.88)
Parsed: autoid#1 (23,autoid#1,Black,Rebecca,0)
Parsed: autoid#2 (77,autoid#2,Waters,Roger,24815.1)

Conclusion

In retrospect, I think the alternative has more appeal.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for this. For example in [link](https://stackoverflow.com/a/62970956/2987773). Assuming I wanted to attach this semantic action to _term rule using the alternative approach you gave, will that work? – user2987773 Feb 26 '21 at 23:45
  • Can you be more specific? What part of `term_` attribute do you want to affect from the SA? Are you trying to achieve something like source-location tagging of your AST nodes? Because there are better ways to do this in Spirit QI as will as X3. – sehe Feb 26 '21 at 23:51
  • So essentially for a rule like this `_term = _literal | _rule_name;`, attach different semantic actions to set an id field. – user2987773 Feb 27 '21 at 00:13
  • You can, but I suspect you would be far better of initializing from (a) the AST constructor or (b) maybe an `on_success` handler (see e.g. https://stackoverflow.com/search?tab=newest&q=user%3a85371%20on_success%20qi) – sehe Feb 27 '21 at 00:20
  • I was looking at the on_success approach in [line](https://stackoverflow.com/a/51254774/2987773) but I'm struggling to put it all together. Essentially what I want to do is commented in the [code](http://coliru.stacked-crooked.com/a/de566c8817659317)(adapted from [link](https://stackoverflow.com/a/62970956/2987773)); my changes to the rule however is not making it recognize rule_names. – user2987773 Feb 27 '21 at 15:16
  • Sounds a bit like it it time for new question. I'll find it later today when i have time – sehe Feb 27 '21 at 15:17