Using semantic actions in boost spirit to set fields

Viewed 61

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)
)
1 Answers

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.

Related