Question

I want to understand what exactly happens under the hood of boost::spirit::qi. Suppose we have simple parser that parses and calculates expressions consisting of numbers and add/subtract operations:

int main()
{
    std::string INPUT_DATA = "12e-1 + 3.4 - .67";
    typedef std::string::iterator iterator_type;
    iterator_type begin = std::begin(INPUT_DATA);
    iterator_type end = std::end(INPUT_DATA);

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

    auto parser = qi::double_[qi::_val = qi::_1]                      // (1)
        >> *(
                (qi::lit('+') >> qi::double_[qi::_val += qi::_1])     // (2)
                |
                (qi::lit('-') >> qi::double_[qi::_val -= qi::_1])     // (3)
            );

    double result;
    bool ok = qi::phrase_parse(begin, end, parser, ascii::space, result);

    if ( ok  && begin == end)
    {
        std::cout << "parsed, result = " << result << std::endl;
    }
    else
    {
        std::cout << "not parsed" << std::endl;
    }

    return 0;
}

How come qi::_val in semantic actions in lines (1), (2) and (3) refer to the same value? How do I achieve the same result without using boost::phoenix?

I suppose I have to write a bunch of functors that will receive the parsed value from qi::double_, but what should I do with it? How do I access synthesized value of a parser?

Was it helpful?

Solution

In addition to the exquisite low-level information provided in the comments, let me show you the ways of Spirit.

Of course, I'll end on a demo that doesn't use semantic actions. Yes, it involves more code, but it also decouples the parsing from the evaluation. This is good in more complex situations (think about parsers that backtrack).

1.

Starting from a slight simplification of your code: step 1

auto parser = 
       double_                  [_val  = _1]      // (1)
    >> *(   (lit('+') >> double_[_val += _1])     // (2)
          | (lit('-') >> double_[_val -= _1])     // (3)
        );

2.

You could, of course use regular binds to functions: step 2

void add_operand(double& lhs, double rhs) { lhs += rhs; }
void sub_operand(double& lhs, double rhs) { lhs -= rhs; }

auto parser = 
       double_                  [_val  = _1]
    >> *(   (lit('+') >> double_[bind(add_operand, _val, _1)])
          | (lit('-') >> double_[bind(sub_operand, _val, _1)])
        );

3.

Now, using BOOST_PHOENIX_ADAPT_FUNCTION makes it slightly prettier: step 3

BOOST_PHOENIX_ADAPT_FUNCTION(void, add_, add_operand, 2)
BOOST_PHOENIX_ADAPT_FUNCTION(void, sub_, sub_operand, 2)

       double_                  [_val  = _1]
    >> *(   (lit('+') >> double_[add_(_val, _1)])
          | (lit('-') >> double_[sub_(_val, _1)])
        );

4.

Or you can employ a functor: step 4

struct add_operand { 
    template<typename...> struct result { typedef void type; };
    template<typename L, typename R>
    void operator()(L& lhs, R rhs) const { lhs += rhs; } 
};

struct sub_operand { 
    template<typename...> struct result { typedef void type; };
    template<typename L, typename R>
    void operator()(L& lhs, R rhs) const { lhs -= rhs; } 
};

    auto parser = 
           double_                  [_val  = _1]
        >> *(   (lit('+') >> double_[bind(add_operand(), _val, _1)])
              | (lit('-') >> double_[bind(sub_operand(), _val, _1)])
            );

Ouch, so much for pretty.


5.

But, no worries, you can adapt those too: step 5

phx::function<add_operand> add_;
phx::function<sub_operand> sub_;

auto parser = 
       double_                  [_val  = _1]
    >> *(   (lit('+') >> double_[add_(_val, _1)])
          | (lit('-') >> double_[sub_(_val, _1)])
        );

Finally: go Pro

And finally, you could do this totally without any semantic actions, by using a simple AST:

rule<iterator_type, term<add>()     , ascii::space_type> add_term;
rule<iterator_type, term<subtract>(), ascii::space_type> sub_term;
rule<iterator_type, expression()    , ascii::space_type> parser;

add_term = '+' >> double_;
sub_term = '-' >> double_;
parser   = double_ >> *(add_term|sub_term);

Now we parse into an expression AST:

expression result;
ok = phrase_parse(begin, end, parser, ascii::space, result);

And we print the result using an eval function:

std::cout << "parsed, result = " << eval(result) << std::endl;

How does it work? See for yourself:

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

/////////////////
// AST
template <typename> struct term {
    term(double value=0) : value(value) {}
    double value;
};

using operation = boost::variant<term<struct add>, term<struct subtract> >;

struct expression
{
    double initial;
    std::vector<operation> operations;
};

BOOST_FUSION_ADAPT_STRUCT(expression, (double, initial)(std::vector<operation>,operations))
// End of AST
/////////////////

double eval(expression const& e)
{
    double result = e.initial;

    struct visitor : boost::static_visitor<> {
        double& _v; visitor(double& ref) : _v(ref) {}
        void operator()(term<add>      const& rhs) const { _v += rhs.value; }
        void operator()(term<subtract> const& rhs) const { _v -= rhs.value; }
    };

    for(auto& o : e.operations)
        boost::apply_visitor(visitor(result), o);
    return result;
}

int main()
{
    const std::string INPUT_DATA = "12e-1 + 3.4 - .67";
    typedef std::string::const_iterator iterator_type;
    iterator_type begin = std::begin(INPUT_DATA);
    iterator_type end   = std::end(INPUT_DATA);

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

    bool ok;
    expression result;
    {
        using namespace qi;

        rule<iterator_type, term<add>()     , ascii::space_type> add_term;
        rule<iterator_type, term<subtract>(), ascii::space_type> sub_term;
        rule<iterator_type, expression()    , ascii::space_type> parser;

        add_term = '+' >> double_;
        sub_term = '-' >> double_;
        parser   = double_ >> *(add_term|sub_term);

        ok = phrase_parse(begin, end, parser, ascii::space, result);
    }

    if (ok  && begin == end)
        std::cout << "parsed, result = " << eval(result) << std::endl;
    else
        std::cout << "not parsed" << std::endl;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top