3

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?

n0rd
  • 11,850
  • 5
  • 35
  • 56
  • 2
    [This answer](http://stackoverflow.com/questions/3066701/boost-spirit-semantic-action-parameters) from academicRobot is the one that helped me understand this. – llonesmiz Sep 03 '13 at 06:13
  • 2
    [Your specific example](http://coliru.stacked-crooked.com/a/961c0191df827540). – llonesmiz Sep 03 '13 at 06:32
  • @cv_and_he It would be ... customary to post that as an answer :) – sehe Sep 03 '13 at 08:03
  • @sehe It was just an example, the answer is the one linked. Feel free to put an answer yourself if you think you can give one that is more focused on this question. – llonesmiz Sep 03 '13 at 08:27

1 Answers1

5

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;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Excellent answer, very exhaustive. Maybe you should also include the "low-level" solution from academicRobot's answer in order to have all the possible approaches in a single answer. – llonesmiz Sep 03 '13 at 14:09
  • Mmm. I might just link to the 'classic' article-answer by academicRobot. Will have to be a bit later, though. Making dinner :/ – sehe Sep 03 '13 at 16:22
  • 1
    Thank you so much for taking time to write this answer. – n0rd Sep 04 '13 at 05:14