Search code examples
c++parsingstlboost-spirit-qi

How to place structure "summand" into a vector when parsing with boost::spirit?


My code below successfully parse string like "-5.24 * [HelloWorld : w]" into struct summand but i need that it would be possible to parse for example string like "-5.24 * [HelloWorld : w] -7 * [HelloWorld : b] -8.24 * [HelloWorld : h]" into a vector<summand>. So my question is how to do that?

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <iostream>
#include <string>
#include <vector>

enum RectProperty {
    RectPropertyNone                = 0,
    RectPropertyLeft                = 1<<0,
    RectPropertyRight               = 1<<1,
    RectPropertyCentreX             = 1<<2,
    RectPropertyWidth               = 1<<3,
    RectPropertyTop                 = 1<<4,
    RectPropertyBottom              = 1<<5,
    RectPropertyCentreY             = 1<<6,
    RectPropertyHeight               = 1<<7
};

namespace client
{
    namespace spirit = boost::spirit;
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phoenix = boost::phoenix;

    struct rectProperties_ : qi::symbols<char, unsigned>
    {
        rectProperties_()
        {
        add
        ("cx"   , RectPropertyCentreX)
        ("cy"   , RectPropertyCentreY)
        ("w"    , RectPropertyWidth)
        ("h"    , RectPropertyHeight)
        ("l"    , RectPropertyLeft)
        ("r"    , RectPropertyRight)
        ("t"    , RectPropertyTop)
        ("b"    , RectPropertyBottom)
        ;
        }

    } rectProperties;

    struct summand
    {
        float factor;
        std::string nodeName;
        RectProperty property;
    };

    std::vector<summand> summands;

    void addToVector(summand const& sum)
    {
        summands.push_back(sum);
    }
}

BOOST_FUSION_ADAPT_STRUCT(client::summand,
                      (float, factor)
                      (std::string, nodeName)
                      (RectProperty, property)
                      );

namespace client {

template <typename Iterator>
struct summand_parser : qi::grammar<Iterator, summand(), ascii::space_type>
{
    summand_parser() : summand_parser::base_type(start)
    {
        using spirit::float_;
        using ascii::no_case;
        using ascii::alpha;
        using ascii::alnum;

        using qi::lexeme;
        using qi::lit;


        start %= float_ >> -lit('*') >> '[' >> lexeme[alpha >> *alnum] >> ':' >> no_case[rectProperties] >> ']';
    }

    qi::rule<Iterator, summand(), ascii::space_type> start;
};
}

float computeSimpleMathExpression(const char * pStr)
{
    using boost::spirit::ascii::space;
    typedef std::string::const_iterator iterator_type;
    typedef client::summand_parser<iterator_type> summand_parser;

    summand_parser g; // Our grammar
    std::string str("-5.24 * [ HelloWorld : w  ]");

    client::summand sum;
    std::string::const_iterator iter = str.begin();
    std::string::const_iterator end = str.end();
    bool r = phrase_parse(iter, end, g, space, sum);

    if (r && iter == end)
    {
        std::cout << "-------------------------\n";
        std::cout << "Parsing succeeded\n";
        std::cout << "got: " << boost::fusion::as_vector(sum) << std::endl;
        std::cout << str << " Parses OK: " << std::endl;
    }
    else
    {
        std::cout << "-------------------------\n";
        std::cout << "Parsing failed\n";
        std::cout << "-------------------------\n";
    }
    return 0;
}

I have tried to change

start %= float_ >> -lit('*') >> '[' >> lexeme[alpha >> *alnum] >> ':' >> no_case[rectProperties] >> ']';

to

start %= (float_ >> -lit('*') >> '[' >> lexeme[alpha >> *alnum] >> ':' >> no_case[rectProperties] >> ']')[&addToVector];

but this leads to compile time errors:

/Users/apple/Documents/ios/framework/boost.framework/Headers/spirit/home/support/action_dispatch.hpp:204:15: No viable conversion from 'boost::fusion::vector3<float, boost::fusion::vector2<char, std::__1::vector<char, std::__1::allocator<char> > >, unsigned int>' to 'const client::summand'

and

/Users/apple/Documents/ios/framework/boost.framework/Headers/spirit/home/qi/detail/assign_to.hpp:152:20: No matching conversion for static_cast from 'const boost::fusion::vector3<float, boost::fusion::vector2<char, std::__1::vector<char, std::__1::allocator<char> > >, unsigned int>' to 'client::summand'

