Search code examples
c++boostboost-spiritboost-spirit-qiboost-phoenix

Boost spirit using local variables


I would like to define a rule based on a previously parsed value, i. e. the input string has the following structure: D <double number> or I <integer number>. I keep in a local boolean variable whether the first read character is D or I. The complete code is:

#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 spirit = boost::spirit;
namespace ascii = boost::spirit::ascii;
using boost::phoenix::ref;

template <typename Iterator>
struct x_grammar : public qi::grammar<Iterator, std::string(), ascii::space_type>
{
public:
    x_grammar() : x_grammar::base_type(start_rule, "x_grammar")
    {
        using namespace qi;
        bool is_int = false;
        start_rule = lit("I")[ref(is_int) = true] | lit("D")[ref(is_int) = false] > digit_rule;
        if(ref(is_int)()) {
            digit_rule = int_[std::cout << "int " << _1 << ".\n"];
        } else {
            digit_rule = double_[std::cout << "double " << _1 << ".\n"];
        }
    }
private:
    qi::rule<Iterator, std::string(), ascii::space_type> start_rule;
    qi::rule<Iterator, std::string(), ascii::space_type> digit_rule;
};

int main()
{
    typedef std::string::const_iterator iter;
    std::string storage("I 5");
    iter it_begin(storage.begin());
    iter it_end(storage.end());
    std::string read_data;
    using boost::spirit::ascii::space;
    x_grammar<iter> g;
    try {
        bool r = qi::phrase_parse(it_begin, it_end, g, space, read_data);
        if(r) {
            std::cout << "Pass!\n";
        } else {
            std::cout << "Fail!\n";
        }
    } catch (const qi::expectation_failure<iter>& x) {
        std::cout << "Fail!\n";
    }
    return 0;
}                                                                                                                                                    

The output is: double Pass! !! It neither recognizes the if statement, nor prints the parsed number!

Note: I know that there are other straightforward ways to parse the example above. The actual string I have to parse looks quite complicated, and this example just illustrates what I want to achieve. The general goal is to use local variables and define other rules based on those variables.

I have used 4.6.1 and Boost 1.55 versions.


Solution

  •     if(ref(is_int)()) {
    

    here you evaluate condition during construction. This is not how it works. The rule will always take the same branch.

    Instead, look at the Nabialek trick: http://boost-spirit.com/home/articles/qi-example/nabialek-trick/

    Here's the full Nabialek Trick applied to your sample Live On Coliru:

    1. you needed to make std::cout << "int" lazy actors (by wrapping at least phx::ref(std::cout) or phx::val("int") as a Phoenix Actor)

    2. you still have no use for the attribute propagation (std::string()) since it is disabled in the presence of Semantic Actions (see the previous answer). You can propagate values from Nabialek subrules, though:

    #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 spirit = boost::spirit;
    namespace ascii = boost::spirit::ascii;
    namespace phx = boost::phoenix;
    using boost::phoenix::ref;
    
    
    template <typename Iterator>
    struct x_grammar : public qi::grammar<Iterator, ascii::space_type, qi::locals<qi::rule<Iterator, ascii::space_type>*> >
    {
    public:
        x_grammar() : x_grammar::base_type(start_rule, "x_grammar")
        {
            using namespace qi;
    
            int_rule = int_   [std::cout << phx::val("int ") << _1 << ".\n"];
            dbl_rule = double_[std::cout << phx::val("double ") << _1 << ".\n"];
            subrules.add
                ("I", &int_rule)
                ("D", &dbl_rule);
    
            start_rule = subrules[_a = _1] >> lazy(*_a);
        }
    private:
        typedef qi::rule<Iterator, ascii::space_type> subrule;
    
        qi::symbols<char, subrule*> subrules;
        qi::rule<Iterator, ascii::space_type, qi::locals<subrule*> > start_rule;
        qi::rule<Iterator, ascii::space_type> int_rule, dbl_rule;
    };
    
    int main()
    {
        typedef std::string::const_iterator iter;
        std::string storage("I 5");
        iter it_begin(storage.begin());
        iter it_end(storage.end());
        using boost::spirit::ascii::space;
        x_grammar<iter> g;
        try {
            bool r = qi::phrase_parse(it_begin, it_end, g, space);
            if (r) {
                std::cout << "Pass!\n";
            }
            else {
                std::cout << "Fail!\n";
            }
        }
        catch (const qi::expectation_failure<iter>&) {
            std::cout << "Fail!\n";
        }
        return 0;
    }