Is it possible to attach an action to a boost::spirit::rule parser which assigns the parsed result to a member of a (yet) unknown instance?

StackOverflow https://stackoverflow.com/questions/16675368

Question

I'm trying to reference a member of a (yet) unknown instance from within a boost::spirit rule definitions' action, so in pseudocode,

instead of double_[ref(rN) = _1] I'm looking for something like X** ppx; double_[ref(&X::rN, ppx) = _1]

A workaround for it could be a simple "semantic action" with a parameter which knows the instance and would be able to write to it, like

qi::rule<Iterator, Skipper> start;
my_grammar(DataContext*& dataContext) : my_grammar::base_type(start) , execContext(execContext) {
    start = qi::double_[ boost::bind(&my_grammar::newValueForXY, dataContext, ::_1) ];

However, I'm wondering if there is a possibility to "bind" directly to the member variable like it's possible to bind to a "local" variable by using "phoenix::ref(...) = value".

I tried the following syntax:

start = qi::int_[ boost::bind<int&>(&DataContext::newValueForXY, boost::ref(dataContext))() = ::_1] ];

but failed with VS2010SP1 and the error message

error C2440: '=': 'boost::arg' cannot be converted into ...

Was it helpful?

Solution

There are several ways to skin this cat:

  1. you probably thought of deferring execution of the bind expression: phx::bind can do that
  2. alternatively, you could just use attribute propagation for that (and do without semantic actions altogether)
  3. lastly, you could use inherited attributes (e.g. when the DataContext has no default constructor or copying is expensive)

1. Deferring the Bind with Phoenix

Use phoenix bind for the purpose: this will result in a Phoenix actor, that will be "deferred executed" at the time the semantic action is triggered.

Here's a reconstruction of the missing code sample you might have been after:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

struct DataContext
{
    double xy;
};

template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, Skipper>
{
    my_grammar(DataContext& dataContext) : my_grammar::base_type(start)
    {
        start = qi::double_
            [ phx::bind(&my_grammar::newValueForXY, 
                    phx::ref(dataContext), 
                    qi::_1) ];
    }
  private:
    static void newValueForXY(DataContext& dc, double value)
    {
         dc.xy = value;
    }

    qi::rule<Iterator, Skipper> start;
};

int main()
{
    const std::string s = "3.14";

    DataContext ctx;

    my_grammar<decltype(begin(s)), qi::space_type> p(ctx);
    auto f(begin(s)), l(end(s));
    if (qi::phrase_parse(f, l, p, qi::space))
        std::cout << "Success: " << ctx.xy << "\n";
}

Note:

  • phx::ref() to wrap the reference to the datacontext
  • qi::_1 instead of boost::_1 as a placeholder
  • given this implementation of newValueForXY you could just as easily have written

        start = qi::double_
            [ phx::bind(&DataContext::xy, phx::ref(dataContext)) = qi::_1 ];
    

2. Use an attribute grammar, instead of semantic actions

However, I'd probably write the same example using attributes instead of semantic actions (because that's basically what they are for):

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

struct DataContext {
    double xy;
};

BOOST_FUSION_ADAPT_STRUCT(DataContext, (double, xy))

template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, DataContext(), Skipper>
{
    my_grammar() : my_grammar::base_type(start) {
        start = qi::double_;
    }
  private:
    qi::rule<Iterator, DataContext(), Skipper> start;
};

int main()
{
    const std::string s = "3.14";
    static const my_grammar<decltype(begin(s)), qi::space_type> p;

    DataContext ctx;
    if (qi::phrase_parse(begin(s), end(s), p, qi::space, ctx))
        std::cout << "Success: " << ctx.xy << "\n";
}

3. Use inherited attributes to pass in the context reference

If you absolutely insist, you can even use inherited attributes for the purpose:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

struct DataContext {
    double xy;
};

template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, void(DataContext&), Skipper> {
    my_grammar() : my_grammar::base_type(start) 
    {
        start = qi::double_ [ phx::bind(&DataContext::xy, qi::_r1) = qi::_1 ];
    }
    qi::rule<Iterator, void(DataContext&), Skipper> start;
};

int main() {
    const std::string s = "3.14";
    const static my_grammar<std::string::const_iterator, qi::space_type> p;
    DataContext ctx;
    if(qi::phrase_parse(begin(s), end(s), p(phx::ref(ctx)), qi::space)) {
        std::cout << "Success: " << ctx.xy << "\n";
    }
}

This is somewhat more expressive at the call site:

qi::phrase_parse(begin(s), end(s), p(phx::ref(ctx)), qi::space));

and doesn't require the context to be default constructible.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top