Handling logical operators for implementing a small query language

Olivier Trempe <[email protected]> Wed, 5 Dec 2018 16:03:22 -0500
Newsgroups gmane.comp.parsers.spirit.general
Message-ID <CAM1151ysY82D19OkX6FaeOSerq4BHqwQX4Tgsus4HXiDvWGmnQ@mail.gmail.com>
Hi,

I need to implement a query parser for a filter parameter used in a REST
query.

More specifically, I need to implement the query language defined at
https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md#97-filtering

Based on the following examples
https://stackoverflow.com/questions/24219969/implementing-operator-precedence-with-boost-spirit
https://stackoverflow.com/questions/20387627/ast-and-operator-precedence-in-rule-definition

i came up with the following grammar

template< typename Iterator, typename Skipper >
struct CFilterQueryGrammar : qi::grammar< Iterator, Skipper, FilterQuery() >
{
    CFilterQueryGrammar()
    : CFilterQueryGrammar::base_type( start, "start" )
    {
        unaryOp.add( "not", UnaryOperator::not );

        relationalOp.add( "gt", BinaryOperator::gt )
            ( "ge", BinaryOperator::ge )
            ( "lt", BinaryOperator::lt )
            ( "le", BinaryOperator::le );

        equalityOp.add( "eq", BinaryOperator::eq )
            ( "ne", BinaryOperator::ne );

        andOp.add( "and", UnaryOperator::and );

        orOp.add( "or", UnaryOperator::or );

        plainString  = qi::lexeme[ +boost::spirit::ascii::char_(
"0-9a-zA-Z" ) ];
        quotedString = qi::lexeme[ '"' >> +( boost::spirit::ascii::char_ -
'"' ) >> '"' ];
        qi::real_parser< double, qi::strict_real_policies< double > >
strict_double;

        name  = quotedString | plainString;
        value = quotedString | strict_double | qi::int_ | plainString;

        relationalExpression = ( name >> relationalOp >> value )[ qi::_val
= phx::construct< BinaryOperation >( qi::_1, qi::_2, qi::_3 ) ];
        equalityExpression   = ( name >> equalityOp >> value )[ qi::_val =
phx::construct< BinaryOperation >( qi::_1, qi::_2, qi::_3 ) ];
        andExpression        = ( andOp >> expression )[ qi::_val =
phx::construct< UnaryOperation >( qi::_1, qi::_2 ) ];
        orExpression         = ( orOp >> expression )[ qi::_val =
phx::construct< UnaryOperation >( qi::_1, qi::_2 ) ];

        binaryExpression = relationalExpression | equalityExpression;
        unaryExpression  = ( unaryOp >> expression )[ qi::_val =
phx::construct< UnaryOperation >( qi::_1, qi::_2 ) ];

        logicalExpression = andExpression | orExpression | qi::eps;

        expression            = ( parenthesesExpression | unaryExpression |
binaryExpression ) >> logicalExpression;
        parenthesesExpression = "(" >> expression >> ")";

        start = qi::eps >> +expression;

        BOOST_SPIRIT_DEBUG_NODES( ( start )( parenthesesExpression )(
expression )( binaryExpression )( orExpression )( andExpression )(
logicalExpression )( equalityExpression )( relationalExpression )(
unaryExpression )( value )( name )( quotedString )( plainString ) )
    }

    qi::symbols< char, UnaryOperator >  unaryOp;
    qi::symbols< char, BinaryOperator > relationalOp;
    qi::symbols< char, BinaryOperator > equalityOp;
    qi::symbols< char, UnaryOperator >  andOp;
    qi::symbols< char, UnaryOperator >  orOp;

    qi::rule< Iterator, Skipper, Expression() > unaryExpression;
    qi::rule< Iterator, Skipper, Expression() > relationalExpression;
    qi::rule< Iterator, Skipper, Expression() > equalityExpression;
    qi::rule< Iterator, Skipper, Expression() > andExpression;
    qi::rule< Iterator, Skipper, Expression() > orExpression;
    qi::rule< Iterator, Skipper, Expression() > logicalExpression;

    qi::rule< Iterator, Skipper, FilterQuery() > start;
    qi::rule< Iterator, Skipper, Expression() >  expression;
    qi::rule< Iterator, Skipper, Expression() >  parenthesesExpression;
    qi::rule< Iterator, Skipper, Expression() >  binaryExpression;
    qi::rule< Iterator, Skipper, Expression() >  rhsBinaryExpression;

    qi::rule< Iterator, Skipper, std::string() > plainString;
    qi::rule< Iterator, Skipper, std::string() > quotedString;
    qi::rule< Iterator, Skipper, std::string() > name;
    qi::rule< Iterator, Skipper, Literal() >     value;
};

Using this grammar to parse a matching query is successful, except for one
thing. Because the line

 expression = ( parenthesesExpression | unaryExpression | binaryExpression
) >> logicalExpression;

does not build an operation from both of its expression, only the
logicalExpression  persists.

I would like to be able to extract the operator and expression from the
logicalExpression  and inject them in a constructed BinaryOperation as the
operator and rhs expression.
Something like this:
expression = (( parenthesesExpression | unaryExpression | binaryExpression
) >> logicalExpression) [ qi::_val = phx::construct< BinaryOperation >(
qi::_1, qi::_2.op, qi::_2.rhs ) ];

I know this might not be the right way to achieve what I need to do.
I would appreciate some guidance to improve the grammar to handle logical
operators properly.

Note that I first defined my logical expressions as:
andExpression = expression >> andOp >> expression
orExpression  = expression >> orOp >> expression
but I was quickly bitten by left recursion.

Thanks!

Olivier Trempe

_______________________________________________
Spirit-general mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/spirit-general