How to use std::function in a qi symbol table

90 Views Asked by At

I have the following code. This code shall parse two int and return the result of a comparison as a bool. For compare, I use the qi::symbol table. But, unfortunately, it does not compile. Any idea what goes wrong?

#include <boost/spirit/include/qi.hpp>
int main(sint32 argc, char **argv)
{
  boost::spirit::qi::symbols<char, std::function<bool(int, int)>> sym;
  sym.add
  ("==", [](int v1, int v2) {return v1 == v2; })
  ("!=", [](int v1, int v2) {return v1 != v2; });
  bool result;
  std::string s("1==2");
  namespace qi = boost::spirit::qi;
  qi::phrase_parse(s.begin(), s.end(), (qi::int_ >> sym >> qi::int_)[qi::_val = boost::bind(qi::_2, qi::_1, qi::_3)], qi::space, result);
}
1

There are 1 best solutions below

0
On

Firstly, don't use boost::bind in a semantic action.

Semantic actions need to be Phoenix Actors. In other words, deferred or lazy function objects.

The way to get that would normally be using boost::phoenix::bind, but in this case you lose out: the bind doesn't expect a placeholder and doesn't know how to bind to it.

Instead, make the symbol table expose deferred functions. But then you need advanced wizardry to protect the inner bind from the outer ones: Post callbacks to a task queue using boost::bind

Instead I suggest doing the entire evaluation in a custom lazy action:

auto bin_eval = [](auto const& lhs, auto const& op, auto const& rhs) {
    return op(lhs, rhs);
};

And then

   (qi::int_ >> sym >> qi::int_)
        [qi::_val = px::bind(bin_eval, _1, _2, _3)],

However, that's not all yet. Your sample was likely oversimplified, as it couldn't even compile with correct semantic action. Instead you need to suppress attribute propagation, which only happens when you add a semantic action to a rule:

Live On Coliru

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

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

int main() {
    qi::symbols<char, std::function<bool(int, int)>> sym;
    sym.add
        ("==", [](int v1, int v2) {return v1 == v2; })
        ("!=", [](int v1, int v2) {return v1 != v2; });

    using namespace qi::labels;
    bool result;
    auto bin_eval = [](auto const& lhs, auto const& op, auto const& rhs) {
        return op(lhs, rhs);
    };

    qi::rule<std::string::const_iterator, bool(), qi::space_type> rule;
    rule = (qi::int_ >> sym >> qi::int_)
            [qi::_val = px::bind(bin_eval, _1, _2, _3)];

    for (std::string const s : {"1==2", "1!=2" }) {
        std::cout << std::quoted(s) << " -> ";

        if (qi::phrase_parse(s.begin(), s.end(), rule, qi::space, result)) {
            std::cout << "result: " << std::boolalpha << result << "\n";
        } else {
            std::cout << "parse failed\n";
        }
    }
}

Prints

"1==2" -> result: false
"1!=2" -> result: true

Prettier?

For bonus points, lift to a proper Phoenix Function:

struct bin_eval_t {
    template <typename T, typename U, typename V>
    auto operator()(T const& lhs, U const& op, V const& rhs) const {
        return op(lhs, rhs);
    }
};

// ...
px::function<bin_eval_t> bin_eval;

// ...
rule = (qi::int_ >> sym >> qi::int_)
        [qi::_val = bin_eval(_1, _2, _3)];

Simpler?

You can replace the lambdas with std functional:

sym.add
    ("==", std::equal_to<>{})
    ("!=", std::not_equal_to<>{});

Or if you don't have c++14

sym.add
    ("==", std::equal_to<int>{})
    ("!=", std::not_equal_to<int>{});

Much More?

If you want heterogeneous evaluations (not just boolean predicates), unary operators, precedence, associativity, look at some examples I made on this site.

Usually they separate the evaluation stage from the parsing stage:

However, if you want you can indeed combine parsing with evaluation. In that case it makes sense to simplify everything to be directly in the semantic actions:

Live On Coliru

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

namespace qi = boost::spirit::qi;

int main() {
    using namespace qi::labels;

    for (std::string const s : {"1==2", "1!=2" }) {
        std::cout << std::quoted(s) << " -> ";

        bool result;
        if (qi::phrase_parse(s.begin(), s.end(), 
            qi::int_ >> (
                "==" >> qi::int_ [ _val = (_val == _1) ]
              | "!=" >> qi::int_ [ _val = (_val != _1) ]
            ),
            qi::space,
            result))
        {
            std::cout << "result: " << std::boolalpha << result << "\n";
        } else {
            std::cout << "parse failed\n";
        }
    }
}

Prints

"1==2" -> result: false
"1!=2" -> result: true