1

I'm struggling to get to grips with boost qi parser semantic actions, using boost::bind and class members as follows:

#include <boost/spirit/include/qi.hpp>
#include <boost/bind/bind.hpp>
#include <string>
#include <iostream>

namespace qi = boost::spirit::qi;

class Parser
{
public:
    Parser() {}
    ~Parser() {}

    void GotComment(std::wstring const& s)
    {
        std::wcout << s << std::endl;
    }
};

template <typename IteratorT, typename SkipperT>
struct comment_grammar
    : qi::grammar<IteratorT, SkipperT, std::wstring()>
{
    comment_grammar(Parser& parser)
        : comment_grammar::base_type(rule, "Comment")
        , _parser(parser)
    {
        using namespace qi;
        using qi::standard_wide::print;
        using boost::placeholders::_1;

        rule %= L'#' >> lexeme[*print] [boost::bind(&Parser::GotComment, &_parser, _1)];
    }

    qi::rule<IteratorT, SkipperT, std::wstring()> rule;
    Parser& _parser;
};


int main()
{
    using namespace qi;
    using qi::standard_wide::blank_type;
    using qi::standard_wide::blank;
    using IteratorT = std::wstring::const_iterator;
    std::wstring result;
    std::wstring source(L"# A comment");
    IteratorT iter = source.cbegin();

    Parser parser;
    comment_grammar<IteratorT, blank_type> cg(parser);

    bool b = qi::phrase_parse<IteratorT>(iter, source.cend(), cg, blank, result);

    if (b)
        std::wcout << L"Succeeded: " << result << std::endl;
    else
        std::wcout << L"Parsing failed" << std::endl;

    return 0;
}

When compiled with Visual C++ 2019 and boost 1.76.0, it generates a 460 line error message starting:

boost\bind\bind.hpp(303,1): error C2664: 'R boost::_mfi::mf1<R,Parser,std::wstring>::operator ()(T &,A1) const': cannot convert argument 1 from 'const T' to 'T &'
with
[
    R=void,
    T=Parser,
    A1=std::wstring
]
and
[
    T=Parser *
]
and
[
    T=Parser
]

This is caused by the semantic action for the rule definition in comment_grammar. If I comment that out, all is well and the matching text is returned in result, but I really want Parser::GotComment to process it. Can anyone enlighten me?

sehe
  • 374,641
  • 47
  • 450
  • 633
Keith M
  • 101
  • 8

1 Answers1

0

Semantic actions have to be deferred actors. They're usually composed from Phoenix actors.

Simply replacing boost::bind with boost::phoenix::bind should do the trick in that respect (obviously also using the Qi placeholders, which are Phoenix-implemented).

Side notes:

  • Your using directives made _1 an ambiguous symbol anyways, which could have tipped you off about competing bind mechanisms

  • Your member handler takes a wstring, whereas the parser expression synthesizes vector<wchar> (or compatible). To fix the mismatch, you could

    • wrap the expression in qi::as_wstring

    • use the undocumented (experimental?) flag

      #define BOOST_SPIRIT_ACTIONS_ALLOW_ATTR_COMPAT
      
  • It probably makes very little sense to use a skipper in your comment parser. In fact, you'd usually make your comment parser part of a skipper

  • Consider not using using namespace quite so often/so broadly. It invites problems in generic code bases due to naming conflicts and ADL

  • I'd advise to use either semantic actions or attribute propagation¹.

Live Demo

Live On Coliru](http://coliru.stacked-crooked.com/a/e9b9eef8513e2f80)

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

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

struct Parser {
    void GotComment(std::wstring const& s) { std::wcout << s << std::endl; }
};

template <typename IteratorT, typename SkipperT>
struct comment_grammar
    : qi::grammar<IteratorT, SkipperT, std::wstring()>
{
    comment_grammar(Parser& parser)
        : comment_grammar::base_type(rule, "Comment")
        , _parser(parser)
    {
        using namespace qi::labels;

        rule %= L'#' >>
            qi::lexeme[*enc::print][px::bind(&Parser::GotComment, &_parser, _1)];
    }

    qi::rule<IteratorT, SkipperT, std::wstring()> rule;
    Parser& _parser;
};


int main()
{
    std::wstring const source(L"# A comment");
    using IteratorT = std::wstring::const_iterator;

    std::wstring result;
    IteratorT iter = source.cbegin(), end = source.end();;

    Parser parser;
    comment_grammar<IteratorT, enc::blank_type> const cg(parser);

    bool ok = qi::phrase_parse<IteratorT>(iter, end, cg, enc::blank, result);

    if (ok)
        std::wcout << L"Succeeded: " << std::quoted(result) << std::endl;
    else
        std::wcout << L"Parsing failed" << std::endl;

    if (iter != end) {
        std::wcout << L"Remaining input: " << std::quoted(std::wstring(iter,end)) << std::endl;
    }
}

Prints

A comment
Succeeded: "A comment"

¹ Boost Spirit: "Semantic actions are evil"?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • And this alternative with `as_wstring` just goes to show how mixing SA/attribute propagation is inviting complexity. I had to hack with `_val = _1` because `%=` will not compile: http://coliru.stacked-crooked.com/a/4dc5686f91694523 – sehe Apr 30 '21 at 16:47
  • More different approaches to write semantic actions (using adapted functions, phoenix::function<>, or free functions) are here https://stackoverflow.com/questions/16607238/how-to-avoid-boostphoenix-when-generating-with-boostspiritkarma/16609770#16609770 - it applies to Qi just as it does to Karma (also note the BOOST_FUSION_ADAPT_ADT there) – sehe Apr 30 '21 at 16:51
  • Thank you for coming up with your comprehensive solution and advice so quickly!. I've been working my way through the documentation for spirit and modelled my solution on this section: https://www.boost.org/doc/libs/1_76_0/libs/spirit/doc/html/spirit/qi/tutorials/semantic_actions.html I must have missed the part where it documents that the parser expression synthesizes vector, but your solution is cleaner anyway. – Keith M Apr 30 '21 at 17:53
  • I do actually need to preserve the comments, hence the use of a skipper to swallow any spaces after the '#' and a lexeme to preserve them in the comment. The complete grammar will have parsed values including strings, ints and structs, and I've yet to figure out how to implement the call to phrase_parse so that it doesn't return a value. – Keith M Apr 30 '21 at 17:53
  • Re. docs: https://www.boost.org/doc/libs/1_76_0/libs/spirit/doc/html/spirit/qi/reference/operator/kleene.html#spirit.qi.reference.operator.kleene.attributes – sehe Apr 30 '21 at 20:50
  • Re: preserving comments: okay, in that case you do not put the comment in the skipper. Still you wouldn't expect a skipper in the comment parser (evidenced by the fact that you `lexeme[]` the whole thing). Background: https://stackoverflow.com/questions/17072987/boost-spirit-skipper-issues/17073965#17073965 – sehe Apr 30 '21 at 20:50