diff --git a/py_rql/grammar.py b/py_rql/grammar.py index 4947408..6013093 100644 --- a/py_rql/grammar.py +++ b/py_rql/grammar.py @@ -12,7 +12,10 @@ """ RQL_GRAMMAR = r""" -start: term? +start: _top_term? + +_top_term: term + | term_and_comma term: expr_term | logical @@ -34,7 +37,6 @@ _and: _AND? _logical_exp | term ("&" term)+ - | term (_COMMA term)+ or_op: _or | _L_BRACE _or _R_BRACE @@ -47,6 +49,11 @@ not_op: _NOT _L_BRACE expr_term _R_BRACE +// Separated rule for the "and" expression in comma style on the top level to avoid ambiguous parsing +term_and_comma: logical_and_comma -> term +logical_and_comma: and_op_comma -> logical +and_op_comma: term (_COMMA term)+ -> and_op + comp: comp_term _L_BRACE prop _COMMA val _R_BRACE | prop _EQUALITY comp_term _EQUALITY val | prop _EQUALITY val diff --git a/tests/test_parser/test_logical.py b/tests/test_parser/test_logical.py index 6474149..dbabb30 100644 --- a/tests/test_parser/test_logical.py +++ b/tests/test_parser/test_logical.py @@ -135,7 +135,6 @@ def test_logical_nesting_or(query): '((p1=v1,or(p2=v2,p3=v3),and(p4=v4,p5=v5)))', '(p1=v1,or(p2=v2,p3=v3),and(p4=v4,p5=v5))', 'and(p1=v1,or(p2=v2,p3=v3),and(p4=v4,p5=v5))', - 'p1=v1&(p2=v2|p3=v3)&(p4=v4,p5=v5)', 'p1=v1,(p2=v2|p3=v3),(p4=v4,p5=v5)', ]) def test_logical_nesting_and(query): @@ -147,20 +146,16 @@ def test_logical_nesting_and(query): assert result == { and_grammar_key: [ ('eq', 'p1', 'v1'), + { + or_grammar_key: [ + ('eq', 'p2', 'v2'), + ('eq', 'p3', 'v3'), + ], + }, { and_grammar_key: [ - { - or_grammar_key: [ - ('eq', 'p2', 'v2'), - ('eq', 'p3', 'v3'), - ], - }, - { - and_grammar_key: [ - ('eq', 'p4', 'v4'), - ('eq', 'p5', 'v5'), - ], - }, + ('eq', 'p4', 'v4'), + ('eq', 'p5', 'v5'), ], }, ], @@ -173,23 +168,26 @@ def test_and_chain(): and_grammar_key = LogicalOperators.get_grammar_key(LogicalOperators.AND) assert result == { and_grammar_key: [ - (ComparisonOperators.NE, 'p1', 'v1'), { and_grammar_key: [ + (ComparisonOperators.NE, 'p1', 'v1'), (ComparisonOperators.GE, 'p2', 'and'), - (ComparisonOperators.EQ, 'or', 'v3'), ], }, + (ComparisonOperators.EQ, 'or', 'v3'), ], } - - assert result == logical_transform('(ne(p1,v1)&(p2=ge=and,or=v3))') + assert result == logical_transform('((ne(p1,v1)&p2=ge=and),or=v3)') -def test_or_chain(): - q = '(ne(p1,v1)|(p2=ge=and;or=v3))' +@pytest.mark.parametrize('query', ( + '(ne(p1,v1)|(p2=ge=and;or=v3)|p4=v4)', + 'or(ne(p1,v1),(ge(p2,and);or=v3),p4=v4)', +)) +def test_or_chain(query): or_grammar_key = LogicalOperators.get_grammar_key(LogicalOperators.OR) - assert logical_transform(q) == { + result = logical_transform(query) + assert result == { or_grammar_key: [ (ComparisonOperators.NE, 'p1', 'v1'), { @@ -198,5 +196,6 @@ def test_or_chain(): (ComparisonOperators.EQ, 'or', 'v3'), ], }, + (ComparisonOperators.EQ, 'p4', 'v4'), ], }