Search code examples
c++parsingboost-spirit

boost::spirit lazy parser with arguments?


I cannot find anything on this except ominous hints that it might be entirely impossible, but I don't want to simply believe it since lazy parsers would seem so useless in that case. What I want to do is choose a parser at parse-time depending on the result of some previous non-terminal. It essentially boils down to:

static rule<Constant *(Scope &)> &get_constant_parser(Typename type);

rule<Constant *(Scope &, Typename)> constant {
    lazy(phoenix::bind(&get_constant_parser, _r2))(_r1)
};

So get_constant_parser returns a parser fitting the given type name, however that parser requires an argument of type Scope &. So intuitively, I'd write that down as above, adding the argument to the lazy parser. However that gives me an invalid expression:

/usr/include/boost/spirit/home/qi/nonterminal/rule.hpp:177:13: error: static assertion failed: error_invalid_expression
             BOOST_SPIRIT_ASSERT_MATCH(qi::domain, Expr);
             ^~~~~~~~~~~~~~~~~~~~~~~~~

So how do I give arguments to a lazy parser? If it is indeed impossible, then does anyone have an idea why?

Sorry this is not a proper MWE, right now I'm hoping that someone has done it before and just knows the answer. Please let me know if you want to actively investigate and need an MWE ;-)


Solution

  • Trying to do this kind of sorcery without understanding how actually Phoenix and Spirit communicate is extremely hard. Let's try digging into it:

    1. Rule parametrization happens via operator() of qi::rule that creates an instance of qi::parameterized_nonterminal parser.
    2. Lazy parser evaluation is performed this way: qi::lazy wraps phoenix::actor into proto::terminal, which is later transformed (by meta compiler) into qi::lazy_parser/qi::lazy_directive.

    So, in your example Phoenix actor is converted to a Proto terminal and then call operator creates a Proto expression that Spirit meta compiler does not understand.

    My guess was that it should be lazy(phoenix::bind(&get_constant_parser, _r2)(_r1)) because you need to call that operator() on the actual rule, but Phoenix does not let you invoke operator() like this.

    What should work is: lazy(phoenix::bind(phoenix::bind(&get_constant_parser, _r2), _r1)).


    Long time ago I tried something like you are doing and also failed. I also googled those topics that say it is impossible and stopped at that point. But your question raised my interest and after a short trial and error (i.e. scratching my head and digging into Spirit sources) I came to this proof of concept:

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/support_argument.hpp>
    #include <iostream>
    
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    int main()
    {
        using passed_rule_t = qi::rule<char const*, int(int)>;
        qi::rule<char const*, int(int, passed_rule_t const&)> lazyinvoke
            = qi::lazy(phx::bind(qi::labels::_r2,   // binding is a way to call `operator()` lazily
                                 qi::labels::_r1)); // non-lazy equivalent of this is `_r2(_r1)`
        int v;
        char const* s = nullptr;
        passed_rule_t inout = qi::attr(qi::labels::_r1);
        if (qi::parse(s, s, lazyinvoke(phx::val(123), phx::cref(inout)), v))
            std::cout << "OK: " << v << "\n";
        else
            std::cout << "Failed\n";
    }
    

    https://wandbox.org/permlink/m40DpeMikKRYyvH0