Suedocode
Suedocode

Reputation: 2534

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?

Upvotes: 1

Views: 1277

Answers (1)

sehe
sehe

Reputation: 394054

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')

Upvotes: 3

Related Questions