From 3db3a1c52868d865cac4e96d6521f62feb15cf77 Mon Sep 17 00:00:00 2001 From: Oleg Broytman Date: Sat, 20 May 2017 20:13:50 +0300 Subject: [PATCH] Version 0.6: Use parsley instead of parsimonious --- ChangeLog | 6 ++++- parser/grammar.ebnf | 33 +++++++++++++++------------ parser/parser.py | 53 +++++++++++++------------------------------ parser/test_parser.py | 6 ++--- search-tags.py | 4 ++-- version | 2 +- 6 files changed, 45 insertions(+), 59 deletions(-) mode change 100644 => 100755 parser/parser.py diff --git a/ChangeLog b/ChangeLog index 8387608..9b7a3dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ -Version 0.5 (2017-04-??) +Version 0.6 (2017-05-20) + + Use parsley instead of parsimonious. + +Version 0.5 (2017-04-22) Use parsimonious instead of grako. diff --git a/parser/grammar.ebnf b/parser/grammar.ebnf index 935e6a4..4375257 100644 --- a/parser/grammar.ebnf +++ b/parser/grammar.ebnf @@ -15,34 +15,37 @@ # This is a simple version of the grammar and it allows # rather stupid expressions, like (TAG) or ((TAG)) or !(!(TAG)). -expression = or_expression / aterm_expression +expression = inner_expression:e end -> e -or_expression = aterm_expression or_op expression +inner_expression = (or_expression | aterm_expression):e -> e -and_expression = term_expression and_op aterm_expression +or_expression = aterm_expression:a or_op inner_expression:e -> ('OR', a, e) -not_expression = not_op space0 (parens_expression / name) +and_expression = term_expression:t and_op aterm_expression:a -> ('AND', +t, a) -aterm_expression = and_expression / term_expression +not_expression = not_op ws (parens_expression | name):n -> ('NOT', n) -term_expression = not_expression / parens_expression / (name space_b4letter) +aterm_expression = (and_expression | term_expression):e -> e -parens_expression = "(" space0 expression space0 ")" +term_expression = (not_expression:e -> e) | (parens_expression:p -> p) | (name:n space_b4letter -> n) -and_op = (space0 ("&&" / "&") space0) / (space0 ("AND" / "and") space_b4letter) +parens_expression = '(' ws inner_expression:e ws ')' -> ('PARENS', e) -or_op = (space0 ("||" / "|") space0) / (space0 ("OR" / "or") space_b4letter) +and_op = (ws ('&&' | '&') ws) | (ws ('AND' | 'and') space_b4letter) -not_op = (space0 "!" space0) / (space0 ("NOT" / "not") space_b4letter) +or_op = (ws ('||' | '|') ws) | (ws ('OR' | 'or') space_b4letter) -letter = ~"[a-z]"i +not_op = (ws '!' ws) | (ws ('NOT' | 'not') space_b4letter) -name = ~"[a-z][a-z0-9_]*" +name = :n -> ('NAME', n) -space_b4letter = (space1 &letter) / space0 +lletter = :l ?(l in 'abcdefghijklmnopqrstuvwxyz') -> l -space0 = ~" *" +digit = :d ?(d in '0123456789') -> d -space1 = ~" +" +lletterOrDigit = (lletter | digit):c -> c + +space_b4letter = (' '+ ~~letter) | ws # vim: set ft=text : diff --git a/parser/parser.py b/parser/parser.py old mode 100644 new mode 100755 index dc562dd..3f51d3f --- a/parser/parser.py +++ b/parser/parser.py @@ -1,5 +1,7 @@ +#! /usr/bin/env python + import os -from parsimonious import Grammar, NodeVisitor +from parsley import makeGrammar # cache @@ -11,44 +13,21 @@ def load_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 = Grammar(grammar_text) + _grammar = makeGrammar(grammar_text, {}, 'Tags') def parse(input): if _grammar is None: load_grammar() - return _grammar.parse(input) - - -def cleanup_children(visited_children): - children = [c for c in visited_children if c] - if len(children) == 1: - return children[0] - else: - return children - - -class Compiler(NodeVisitor): - def generic_visit(self, node, visited_children): - return cleanup_children(visited_children) - - def visit_or_expression(self, node, visited_children): - return ('OR', visited_children[0], visited_children[2]) - - def visit_and_expression(self, node, visited_children): - return ('AND', visited_children[0], visited_children[2]) - - def visit_not_expression(self, node, visited_children): - return ('NOT', visited_children[2]) - - def visit_parens_expression(self, node, visited_children): - return ('PARENS', visited_children[2]) - - def visit_name(self, node, visited_children): - return ('NAME', node.text) - - -def compile(tree): - if isinstance(tree, str): - tree = parse(tree) - return Compiler().visit(tree) + return _grammar(input).expression() + + +if __name__ == '__main__': + print parse('test') + print parse('!test') + print parse('not test') + print parse('foo or bar') + print parse('foo && bar') + print parse('(test)') + print parse('(foo || bar)') + print parse('(foo and !bar)') diff --git a/parser/test_parser.py b/parser/test_parser.py index 0a30eda..67c0ae3 100755 --- a/parser/test_parser.py +++ b/parser/test_parser.py @@ -1,12 +1,12 @@ #! /usr/bin/env python import unittest -from parsimonious import ParseError -from parser import compile +from ometa.runtime import ParseError +from parser import parse class TestParser(unittest.TestCase): def _parse(self, input): - return compile(input) + return parse(input) def test_02_tag(self): self.assertEqual(self._parse('xxx'), ('NAME', 'xxx')) diff --git a/search-tags.py b/search-tags.py index c091966..4f6810a 100755 --- a/search-tags.py +++ b/search-tags.py @@ -7,7 +7,7 @@ __copyright__ = "Copyright (C) 2014-2017 PhiloSoft Design" __license__ = "GNU GPL" import cgi, sys -from parsimonious import ParseError +from ometa.runtime import ParseError from html.response import redirect, response from parser import parser @@ -20,7 +20,7 @@ if not form.has_key('q'): else: q = form['q'].value try: - tree = parser.compile(q) + tree = parser.parse(q) except ParseError: status = "400 Bad request" title = "Error!" diff --git a/version b/version index 2eb3c4f..5a2a580 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5 +0.6 -- 2.39.2