Search code examples
c++boostboost-spiritboost-phoenix

Problems with boost::phoenix::bind and boost::phoenix::actors in a semantic action for boost::spirit::qi


I think I am having an issue understanding how my boost::spirit::qi parser is supposed to be written. I simply want to pass matched substrings to functions via semantic actions. In an attempt to roughly emulate this boost tutorial, I have come up with the following code:

template<class Iterator> 
struct _Calculator : public qi::grammar<Iterator> {                                                                                               

    qi::rule<Iterator> number, result; //and a whole bunch of other ones too                                                            

    qi::rule<Iterator,std::string()> variable;                                                                                      

    Code& code;                                                                                                    
    Variables& variables;                                                                                                          

    _Calculator(Code& code_, Variables& variables_)
      : _Calculator::base_type(result),                                                                                                           
        code(code_),
        variables(variables_)                                                                                                                     
   {  
          number =
                lexeme[(qi::char_("1-9") >> +qi::char_("0-9"))[boost::phoenix::bind(&fPushIntCV, qi::_1, boost::ref(code), boost::ref(variables))]]
              | lexeme[("0x" >> +qi::char_("0-9a-fA-F"))      [boost::phoenix::bind(&fPushIntCV, qi::_1, boost::ref(code), boost::ref(variables))]]
              | lexeme[("0b" >> +qi::char_("0-1"))            [boost::phoenix::bind(&fPushIntCV, qi::_1, boost::ref(code), boost::ref(variables))]]
              | lexeme[("0" >>  +qi::char_("0-7"))            [boost::phoenix::bind(&fPushIntCV, qi::_1, boost::ref(code), boost::ref(variables))]]
          //some other junk
    }
};
typedef _Calculator<std::string::const_iterator> Calculator;

When the declaration of fPushIntCV looks like this:

void fPushIntCV (vector<char> my_str, Code& c, Variables& v);

I get this error:

function_ptr.hpp:89: error: conversion from 'char' to non-scalar type 'std::vector<char, std::allocator<char> >' requested

When I change the declaration of fPushIntCV to look like this:

void fPushIntCV (char my_str, Code& c, Variables& v);

I get this error:

function_ptr.hpp:89: error: cannot convert 'std::vector<char, std::allocator<char> >' to 'char' in argument passing

I figured that the attribute of qi::_1 is changing, but I get unresolved references if I include both function prototypes and now pass boost::phoenix::bind some ambiguous overloaded function pointer:

error: no matching function for call to 'bind(<unresolved overloaded function type>, const boost::spirit::_1_type&, ... (my ... for trailing unrelated garbage)

I know this is probably a really simple error and a really simple fix, but I'm having a helluva time understanding the spell to make the boost magic do its thing. What is the function prototype that this semantic action is expecting?


Solution

  • A few observations:

    • You don't seem to be using a skipper, so using lexeme is redundant (see Boost spirit skipper issues)

    • You want to know how to detect the type of the attribute exposed by a parser expression: see Detecting the parameter types in a Spirit semantic action

      The types are documented with the parser directives, though, so e.g. as_string[(qi::char_("1-9") >> +qi::char_("0-9"))] results in boost::fusion::vector2<char, std::vector<char> >, which is directly reflected in the error message on GCC:

      boost/phoenix/bind/detail/preprocessed/function_ptr_10.hpp|50 col 39| error: could not convert ‘a0’ from ‘boost::fusion::vector2<char, std::vector<char> >’ to ‘std::vector<char>’
      
    • Prefer not to mix and match library placeholders/wrappers, e.g. boost::ref and boost::phoenix::ref

    • You seem to be reinventing integer parsing; consider using qi::int_parser instead

    • It seems that the case to parse 0 is missing :)

    Assuming you want my_str to simply reflect the input string including number base prefix, I could suggest using:

    number =
         as_string[(qi::char_("1-9") >> +qi::char_("0-9"))] [phx::bind(&fPushIntCV, qi::_1, phx::ref(code), phx::ref(variables))]
       | as_string[("0x" >> +qi::char_("0-9a-fA-F"))      ] [phx::bind(&fPushIntCV, qi::_1, phx::ref(code), phx::ref(variables))]
       | as_string[("0b" >> +qi::char_("0-1"))            ] [phx::bind(&fPushIntCV, qi::_1, phx::ref(code), phx::ref(variables))]
       | as_string[("0" >>  +qi::char_("0-7"))            ] [phx::bind(&fPushIntCV, qi::_1, phx::ref(code), phx::ref(variables))]
       //some other junk
       ;
    

    However, this could be simplified to:

    number = as_string[
           (qi::char_("1-9") >> +qi::char_("0-9"))
         | ("0x" >> +qi::char_("0-9a-fA-F"))
         | ("0b" >> +qi::char_("01"))
         | ("0"  >> +qi::char_("0-7"))
       ]        [phx::bind(&fPushIntCV, qi::_1, phx::ref(code), phx::ref(variables))]
       ;
    

    Now, you would probably like to just parse an integer value instead:

    number = 
         ( 
           ("0x" >> qi::int_parser<int, 16, 1>())
         | ("0b" >> qi::int_parser<int,  2, 1>())
         | ("0"  >> qi::int_parser<int,  8, 1>())
         | qi::int_ /* accepts "0" */) [phx::bind(&fPushIntCV, qi::_1, phx::ref(code), phx::ref(variables))]
       ;
    

    Which handsomely does the conversions[1], and you can just take an int:

    void fPushIntCV (int my_number, Code& c, Variables& v) {
        std::cout << "fPushIntCV: " << my_number << "\n";
    }
    

    [1] (there's also uint_parser and you can parse long, long long etc.; even big integers like boost::multiprecision::cpp_int should be no issue)

    Here's a demo program using this, showing that the values are converted correctly (and: "0" is accepted :)): Live On Coliru

    int main()
    {
        Code code;
        Variables variables;
        Calculator g(code, variables); 
    
        for (std::string const input : { "0", "0xef1A", "010", "0b10101" })
        {
            It f(input.begin()), l(input.end());
    
            if(qi::parse(f, l, g))
                std::cout << "Parse success ('" << input << "')\n";
            else std::cout << "Parse failed ('" << input << "')\n";
    
            if (f != l)
                std::cout << "Input remaining: '" << std::string(f, l) << "'\n";
        }
    }
    

    Prints

    fPushIntCV: 0
    Parse success ('0')
    
    fPushIntCV: 61210
    Parse success ('0xef1A')
    
    fPushIntCV: 8
    Parse success ('010')
    
    fPushIntCV: 21
    Parse success ('0b10101')