Solution

  • You need to have "lazy functors" in semantic actions. Phoenix names these "Actors". To use a regular function as an actor, just bind it:

        summand_rule %= 
               float_ 
            >> -lit('*') 
            >> '[' 
            >> lexeme[alpha >> *alnum] 
            >> ':' 
            >> no_case[rectProperties] 
            >> ']';
    
        start %= summand_rule [phoenix::bind(&addToVector, qi::_1)];
    

    See a compiling example live on Coliru, printing

    pushed: (-5.24 HelloWorld 8)
    

    Now, I get the distinct impression you're simply trying to parse a vector of summands, in which case you should just use operator% (the list parser) or operator* (Kleen star).

    Here's the example using the three-summand input text from the question and using

        qi::rule<Iterator, std::vector<summand>(), ascii::space_type> start;
        // defined as
        start = *summand_rule;
    

    Nothing else required! The demo program Live on Coliru

    int main()
    {
        std::vector<client::summand> parsed;
        parseSummandsInto("-5.24 * [HelloWorld : w] -7 * [HelloWorld : b] -8.24 * [HelloWorld : h]", parsed);
    
        for(auto const& summand : parsed)
            std::cout << "pushed: " << boost::fusion::as_vector(summand) << std::endl;
    }
    

    Prints

    pushed: (-5.24 HelloWorld 8)
    pushed: (-7 HelloWorld 32)
    pushed: (-8.24 HelloWorld 128)
    

    Note:

    • removed the need for phoenix or semantic actions
    • removed the need for a global variable client::summands
    • removed the need for addToVector
    • in total 33 lines of code less (89 vs. 122)

    Full code

    For future reference:

    #include <boost/config/warning_disable.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    
    #include <iostream>
    #include <string>
    #include <vector>
    
    enum RectProperty {
        RectPropertyNone                = 0,
        RectPropertyLeft                = 1<<0,
        RectPropertyRight               = 1<<1,
        RectPropertyCentreX             = 1<<2,
        RectPropertyWidth               = 1<<3,
        RectPropertyTop                 = 1<<4,
        RectPropertyBottom              = 1<<5,
        RectPropertyCentreY             = 1<<6,
        RectPropertyHeight              = 1<<7
    };
    
    namespace client
    {
        namespace spirit = boost::spirit;
        namespace qi     = boost::spirit::qi;
        namespace ascii  = boost::spirit::ascii;
    
        struct rectProperties_ : qi::symbols<char, unsigned>
        {
            rectProperties_() {
                add ("cx"   , RectPropertyCentreX)
                    ("cy"   , RectPropertyCentreY)
                    ("w"    , RectPropertyWidth)
                    ("h"    , RectPropertyHeight)
                    ("l"    , RectPropertyLeft)
                    ("r"    , RectPropertyRight)
                    ("t"    , RectPropertyTop)
                    ("b"    , RectPropertyBottom)
                    ;
            }
        } rectProperties;
    
        struct summand {
            float factor;
            std::string nodeName;
            RectProperty property;
        };
    
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::summand,
                          (float, factor)
                          (std::string, nodeName)
                          (RectProperty, property)
                          )
    
    namespace client {
    
    template <typename Iterator>
    struct summand_parser : qi::grammar<Iterator, std::vector<summand>(), ascii::space_type>
    {
        summand_parser() : summand_parser::base_type(start)
        {
            using namespace ascii;
    
            summand_rule %= qi::float_ >> -qi::lit('*') >> '[' >> qi::lexeme[alpha >> *alnum] >> ':' >> no_case[rectProperties] >> ']';
            start = *summand_rule;
        }
    
        qi::rule<Iterator, summand(), ascii::space_type> summand_rule;
        qi::rule<Iterator, std::vector<summand>(), ascii::space_type> start;
    };
    }
    
    void parseSummandsInto(std::string const& str, std::vector<client::summand>& summands)
    {
        typedef std::string::const_iterator It;
        static const client::summand_parser<It> g;
    
        It iter = str.begin(), 
           end = str.end();
    
        bool r = phrase_parse(iter, end, g, boost::spirit::ascii::space, summands);
    
        if (r && iter == end)
            return;
        else
            throw "Parse failed";
    }
    
    int main()
    {
        std::vector<client::summand> parsed;
        parseSummandsInto("-5.24 * [HelloWorld : w] -7 * [HelloWorld : b] -8.24 * [HelloWorld : h]", parsed);
    
        for(auto const& summand : parsed)
            std::cout << "pushed: " << boost::fusion::as_vector(summand) << std::endl;
    }