Boost::Spirit::Qi. How to turn inlined parser expressions into standalone grammars, and how to unpack the tuples generated by them?
Asked Answered
B

3

9

I'm using QI and Phoenix, and I want to write a small grammar that returns 4 bools which are to be used as arguments for a function call inside a semantic action.

I have several functions that need those things, and so far I have used this approach:

( qi::_bool >>  qi::_bool >>  qi::_bool >>  qi::_bool)
[px::bind(&Bool4Function, spirit::_val, spirit::_1, spirit::_2, spirit::_3, spirit::_4)]

and while it's okay on it's own, using it all over the place is just plain ugly and confusing, even with 'using' the namespace parts.

That's why I wanted to extract this expression into a standalone grammar.

So I tried this (credit goes to ildjarn for the testbed):

///// grammar implementation /////
#include <boost/fusion/include/vector10.hpp>
#include <boost/spirit/include/qi_bool.hpp>
#include <boost/spirit/include/qi_char_.hpp>
#include <boost/spirit/include/qi_grammar.hpp>
#include <boost/spirit/include/qi_operator.hpp>
#include <boost/spirit/include/qi_rule.hpp>
#include <boost/spirit/include/qi_string.hpp>

struct FourBools : boost::spirit::qi::grammar<
    char const*,
    boost::fusion::vector4<bool, bool, bool, bool>()
>
{
    typedef boost::fusion::vector4<bool, bool, bool, bool> attribute_type;

    FourBools() : base_type(start_)
    {
        using boost::spirit::bool_;

        start_
            =   "4bools:"
            >> bool_ >> ','
            >> bool_ >> ','
            >> bool_ >> ','
            >> bool_ >> ';'
            ;
    }

private:
    boost::spirit::qi::rule<
        base_type::iterator_type,
        base_type::sig_type
    > start_;
};
FourBools const fourBools;


///// demonstration of use /////
#include <string>
#include <ios>
#include <iostream>
#include <boost/fusion/include/at_c.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/qi_action.hpp>
#include <boost/spirit/include/qi_parse.hpp>



void noDice(bool a, bool b, bool c, bool d) 
{

}

void worksFine(boost::fusion::vector4<bool, bool, bool, bool> a)
{

}
int main()
{
    namespace phx = boost::phoenix;
    namespace spirit = boost::spirit;

    std::string const input("4bools:true,true,true,false;");


    char const* first = input.c_str();
    char const* const last = first + input.size();
    bool const success = spirit::qi::parse(
        first, last,
        fourBools[phx::bind(&noDice, spirit::_1)]
    );


    if (!success)
        std::cout << "parse() failed\n";
    else if (first != last)
        std::cout << "didn't consume all input\n";
    std::cout.flush();
}

That doesn't compile unless fourBools[phx::bind(&noDice, spirit::_1)] is replaced with fourBools[phx::bind(&worksFine, spirit::_1)].

That means, my problem is the unpacking of arguments to match the signature of the function to be called, since the number of arguments differ at signature level (one tuple of four bools, vs four bools on their own).

Is it possible to unpack using phoenix placeholders directly, instead of writing wrappers which translate tuples into individual arguments for my existing functions that need them separate? If it is, what would be the syntax for that? After all, an inline version like ( qi::_bool >> qi::_bool >> qi::_bool >> qi::_bool) works fine when 'unpacked' by spirit::_1 - spirit::_4, placeholders.

That makes it appear to me as if this version returns a tuple as well, and is somehow unpackable with the above approach, unlike a grammar that returns one.

How do I deal with this?

Bugg answered 27/5, 2011 at 20:53 Comment(0)
G
12

It's pretty much impossible to diagnose your issue if you don't post a complete, coherent repro; it could be a syntax error, it could be a missing #include, who knows..?

Here's a working demonstration; hopefully you can use it as a reference to figure out what's wrong with your code:

///// grammar implementation /////
#include <boost/fusion/include/vector10.hpp>
#include <boost/spirit/include/qi_bool.hpp>
#include <boost/spirit/include/qi_char_.hpp>
#include <boost/spirit/include/qi_grammar.hpp>
#include <boost/spirit/include/qi_operator.hpp>
#include <boost/spirit/include/qi_rule.hpp>
#include <boost/spirit/include/qi_string.hpp>

struct FourBools : boost::spirit::qi::grammar<
    char const*,
    boost::fusion::vector4<bool, bool, bool, bool>()
>
{
    typedef boost::fusion::vector4<bool, bool, bool, bool> attribute_type;

    FourBools() : base_type(start_)
    {
        using boost::spirit::bool_;

        start_
            =   "4bools:"
                >> bool_ >> ','
                >> bool_ >> ','
                >> bool_ >> ','
                >> bool_ >> ';'
            ;
    }

private:
    boost::spirit::qi::rule<
        base_type::iterator_type,
        base_type::sig_type
    > start_;
};
FourBools const fourBools;


///// demonstration of use /////
#include <string>
#include <ios>
#include <iostream>
#include <boost/fusion/include/at_c.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/qi_action.hpp>
#include <boost/spirit/include/qi_parse.hpp>

typedef FourBools::attribute_type attr_t;

struct verify_same
{
    explicit verify_same(attr_t const& expected) : expected_(expected) { }

    void verify(attr_t const& actual) const
    {
        using boost::fusion::at_c;

        std::cout << std::boolalpha
            << "same as expected: " << (actual == expected_)
            << "\nactual values: "
            << at_c<0>(actual) << ' '
            << at_c<1>(actual) << ' '
            << at_c<2>(actual) << ' '
            << at_c<3>(actual) << '\n';
    }

private:
    attr_t expected_;
};

