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?

扶醉桌前 提交于 2019-12-06 01:25:45

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!