Question

Expanding on this earlier post, I thought I would try to capture a std::vector<boost::variant<double,std::string>> instead of just boost::variant<double,std::string>, but starting with the same-old-inputs first.

Here is my output given inputs 'foo' and 42.7:

/tmp$ g++ -g -std=c++11 sandbox.cpp -o sandbox && ./sandbox 
<m_rule>
  <try>foo</try>
  <success></success>
  <attributes>[[f, o, o]]</attributes>
</m_rule>
<m_rule>
  <try>42.7</try>
  <success></success>
  <attributes>[42.7]</attributes>
</m_rule>
std::string='foo'
double='42.7'
/tmp$ g++ -g -std=c++11 -DDO_VECTOR sandbox.cpp -o sandbox && ./sandbox 
<m_rule>
  <try>foo</try>
  <success></success>
  <attributes>[[102, 111, 111]]</attributes>
</m_rule>
<m_rule>
  <try>42.7</try>
  <success></success>
  <attributes>[[42.7]]</attributes>
</m_rule>
double='111'
double='42.7'
/tmp$

For some reason that I don't understand, the parser seems to be generating ASCII values for 'foo' and causing some confusion.

Do I need to change the parser when I turn on DO_VECTOR?

Should I be using a different container?

CODE

#define BOOST_SPIRIT_DEBUG

#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>

// #define DO_VECTOR

namespace {
namespace                              qi = boost::spirit::qi;
typedef   std::string::const_iterator  Iterator;

#ifdef DO_VECTOR
typedef std::vector<boost::variant<double, std::string>>  MY_TYPE;
#else
typedef boost::variant<double, std::string>               MY_TYPE;
#endif

class my_visitor
    : public boost::static_visitor<>
{
    public:
    my_visitor( std::string& result ) : m_str( result ) { }
    void operator()( double& operand )
    {
        std::ostringstream oss;
        oss << "double='" << operand << "'";
        m_str = oss.str();
    }
    void operator()( std::string& operand )
    {
        m_str = "std::string='";
        m_str.append( operand );
        m_str.append( "'" );
    }
    std::string& m_str;
};

// -----------------------------------------------------------------------------

struct variant_grammar : 
    qi::grammar<Iterator, MY_TYPE()>
{
    qi::rule<Iterator, MY_TYPE()> m_rule;

    variant_grammar() : variant_grammar::base_type(m_rule)
    {
        m_rule %= (qi::double_ | +qi::char_);

        BOOST_SPIRIT_DEBUG_NODE( m_rule );
    }
};

}

// -----------------------------------------------------------------------------

int main()
{
    const std::string    a( "foo" ), b( "42.7" );
    variant_grammar      varGrammar;

    MY_TYPE varA, varB;
    auto begA = a.begin(), endA = a.end();
    auto begB = b.begin(), endB = b.end();
    qi::parse( begA, endA, varGrammar, varA );
    qi::parse( begB, endB, varGrammar, varB );

    if ( begA!=endA )
        std::cerr << "A FAILED TO COMPLETELY PARSE" << std::endl;
    if ( begB!=endB )
        std::cerr << "B FAILED TO COMPLETELY PARSE" << std::endl;

    std::string resultA, resultB;
    my_visitor visitor1( resultA );
    my_visitor visitor2( resultB );

#ifdef DO_VECTOR
    std::for_each( varA.begin(), varA.end(), boost::apply_visitor( visitor1 ));
    std::for_each( varB.begin(), varB.end(), boost::apply_visitor( visitor2 ));
#else
    boost::apply_visitor( visitor1, varA );
    boost::apply_visitor( visitor2, varB );
#endif

    std::cout << resultA << std::endl;
    std::cout << resultB << std::endl;

    return 0;
}
Was it helpful?

Solution

Another solution is using qi::as_string[].

struct variant_grammar : 
    qi::grammar<Iterator, MY_TYPE()>
{
    qi::rule<Iterator, MY_TYPE()> m_rule;

    variant_grammar() : variant_grammar::base_type(m_rule)
    {
        m_rule %= (qi::double_ | qi::as_string[+qi::char_]);

        BOOST_SPIRIT_DEBUG_NODE( m_rule );
    }
};

