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

Setting attributes from semantic action(s) for expression passed into qi::phrase_parse


During parsing I have a few attributes that I need to set in semantic action only ( as they are derived from data being parsed and I want to avoid global variables and dependency on BOOST_FUSION_ADAPT_STRUCT as well as my code should be generic so that I can reuse it for multiple types). If I use more than one variable passed into qi::phrase_parse I get very long list of compilation errors. I need a help badly :-)

#define BOOST_RESULT_OF_USE_DECLTYPE
#define BOOST_SPIRIT_USE_PHOENIX_V3

#include <boost/function.hpp>
#include <boost/phoenix/fusion.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>

#include <iostream>
#include <string>
#include <climits>

namespace qi = boost::spirit::qi;
namespace ph = boost::phoenix;
namespace ascii = boost::spirit::ascii;

int main( int argc, char**argv )
{
    bool rc;
    std::string input("");

    //Test case 1 works fine
    {
        auto iter( input.begin() );
        auto last( input.end() );
        int val1=33;
        rc = qi::phrase_parse( iter, last, qi::eps[ qi::_val=11 ] , 
                   ascii::space, val1 ) && iter==last;
        if( rc )
            std::cout << "val1=" << val1 << std::endl; 
    }
    //Test case 2 does not compile
    {
        auto iter( input.begin() );
        auto last( input.end() );
        int val1=33;
        int val2=0;
        rc = qi::phrase_parse( iter, last, 
                qi::eps[ ph::at_c<0>(qi::_val)=1,ph::at_c<1>(qi::_val)=2 ],
                ascii::space, val1,val2 ) && iter==last;
        if( rc )
            std::cout << "val1=" << val1 << 
                         " val2=" << val2 << std::endl;         
    }
    //Test case 3 works fine
    {
        auto iter( input.begin() );
        auto last( input.end() );
        int val1=33;
        int val2=0;
        rc = qi::phrase_parse( iter, last,
                qi::attr(1)>>qi::attr(2),
                ascii::space, val1,val2 ) && iter==last;
        if( rc )
            std::cout << "val1=" << val1 <<
                         " val2=" << val2 << std::endl;
    }

    return 0;
}

I took the "customized" my_phrase_parse from cv_and_he but it breaks compilation on the full test case that I want to get running:

template<typename T,typename R>
void testParser( R rule )
{
    for ( const auto input : std::vector< std::string >{ "5 1.0 2.0 3.0 4.0 5.0", "1 1.0", "0" , "", "2 3 ab" } )
    {
        bool rc;
        T maxValue;
        T minValue;
        auto iter( input.begin() );
        auto last( input.end() );
        std::vector< T > v;
        rc = my_phrase_parse( iter, last,
            qi::eps[
                     ph::at_c<0>(qi::_val)=std::numeric_limits<T>::max(),
                     ph::at_c<1>(qi::_val)=std::numeric_limits<T>::min()
                   ]
            >> -( qi::omit[ qi::int_] 
            >> *rule[ ph::if_(ph::at_c<0>(qi::_val)>qi::_1)[ ph::at_c<0>(qi::_val)=qi::_1 ],
                      ph::if_(ph::at_c<1>(qi::_val)<qi::_1)[ ph::at_c<1>(qi::_val)=qi::_1 ]
                    ] )
                ,ascii::space, minValue, maxValue,v ) && iter==last;
        std::cout << ( rc ? "ok :`" : "err:`" ) << input << "` -> ";
        if( rc )
        {
            std::cout << "min=" << minValue << " max=" << maxValue << "\t";
            std::copy( v.begin(), v.end(), std::ostream_iterator<T>( std::cout," " ));
        }
        else
            std::cout << *iter;
        std::cout << std::endl;
    }
}
...
    testParser<double>( qi::double_ );

