-# Parse query
-
-from ply import lex, yacc
-
-literals = '()'
-
-tokens = ('NAME', 'AND_OP', 'OR_OP', 'NOT_OP')
-
-t_NAME = '[a-z][a-z0-9_]*'
-
-t_AND_OP = '&'
-
-t_OR_OP = r'\|'
-
-t_NOT_OP = '!'
-
-def t_error(t):
- """Avoid warnings on stderr"""
-
-lexer = lex.lex()
-
-def p_expression_name(p):
- """expression : NAME"""
- p[0] = ('NAME', p[1])
-
-def p_expression_and(p):
- """expression : expression AND_OP expression"""
- p[0] = ('AND', p[1], p[3])
-
-def p_expression_not(p):
- """expression : NOT_OP expression"""
- p[0] = ('NOT', p[2])
-
-def p_expression_or(p):
- """expression : expression OR_OP expression"""
- p[0] = ('OR', p[1], p[3])
-
-def p_expression_parens(p):
- """expression : '(' expression ')'"""
- p[0] = ('PARENS', p[2])
-
-def p_error(p):
- """Avoid warnings on stderr"""
- yacc.restart()
-
-precedence = (
- ('left', 'OR_OP'),
- ('left', 'AND_OP'),
- ('right', 'NOT_OP'),
-)
-
-parser = yacc.yacc()
+#! /usr/bin/env python3
+# coding: koi8-r
+
+import os
+from lark import Lark, Transformer
+
+
+class TagsTransformer(Transformer):
+ def and_expression(self, items):
+ left = items[0]
+ right = items[2]
+ if len(items) > 3:
+ right = self.and_expression(items[2:])
+ return ('AND', left, right)
+
+ def or_expression(self, items):
+ left = items[0]
+ right = items[2]
+ if len(items) > 3:
+ right = self.or_expression(items[2:])
+ return ('OR', left, right)
+
+ def not_expression(self, items):
+ return ('NOT', items[1])
+
+ def expression_parens(self, items):
+ return ('PARENS', items[0])
+
+ def name(self, name):
+ return ('NAME', name[0].value)
+
+
+# cache
+_grammar = None
+
+
+def load_grammar():
+ global _grammar
+ parser_dir = os.path.dirname(__file__)
+ with open(os.path.join(parser_dir, 'grammar.ebnf'), 'rt') as grammar_file:
+ grammar_text = grammar_file.read()
+ grammar_lines = [line for line in grammar_text.splitlines()
+ if not line.startswith('#')]
+ grammar_text = '\n'.join(grammar_lines)
+ _grammar = Lark(grammar_text)
+
+
+def parse(input):
+ if _grammar is None:
+ load_grammar()
+ tree = _grammar.parse(input)
+ return TagsTransformer().transform(tree)
+
+
+if __name__ == '__main__':
+ print('----------')
+ print(parse('test'))
+ print(parse('!test'))
+ print(parse('not test'))
+ print(parse('foo or bar'))
+ print(parse('foo && bar'))
+ print(parse('Foo && bar && baz'))
+ print(parse('!foo && bar && baz'))
+ print(parse('(test)'))
+ print(parse('(foo || bar)'))
+ print(parse('(foo and !bar)'))
+ print(parse(u'(Тест или тест)'))
+ print('----------')