Search code examples
c++boost-spiritboost-spirit-qi

Add to a spirit qi symbol table in a semantic action


Going by the opening paragraph of the boost::spirit::qi::symbols documentation, I assumed that it wouldn't be too hard to add symbols to a qi::symbols from a semantic action. Unfortunately it appears to be not as straightforward as I would have assumed.

The following bit of test code exhibits the problem:

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>

namespace qi = boost::spirit::qi;

typedef qi::symbols<char, unsigned int> constants_dictionary;

template <typename Iter> struct parser : public qi::grammar<Iter, qi::space_type> {

    parser(constants_dictionary &dict) : parser::base_type(start) {

        start = qi::lit("@") >> ((+qi::char_) >> qi::uint_)[dict.add(qi::_1, qi::_2)];
    }

    qi::rule<Iter> start;
};

int main() {
    constants_dictionary dict;
    parser<std::string::const_iterator> prsr(dict);
    std::string test = "@foo 3";
    parse(test.begin(), test.end(), prsr, qi::space);
}

Gives type errors related to qi::_2 from VS2010:

C:\Users\k\Coding\dashCompiler\spirit_test.cpp(12) : error C2664: 'const boost::
spirit::qi::symbols<Char,T>::adder &boost::spirit::qi::symbols<Char,T>::adder::o
perator ()<boost::spirit::_1_type>(const Str &,const T &) const' : cannot conver
t parameter 2 from 'const boost::spirit::_2_type' to 'const unsigned int &'
        with
        [
            Char=char,
            T=unsigned int,
            Str=boost::spirit::_1_type
        ]
        Reason: cannot convert from 'const boost::spirit::_2_type' to 'const uns
igned int'
        No user-defined-conversion operator available that can perform this conv
ersion, or the operator cannot be called
        C:\Users\k\Coding\dashCompiler\spirit_test.cpp(10) : while compiling cla
ss template member function 'parser<Iter>::parser(constants_dictionary &)'
        with
        [
            Iter=std::_String_const_iterator<char,std::char_traits<char>,std::al
locator<char>>
        ]
        C:\Users\k\Coding\dashCompiler\spirit_test.cpp(21) : see reference to cl
ass template instantiation 'parser<Iter>' being compiled
        with
        [
            Iter=std::_String_const_iterator<char,std::char_traits<char>,std::al
locator<char>>
        ]

(Apologies for the nasty VS2010 error-style)

What syntax am I supposed to be using to add (and later on, remove) symbols from this table?


Solution

  • This question has been answered before. However, there is quite a range of problems with your posted code, so I'll fix them up one by one to spare you unnecessary staring at pages of error messages.

    The working code (plus verification of output) is here on liveworkspace.org.

    Notes:

    1. the semantic action must be a Phoenix actor, i.e. you need

      • boost::bind, phoenix::bind, std::bind
      • phoenix::lambda<> or phoenix::function<>
      • a function pointer or polymorphic calleable object (as per the documentation)

        I'd recommend phoenix::bind (in this particular case), which I show below

    2. There was a mismatch between the parser's skipper and the start rule
    3. qi::char_ eats all characters. Combined with the skipper, this resulted in parse failure, because (obviously) the digits in the value were also being eaten by +qi::char_. I show you one of many solutions, based on qi::lexeme[+qi::graph]
    4. use qi::lexeme to 'bypass' the skipper (i.e. to prevent +qi::graph to cut across whitespace because the skipper, well, skipped it)
    5. qi::parse doesn't take a skipper; use qi::phrase_parse for that (the reason it appeared to work is that any trailing 'variadic' arguments are bound to the exposed attributes of the parser, which in this case are unspecified, and therefore qi::unused_type).
    6. if you want to pass test.begin() and test.end() directly to qi::phrase_parse, you need to make it clear that you want const iterators. The more typical solution would be to introduce explicitely typed variables (first and last, e.g.)


    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <string>
    
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    typedef qi::symbols<char, unsigned int> constants_dictionary;
    
    template <typename Iter> struct parser : qi::grammar<Iter, qi::space_type> 
    {
        parser(constants_dictionary &dict) : parser::base_type(start) 
        {
            start = qi::lit("@") >> (qi::lexeme [+qi::graph] >> qi::uint_)
                [ phx::bind(dict.add, qi::_1, qi::_2) ]
                ;
        }
    
        qi::rule<Iter, qi::space_type> start;
    };
    
    int main() {
        constants_dictionary dict;
        parser<std::string::const_iterator> prsr(dict);
        const std::string test = "@foo 3";
    
        if (qi::phrase_parse(test.begin(), test.end(), prsr, qi::space))
        {
            std::cout << "check: " << dict.at("foo") << "\n";
        }
    }