Search code examples
c++semanticsboost-spirit-qiboost-phoenix

Boost spirit: Invalidate parser from member function


this article (boost spirit semantic action parameters) explains how to invalidate a match from a plain function with the signature

void f(int attribute, const boost::fusion::unused_type& it, bool& mFlag)

I would like to invalidate a match from a member function of the grammar:

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

#include <iostream>
#include <string>

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


class moduleAccessManager
{
public:
    bool getModule(const std::string name)
    {
        if(name == "cat" || name == "dog")
            return true;
        else
            return false;
    }
};

void globalIsModule(std::string moduleName, const boost::spirit::unused_type&, bool& mFlag)
{
        moduleAccessManager acm; /* Dirty workaround for this example */
        if(acm.getModule(moduleName))
            std::cout << "[isModule] Info: Found module with name >"  << moduleName << "<" << std::endl;
        else
        {
            std::cout << "[isModule] Error: No module with name >" << moduleName << "<" << std::endl;
            mFlag = false; // No valid module name
        }
}


template <typename Iterator, typename Skipper>
class moduleCommandParser : public qi::grammar<Iterator, Skipper>
{
private:
    moduleAccessManager* m_acm;

    qi::rule<Iterator, Skipper> start, module;

public:
    std::string m_moduleName;

    moduleCommandParser(moduleAccessManager* acm)
        : moduleCommandParser::base_type(start)
        , m_acm(acm)
        , m_moduleName("<empty>")
    {
        module  =   qi::as_string[qi::lexeme[+(~qi::char_(' '))]]
            [&globalIsModule] // This works fine
//          [phoenix::bind(&moduleCommandParser::isModule, this)] // Compile error
            ;
        start    =  module >> qi::as_string[+(~qi::char_('\n'))];
    };

    void isModule(std::string moduleName, const boost::spirit::unused_type&, bool& mFlag)
    {
        // Check if a module with moduleName exists
        if(m_acm->getModule(moduleName))
            std::cout << "[isModule] Info: Found module with name >"  << moduleName << "<" << std::endl;
        else
        {
            std::cout << "[isModule] Error: No module with name >" << moduleName << "<" << std::endl;
            mFlag = false; // No valid module name
        }
    };

};


int main()
{
    moduleAccessManager acm;
    moduleCommandParser<std::string::const_iterator, qi::space_type> commandGrammar(&acm);

    std::string str;
    std::string::const_iterator first;
    std::string::const_iterator last;

    str = "cat run";
    first = str.begin();
    last = str.end();
    qi::phrase_parse(first, last, commandGrammar, qi::space);

    str = "bird fly";
    first = str.begin();
    last = str.end();
    qi::phrase_parse(first, last, commandGrammar, qi::space);
}

Code on Coliru: http://coliru.stacked-crooked.com/a/4319b38a6d36c362

The important part is these two lines:

            [&globalIsModule] // This works fine
//          [phoenix::bind(&moduleCommandParser::isModule, this)] // Compile error

Using the global function works fine, but that's not an option for me because I need access to the m_acm object which is specific to the parser.

How can I bind a member function to a semantic action and at the same time be able to invalidate the match from that member function (using the 3 argument function signature mentioned above)?


Solution

  • There are two ways:

    • you can assign to qi::_val using Phoenix actors
    • you can assign to the third parameter (bool&) inside a "raw" semantic action function

    An example is here:

    The anatomy of a semantic action function (with the third argument):


    In your case you have a member function with roughly the "raw semantic action function" signature. Of course, you'll have to bind for the this parameter (because it's a non-static member function).

    Note that in this particular case, phoenix::bind is not the right bind to use, as Phoenix Actors will be considered to be "cooked" (not raw) semantic actions, and they will get executed in the Spirit context.

    You could either

    1. use boost::bind (or even std::bind) to bind into a function that preserves the arity (!) of the member function:

      [boost::bind(&moduleCommandParser::isModule, this, ::_1, ::_2, ::_3)]
      

      This works: Live On Coliru

    2. instead use a "cooked" semantic action, directly assigning to the _pass context placeholder:

      [qi::_pass = phoenix::bind(&moduleAccessManager::getModule, m_acm, qi::_1)]
      

      This works too: Live On Coliru

    The latter example, for future reference:

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    #include <iostream>
    #include <string>
    
    namespace qi      = boost::spirit::qi;
    namespace phoenix = boost::phoenix;
    
    class moduleAccessManager {
    public:
        bool getModule(const std::string name) {
            return name == "cat" || name == "dog";
        }
    };
    
    void globalIsModule(std::string moduleName, const boost::spirit::unused_type&, bool& mFlag)
    {
            moduleAccessManager acm; /* Dirty workaround for this example */
            if(acm.getModule(moduleName))
                std::cout << "[isModule] Info: Found module with name >"  << moduleName << "<" << std::endl;
            else
            {
                std::cout << "[isModule] Error: No module with name >" << moduleName << "<" << std::endl;
                mFlag = false; // No valid module name
            }
    }
    
    template <typename Iterator, typename Skipper>
    class moduleCommandParser : public qi::grammar<Iterator, Skipper>
    {
    private:
        moduleAccessManager* m_acm;
    
        qi::rule<Iterator, Skipper> start, module;
    
    public:
        std::string m_moduleName;
    
        moduleCommandParser(moduleAccessManager* acm)
            : moduleCommandParser::base_type(start)
            , m_acm(acm)
            , m_moduleName("<empty>")
        {
            using namespace phoenix::arg_names;
            module  =   qi::as_string[qi::lexeme[+(~qi::char_(' '))]]
                            [qi::_pass = phoenix::bind(&moduleAccessManager::getModule, m_acm, qi::_1)]
                        ;
            start   =  module >> qi::as_string[+(~qi::char_('\n'))];
        };
    
    };
    
    
    int main()
    {
        moduleAccessManager acm;
        moduleCommandParser<std::string::const_iterator, qi::space_type> commandGrammar(&acm);
    
        std::string str;
        std::string::const_iterator first;
        std::string::const_iterator last;
    
        str = "cat run";
        first = str.begin();
        last = str.end();
        std::cout << str << std::boolalpha 
                  << qi::phrase_parse(first, last, commandGrammar, qi::space)
                  << "\n";
    
        str = "bird fly";
        first = str.begin();
        last = str.end();
        std::cout << str << std::boolalpha 
                  << qi::phrase_parse(first, last, commandGrammar, qi::space)
                  << "\n";
    }