int main()
{
    namespace phx = boost::phoenix;
    namespace spirit = boost::spirit;

    std::string const input("4bools:true,true,true,false;");
    verify_same const vs(attr_t(true, true, true, false));

    char const* first = input.c_str();
    char const* const last = first + input.size();
    bool const success = spirit::qi::parse(
        first, last,
        fourBools[phx::bind(&verify_same::verify, phx::cref(vs), spirit::_1)]
    );
    if (!success)
        std::cout << "parse() failed\n";
    else if (first != last)
        std::cout << "didn't consume all input\n";
    std::cout.flush();
}

As an aside, I think using a tuple with purely homogeneous types is strange; personally, I'd change the grammar's synthesized attribute to boost::array<bool, 4>.


EDIT (in response to OP's edit): There's good news and bad news and more good news.

Here's the good news: Boost.Fusion has functionality to do exactly what you want to do with minimal code: boost::fusion::fused<>. This will take a callable type (including free-function pointers and member-function pointers) that takes multiple arguments and wrap that callable type in a functor that takes a Fusion sequence; when this functor is invoked, it takes the Fusion sequence and unpacks it, forwarding the individual elements of the tuple to the wrapped callable type as separate arguments.

So, given the grammar I already posted and the following:

#include <string>
#include <ios>
#include <iostream>
#include <boost/fusion/include/at_c.hpp>
#include <boost/fusion/include/make_fused.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/qi_action.hpp>
#include <boost/spirit/include/qi_parse.hpp>

typedef FourBools::attribute_type attr_t;

void free_func_taking_tuple(attr_t const& tup)
{
    using boost::fusion::at_c;

    std::cout << std::boolalpha
        << "inside free_func_taking_tuple() :: "
        << at_c<0>(tup) << ' '
        << at_c<1>(tup) << ' '
        << at_c<2>(tup) << ' '
        << at_c<3>(tup) << '\n';
}

void free_func_taking_bools(
    bool const a, bool const b,
    bool const c, bool const d
)
{
    std::cout << std::boolalpha
        << "inside free_func_taking_bools() :: "
        << a << ' '
        << b << ' '
        << c << ' '
        << d << '\n';
}

boost::spirit::qi::parse() can be called like so:

namespace phx = boost::phoenix;
namespace spirit = boost::spirit;
using boost::fusion::make_fused;

// calls free_func_taking_tuple, nothing new here
spirit::qi::parse(
    first, last,
    fourBools[phx::bind(free_func_taking_tuple, spirit::_1)]
);

// calls free_func_taking_bools, using boost::fusion::fused<> to unpack the tuple
// into separate arguments
spirit::qi::parse(
    first, last,
    fourBools[phx::bind(make_fused(&free_func_taking_bools), spirit::_1)]
);

Here's the bad news: Boost.Fusion's callable type wrappers rely on the TR1/C++11 result_of protocol, while Boost.Phoenix v2 implements the Boost.Lambda result_of protocol – these are not compatible. As a result, you must unpack the tuple elements yourself:

namespace phx = boost::phoenix;
namespace spirit = boost::spirit;

spirit::qi::parse(
    first, last,
    fourBools[phx::bind(
        free_func_taking_bools,
        phx::at_c<0>(spirit::_1),
        phx::at_c<1>(spirit::_1),
        phx::at_c<2>(spirit::_1),
        phx::at_c<3>(spirit::_1)
    )]
);

Yuck! But, there's more good news: Boost.Phoenix v3 is going to be released in Boost 1.47, and it implements the TR1/C++11 result_of protocol. Consequently, starting with Boost 1.47 you'll be able to use boost::fusion::fused<> and save yourself some tedious boilerplate.

Gaselier answered 27/5, 2011 at 22:46 Comment(2)
Comments like this one are probably frowned upon, but I just have to get my elation about seeing this kind of code off my chest. I feel so grubby next to it, it's very motivating, didn't even know half of the things listed and I thought I was already getting better. anyway, I'll include a repro tomorrow, if it still wont work after thoroughly analyzing your post.Bugg
@Bugg :-] If you end up posting a repro, please post a comment on this answer so I'll get a message in my SO inbox, otherwise I probably won't see your edit.Gaselier
M
3

As a general note, I'd suggest to read the articles about attribute handling on the Spirit website here. These constitute a nice addendum to the online docs as distributed with the library.

Maryammaryann answered 28/5, 2011 at 0:2 Comment(1)
Bookmarked. I'll be sure to work myself through that, as well, thanks.Bugg
V
0

The attribute of qi::_bool >> qi::_bool >> qi::_bool >> qi::_bool is std::vector<bool> or any other stl container, as it's described in the reference: http://www.boost.org/doc/libs/1_46_0/libs/spirit/doc/html/spirit/qi/quick_reference/compound_attribute_rules.html.

The first row of the table is the case :)

Ventilation answered 27/5, 2011 at 20:58 Comment(2)
Tried that just now, didn't compile either, unless I'm not supposed to use qi::_1 - qi::_4 to extract the values from the vector.Bugg
The attribute of bool_ >> bool_ >> bool_ >> bool_ is indeed fusion::vector<bool, bool, bool, bool>; Spirit just happens to allow use of some_container<bool> instead because the tuple is composed purely of homogeneous types. This behavior is termed 'attribute collapsing' in the Spirit docs.Gaselier

© 2022 - 2024 — McMap. All rights reserved.