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

parsing C-style relational operators with Spirit Qi


I have the following rule in my parser for parsing inequalities, which works fine:

rel = sum [ _val = _1 ]
    >> *( ('<' >> sum [_val = _val < _1])
        | ('>' >> sum [_val = _val > _1] ) );

Now I'd like to add the <= and >= operators. So I try the following:

rel = sum [ _val = _1 ]
    >> *( ('<' >> sum [_val = _val < _1])
        | ('>' >> sum [_val = _val > _1])
        | (lexeme["<="] >> sum [ _val = _val <= _1])
        | (lexeme[">="] >> sum [ _val = _val >= _1]) );

However this fails to parse expressions like x >= y, presumably because the parser can't look ahead one character to find the = after the >.

How should I modify this parser to support the <= and >= operators?

Does Spirit Qi automatically build a lexer from the lexeme expressions?

Here's the complete parser:

template <typename Iterator>
struct grammar : qi::grammar<Iterator, expression_ast(), ascii::space_type>
{

  grammar() : grammar::base_type(expr) {

    using qi::lexeme;
    using qi::double_;
    using qi::lit;
    using qi::_val;
    using qi::_1;
    using qi::_2;
    using qi::_3;

    var = lexeme[qi::alpha >> *qi::alnum];

    expr = eq [_val = _1] >> *( ('?' >> expr >> ':' >> expr ) [_val = cond(_val, _1, _2)]);

    eq = rel [ _val = _1]
       >> *( lexeme["=="] >> rel [_val = equal(_val, _1)] );

    rel = sum [ _val = _1 ]
        >> *( ('<' >> sum [_val = _val < _1])
            | ('>' >> sum [_val = _val > _1])
            | (lexeme["<="] >> sum [ _val = _val <= _1])
            | (lexeme[">="] >> sum [ _val = _val >= _1]) );

    sum = term [_val = _1]
        >> *( ('+' >> term [_val += _1] )
            | ('-' >> term [_val -= _1] ) );

    term = exp [_val = _1]
        >> *( ('*' >> exp [_val *= _1])
            | ('/' >> exp [_val /= _1]) );

    exp = factor [_val = _1]
          >> *( '^' >> factor [ _val = power(_val, _1)] );

    factor = '-' >> atom [_val = neg(_1)]
           | atom [_val = _1];

    atom = double_ [_val = _1]
         | var [_val = _1] >> -( '(' >> exprlist [_val = call(_val, _1)] >> ')' )
         | '(' >> expr [_val = _1] >> ')';

    exprlist = expr % ',';

  }

  qi::rule<Iterator, std::string(void), ascii::space_type> var;
  qi::rule<Iterator, expression_ast(), ascii::space_type> expr, eq, rel, factor, sum, term, exp, atom;
  qi::rule<Iterator, std::vector<expression_ast>(), ascii::space_type> exprlist;

};

Solution

  • According to boost::spirit documentation, the alternative parser tries its operands

    one by one on a first-match-wins basis starting from the leftmost operand

    So you could simply put your '<=' and '>=' parsers before the '<' and '>'.

    Alternatively you can make your '<' and '>' not match the '<=' and '>=' by using the not-predicate parser which are lookahead. So it would look like:

     ('<' >> !char_('=') >> sum [_val = _val < _1])