Search code examples
c++parsingboost-spirit-qiboost-phoenix

Using lambdas or external functions as spirit.qi semantic actions


Iʼm playing with creating Boost.Spirit.Qi-based parsing. Having an example like calc_utree, I'm trying to extend what to use as semantic action.

It's trivial to reuse the same approach with an alone assignment as semantic action, for example

        term =
            factor [_val = _1]

as literally in the example. But, when I try to pass both into a function (method) external to the rule definition, or even write it as lambda, for example

        term =
            factor [([&] {_val = _1; })]

it causes silent misassignment in that place: _val remains unchanged (without any error or warning from compiler). The same if I change it to something like

        term =
            factor [do_term_factor(_val, _1)]
<...>
template <typename D, typename S>
D& do_term_factor(D& d, S& s) {
    d = s;
}

Seems Iʼve fallen into principal misconception with Qi and Phoenix. The questions are (principally they are different forms of the same one):

  • What is specific with Phoenix variables that they donʼt work in C++ lambdas?
  • How to make it working with such external action call?

Alternatively, how _val can be achieved without Phoenix? Spirit documentation seems quite obscure in this.

Environment details: Boost 1.58.0, gcc 5.4.0 or clang 4.0 (all of Ubuntu 16.04).


Solution

  • The links provided by @llonesmiz are excellent.

    In short: you need to adapt functions to be lazy actors. _val = _1 is that too, but "magically" generated using expression templates.

    For "regular" calleables you have

    • boost::phoenix::function
    • boost::phoenix::bind
    • BOOST_FUNCTION_ADAPT_*

    Here's a small parade

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    
    //////////////////// bindables
    struct GenericDoubler {
        template <typename T>
        auto operator()(T const& v) const { return v * 2; }
    };
    static const px::function<GenericDoubler> s_genericDoubler;
    
    template <typename T>
    T freeGenericDouble(T const& v) { return v * 2; }
    
    BOOST_PHOENIX_ADAPT_FUNCTION(int, genericDouble_, freeGenericDouble, 1)
    
    /////////////////// raw actors
    int main() {
        using It = std::string::const_iterator;
        std::string const input = "42";
    
        using namespace qi::labels;
    
        for (auto rule : std::vector<qi::rule<It, int()> > { 
                    // binds
                    qi::int_ [ _val = 2*_1 ],
                    qi::int_ [ _val = px::bind([](int i) { return 2*i; }, _1) ],
                    qi::int_ [ _val = px::bind(GenericDoubler(), _1) ],
                    qi::int_ [ _val = px::bind(&freeGenericDouble<int>, _1) ],
                    qi::int_ [ _val = genericDouble_(_1) ],
                    qi::int_ [ _val = s_genericDoubler(_1) ],
                    // actors
                    qi::int_ [ ([](int const& /*attribute*/, auto& /*context*/, bool& pass) { 
                            // context is like boost::spirit::context<boost::fusion::cons<int&, boost::fusion::nil_>, boost::fusion::vector<> >
                            pass = false;
                        }) ],
                    qi::int_ [ ([](int& attribute, auto& context, bool& pass) { 
                            int& exposed = boost::fusion::at_c<0>(context.attributes);
                            exposed = 2*attribute;
                            pass = true;
                        }) ],
                }) 
        {
            It f = begin(input), l = end(input);
            int data = 99;
            if (parse(f, l, rule, data))
                std::cout << "Parsed: " << data << " ";
            else
                std::cout << "Parse failed at '" << std::string(f,l) << "' ";
    
            if (f != l)
                std::cout << "Remaining: '" << std::string(f,l) << "'";
            std::cout << '\n';
        }
    }
    

    Prints

    Parsed: 84 
    Parsed: 84 
    Parsed: 84 
    Parsed: 84 
    Parsed: 84 
    Parsed: 84 
    Parse failed at '42' Remaining: '42'
    Parsed: 84