Solution

  • The Phoenix placeholders that Spirit uses allow you to manipulate important information from a rule's Context. When you use them directly in an expression in a call to parse or phrase_parse there is no rule, and thus no context. Before version Boost 1.47.0 that didn't work, and in order to make this behaviour consistent a fix was applied to the single argument versions of those functions, but apparently not to the variadic ones.

    One way to sidestep this problem is creating a rule that has as attribute a fusion::vector of references to the types you use in your call to phrase_parse.

    Edit: removed "my_phrase_parse" since I'm not confident that it is correct

    #define BOOST_RESULT_OF_USE_DECLTYPE
    #define BOOST_SPIRIT_USE_PHOENIX_V3
    
    #include <boost/function.hpp>
    #include <boost/phoenix/fusion.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    #include <iostream>
    #include <string>
    
    namespace qi = boost::spirit::qi;
    namespace ph = boost::phoenix;
    namespace ascii = boost::spirit::ascii;
    namespace fusion = boost::fusion;
    
    int main( int argc, char**argv )
    {
        bool rc;
        std::string input("");
    
        //Test case works fine
        {
            auto iter( input.begin() );
            auto last( input.end() );
            int val1=33;
            rc = qi::phrase_parse( iter, last, qi::eps[ qi::_val=11 ] , 
                       ascii::space, val1 ) && iter==last;
            if( rc )
                std::cout << "val1=" << val1 << std::endl; 
        }
        //You can use a rule
        {
            auto iter( input.begin() );
            auto last( input.end() );
            int val1=33;
            int val2=0;
    
            qi::rule<decltype(iter),fusion::vector<int&, int&>(),ascii::space_type> parser=qi::eps[ ph::at_c<0>(qi::_val)=1,ph::at_c<1>(qi::_val)=2 ];
    
            rc = qi::phrase_parse( iter, last, 
                    parser,
                    ascii::space, val1,val2 ) && iter==last;
            if( rc )
                std::cout << "val1=" << val1 << 
                             " val2=" << val2 << std::endl;      
        }
    
        return 0;
    }
    

    Edit 2: Added another way to approach the problem you want to solve in your edit

    #define BOOST_RESULT_OF_USE_DECLTYPE
    #define BOOST_SPIRIT_USE_PHOENIX_V3
    
    #include <boost/spirit/include/qi_core.hpp>
    #include <boost/spirit/include/qi_omit.hpp>
    
    #include <iostream>
    #include <string>
    #include <climits>
    
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    
    template <typename T>
    struct min_max_set
    {
        min_max_set():min(std::numeric_limits<T>::max()),max(std::numeric_limits<T>::min()),set(){}
        T min;
        T max;
        std::vector<T> set;
    };
    
    namespace boost{ namespace spirit { namespace traits
    {
        template <typename T>
        struct is_container<min_max_set<T>>
            : boost::mpl::true_
        {};
    
        template <typename T>
        struct container_value<min_max_set<T>>
        {
            typedef T type;
        };
    
        template <typename T>
        struct push_back_container<min_max_set<T>,T>
        {
            static bool call(min_max_set<T>& cont, const T& val)
            {
                if(cont.min>val)
                    cont.min=val;
                if(cont.max<val)
                    cont.max=val;
                cont.set.push_back(val);
                return true;
            }
        };
    
    }}}
    
    template<typename T,typename R>
    void testParser( R rule )
    {
        for ( const auto input : std::vector< std::string >{ "5 1.0 2.0 3.0 4.0 5.0", "1 1.0", "0" , "", "2 3 ab" } )
        {
            bool rc;
    
            auto iter( input.begin() );
            auto last( input.end() );
            min_max_set<T> accum;   
    
            rc = qi::phrase_parse( iter, last,
                    qi::omit[qi::int_] >> *rule
                    ,ascii::space, accum ) && iter==last;
            std::cout << ( rc ? "ok :`" : "err:`" ) << input << "` -> ";
            if( rc )
            {
                std::cout << "min=" << accum.min << " max=" << accum.max << "\t";
                std::copy( accum.set.begin(), accum.set.end(), std::ostream_iterator<T>( std::cout," " ));
            }
            else
                std::cout << *iter;
            std::cout << std::endl;
        }
    }
    
    
    int main( int argc, char**argv )
    {
        testParser<double>(qi::double_);
        return 0;
    }