5

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 ;-)

Victor Mataré
  • 2,446
  • 2
  • 16
  • 20

2 Answers2

2

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

Nikita Kniazev
  • 3,728
  • 2
  • 16
  • 30
  • Interesting, I kinda thought something along those lines must be be possible, but I couldn't wrap my head around the required syntactic brainfuckery. Unfortunately it still breaks down for me because somewhere (I think in the inner `bind`) my `Scope &` argument gets converted to a `const Scope`, which fails because these things aren't copyable. – Victor Mataré May 23 '19 at 17:01
  • I'm going to accept this since it's a working proof of concept that answers my question pretty exactly. However it comes with some caveats, a big one being that if you actually do something like this in your code, every code reviewer will just be like WTF. It's also important to note that once you start thinking about such shenanigans, it might be time to rethink your design. For that, see @sehe 's answer. – Victor Mataré May 23 '19 at 18:45
  • Awesome! I was able to make it work by turning the `Scope &` into a `Scope *` for the inner bind, thus making it copyable. It's a hack, but it's a hack that that reduces complexity from O(c^5n) to O(c^n) in many places. – Victor Mataré May 24 '19 at 18:00
  • 1
    With a big probability it is a Phoenix bug (there are other `const`ness problems in it). I suggest to open a bug report If you have time to make an MCVE. And, I am glad that the old misconception had been debunked. – Nikita Kniazev May 24 '19 at 18:40
1

So I'm sorry to say I think it is indeed impossible [*]

However, all is not lost. If you can use qi::locals instead of passing "arguments" (inherited attributes) then you should be fine.

Depending on your actual goal though you could skirt the need for "lazy(arg)" by calling non_lazy(symbols*). The idea was prompted by my hunch that you're trying to do namespace/domain dependent lookups. See e.g.


[*]

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Well, `qi::locals` is definitely not enough since the information needs to be passed **down**. My keyword is essentially a type name, think of e.g. `bool` and `number`, and to parse arbitrary expressions of those types I have to use completely different nonterminals, of course. Haven't really looked into it, yet, maybe a symbol table can do that for me. One issue might be that the symbol (i.e. the type name) might not have been parsed verbatim, but read from a previously parsed object's property. E.g. imagine assigning a bool to a boolean variable reference. – Victor Mataré May 23 '19 at 17:20