Let's forget for a moment about the double. Your rule has an attribute of std::vector<std::string> that simplified is vector<vector<char>> and your +qi::char_ has an attribute vector<char>. What you want for "foo" is a vector1<vector3<char>> and what you get is a vector3<vector1<char>>. This is explained in the link above: when you have a situation like this, +qi::char calls traits::push_back_container for every char it parses. You can either use an auxiliary rule, as in sharth's answer, to disambiguate the situation or you can use one of the atomic parsing directives (qi::as_string[] in this case).

Edit:

Here is the code that solves your new problem:

#define BOOST_SPIRIT_DEBUG

#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>

// #define DO_VECTOR

namespace {
namespace                              qi = boost::spirit::qi;
typedef   std::string::const_iterator  Iterator;

#ifdef DO_VECTOR
typedef std::vector<boost::variant<double, std::string>>  MY_TYPE;
#else
typedef boost::variant<double, std::string>               MY_TYPE;
#endif

class my_visitor
    : public boost::static_visitor<>
{
    public:
    my_visitor( std::string& result ) : m_str( result ) { }
    void operator()( double& operand )
    {
        std::ostringstream oss;
        oss << "double='" << operand << "'";
        m_str += oss.str();
    }
    void operator()( std::string& operand )
    {
        m_str += "std::string='";
        m_str.append( operand );
        m_str.append( "'" );
    }
    std::string& m_str;
};

// -----------------------------------------------------------------------------

struct variant_grammar : 
    qi::grammar<Iterator, MY_TYPE(), qi::space_type> //added a skipper to the grammar
{
    qi::rule<Iterator, MY_TYPE(), qi::space_type> m_rule; //and to the rule. It's specially important that the starting rule and your grammar have the exact same template parameters

    variant_grammar() : variant_grammar::base_type(m_rule)
    {
        m_rule %= +(qi::double_ | qi::as_string[+(qi::char_-qi::digit)]);//Limited the string parser and added a `+` in order to parse more than one element

        BOOST_SPIRIT_DEBUG_NODE( m_rule );
    }
};

}

// -----------------------------------------------------------------------------

int main()
{
    const std::string    a( "foo 4.9 bar" ), b( "42.7" );
    variant_grammar      varGrammar;

    MY_TYPE varA, varB;
    auto begA = a.begin(), endA = a.end();
    auto begB = b.begin(), endB = b.end();
    qi::phrase_parse( begA, endA, varGrammar, qi::space, varA ); //when you have a skipper in your rule/grammar you need to use phrase_parse
    qi::phrase_parse( begB, endB, varGrammar, qi::space, varB );

    if ( begA!=endA )
        std::cerr << "A FAILED TO COMPLETELY PARSE" << std::endl;
    if ( begB!=endB )
        std::cerr << "B FAILED TO COMPLETELY PARSE" << std::endl;

    std::string resultA, resultB;
    my_visitor visitor1( resultA );
    my_visitor visitor2( resultB );

#ifdef DO_VECTOR
    std::for_each( varA.begin(), varA.end(), boost::apply_visitor( visitor1 ));
    std::for_each( varB.begin(), varB.end(), boost::apply_visitor( visitor2 ));
#else
    boost::apply_visitor( visitor1, varA );
    boost::apply_visitor( visitor2, varB );
#endif

    std::cout << resultA << std::endl;
    std::cout << resultB << std::endl;

    return 0;
}

Several small changes: added a skipper to the grammar and used phrase_parse instead of parse consequently. Limited the string parser. Changed the printers to append to your string not overwrite it.

OTHER TIPS

I imagine this will fix it:

struct variant_grammar : qi::grammar<Iterator, MY_TYPE()> {
    qi::rule<Iterator, MY_TYPE()> m_rule;
    qi::rule<Iterator, std::string()> m_string;

    variant_grammar() : variant_grammar::base_type(m_rule) {
        m_rule %= qi::double_ | m_string;
        m_string %= +qi::char_;

        BOOST_SPIRIT_DEBUG_NODE( m_rule );
        BOOST_SPIRIT_DEBUG_NODE( m_string );
    }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top