Created
May 9, 2019 10:09
-
-
Save bodik/bc764ad2b73317b3936019b1e40e9908 to your computer and use it in GitHub Desktop.
parse simple search expression to sqlalchemy-filters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import logging | |
from pprint import pprint | |
from lark import Lark, Transformer | |
""" | |
Boolean expression definition widely recognizes basic building blocks as | |
'terms' and 'factors', do not confuse them with parser's terminals. Also note | |
that EBNF expresses logic operators precedence. | |
https://unnikked.ga/how-to-build-a-boolean-expression-evaluator-518e9e068a65 | |
https://cs.stackexchange.com/questions/10558/grammar-for-describing-boolean-expressions-with-and-or-and-not/10622#10622 | |
""" | |
search_grammar = r""" | |
?start: expression | |
expression: term ("OR" term)* | |
term: _factor ("AND" _factor)* | |
_factor: criteria | "(" expression ")" | |
criteria: FIELD OP VALUE | |
FIELD: /[a-z]+/ | |
OP: "==" | "!=" | |
VALUE: ESCAPED_STRING | |
%import common.ESCAPED_STRING | |
%import common.WS | |
%ignore WS | |
""" | |
class TreeToSAFilter(Transformer): | |
def expression(self, args): | |
if len(args) > 1: | |
return {"or": args} | |
else: | |
return args[0] | |
def term(self, args): | |
if len(args) > 1: | |
return {"and": args} | |
else: | |
return args[0] | |
def criteria(self, args): | |
return {'field': args[0].value, 'op': args[1].value, 'value': args[2].value} | |
search_parser = Lark(search_grammar, parser='lalr', lexer='standard', transformer=TreeToSAFilter()) | |
def test(testcase, expected): | |
output = search_parser.parse(testcase) | |
print('testcase: %s outputs %s' % (testcase, output)) | |
assert output == expected | |
if __name__ == '__main__': | |
test('a=="a"', {'field': 'a', 'op': '==', 'value': '"a"'}) | |
test('a=="a" AND b=="b"', {'and': [{'field': 'a', 'op': '==', 'value': '"a"'}, {'field': 'b', 'op': '==', 'value': '"b"'}]}) | |
test('a=="a" AND b=="b" AND c=="c"', {'and': [{'field': 'a', 'op': '==', 'value': '"a"'}, {'field': 'b', 'op': '==', 'value': '"b"'}, {'field': 'c', 'op': '==', 'value': '"c"'}]}) | |
test('a=="a" OR b=="b"', {'or': [{'field': 'a', 'op': '==', 'value': '"a"'}, {'field': 'b', 'op': '==', 'value': '"b"'}]}) | |
test('a=="a" OR b=="b" OR c=="c"', {'or': [{'field': 'a', 'op': '==', 'value': '"a"'}, {'field': 'b', 'op': '==', 'value': '"b"'}, {'field': 'c', 'op': '==', 'value': '"c"'}]}) | |
test('a=="a" OR b=="b" AND c!="c"', {'or': [{'field': 'a', 'op': '==', 'value': '"a"'}, {'and': [{'field': 'b', 'op': '==', 'value': '"b"'}, {'field': 'c', 'op': '!=', 'value': '"c"'}]}]}) | |
test('a=="a" OR b=="b" OR c!="c" AND d=="d"', {'or': [{'field': 'a', 'op': '==', 'value': '"a"'}, {'field': 'b', 'op': '==', 'value': '"b"'}, {'and': [{'field': 'c', 'op': '!=', 'value': '"c"'}, {'field': 'd', 'op': '==', 'value': '"d"'}]}]}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment