From 7e876c835f10e35c83400c8caa733895f406b2cc Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Wed, 14 May 2014 14:28:32 -0400 Subject: [PATCH] Add sort keys, fix a bug, add tests. --- bitshift/query/__init__.py | 4 ++-- bitshift/query/nodes.py | 35 ++++++++++++++++++++++++++++++++++- bitshift/query/tree.py | 4 ++++ test/test_query_parser.py | 21 +++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/bitshift/query/__init__.py b/bitshift/query/__init__.py index 2ed29c2..20cabd5 100644 --- a/bitshift/query/__init__.py +++ b/bitshift/query/__init__.py @@ -223,7 +223,7 @@ class _QueryParser(object): elif UnaryOp.NOT in nest: index = nest.index(UnaryOp.NOT) right = UnaryOp(UnaryOp.NOT, self._parse_nest(nest[index + 1:])) - if index > 1: + if index > 0: left = self._parse_nest(nest[:index]) return BinaryOp(left, BinaryOp.AND, right) return right @@ -240,7 +240,7 @@ class _QueryParser(object): if isinstance(node, BinaryOp): self._balance_tree(node.left) self._balance_tree(node.right) - if repr(node.right) < repr(node.left): + if node.right.sortkey() < node.left.sortkey(): node.left, node.right = node.right, node.left elif isinstance(node, UnaryOp): self._balance_tree(node.node) diff --git a/bitshift/query/nodes.py b/bitshift/query/nodes.py index 7b12a33..b959118 100644 --- a/bitshift/query/nodes.py +++ b/bitshift/query/nodes.py @@ -10,7 +10,10 @@ class _Node(object): a :py:class:`~.Language` node represents a constraint where only codelets of a specific language are selected. """ - pass + + def sortkey(self): + """Return a string sort key for the node.""" + return "" class _Literal(object): @@ -33,6 +36,9 @@ class String(_Literal): def __repr__(self): return "String({0!r})".format(self.string) + def sortkey(self): + return self.string + class Regex(_Literal): """Represents a regular expression literal.""" @@ -46,6 +52,9 @@ class Regex(_Literal): def __repr__(self): return "Regex({0!r})".format(self.regex) + def sortkey(self): + return self.regex + class Text(_Node): """Represents a text node. @@ -63,6 +72,9 @@ class Text(_Node): def __repr__(self): return "Text({0})".format(self.text) + def sortkey(self): + return self.text.sortkey() + class Language(_Node): """Represents a language node. @@ -79,6 +91,9 @@ class Language(_Node): def __repr__(self): return "Language({0})".format(LANGS[self.lang]) + def sortkey(self): + return LANGS[self.lang] + class Author(_Node): """Represents a author node. @@ -87,11 +102,17 @@ class Author(_Node): """ def __init__(self, name): + """ + :type name: :py:class:`_Literal` + """ self.name = name def __repr__(self): return "Author({0})".format(self.name) + def sortkey(self): + return self.name.sortkey() + class Date(_Node): """Represents a date node. @@ -120,6 +141,9 @@ class Date(_Node): tm = "Date({0}, {1}, {2})" return tm.format(types[self.type], relations[self.relation], self.date) + def sortkey(self): + return self.date.strftime("%Y%m%d%H%M%S") + class Symbol(_Node): """Represents a symbol node. @@ -144,6 +168,9 @@ class Symbol(_Node): self.CLASS: "CLASS", self.VARIABLE: "VARIABLE"} return "Symbol({0}, {1})".format(types[self.type], self.name) + def sortkey(self): + return self.name.sortkey() + class BinaryOp(_Node): """Represents a relationship between two nodes: ``and``, ``or``.""" @@ -160,6 +187,9 @@ class BinaryOp(_Node): tmpl = "BinaryOp({0}, {1}, {2})" return tmpl.format(self.left, ops[self.op], self.right) + def sortkey(self): + return self.left.sortkey() + self.right.sortkey() + class UnaryOp(_Node): """Represents a transformation applied to one node: ``not``.""" @@ -172,3 +202,6 @@ class UnaryOp(_Node): def __repr__(self): ops = {self.NOT: "NOT"} return "UnaryOp({0}, {1})".format(ops[self.op], self.node) + + def sortkey(self): + return self.node.sortkey() diff --git a/bitshift/query/tree.py b/bitshift/query/tree.py index 4c1b463..543889e 100644 --- a/bitshift/query/tree.py +++ b/bitshift/query/tree.py @@ -9,6 +9,10 @@ class Tree(object): def __repr__(self): return "Tree({0})".format(self._root) + def sortkey(self): + """Return a string sort key for the query tree.""" + return self._root.sortkey() + def serialize(self): """Create a string representation of the query for caching. diff --git a/test/test_query_parser.py b/test/test_query_parser.py index 6b33813..24941c0 100644 --- a/test/test_query_parser.py +++ b/test/test_query_parser.py @@ -31,6 +31,27 @@ TESTS = [ ("class:FooBar", "Tree(Symbol(CLASS, String(u'FooBar')))"), ("var:foobar", "Tree(Symbol(VARIABLE, String(u'foobar')))"), ("var:r:foobar", "Tree(Symbol(VARIABLE, Regex(u'foobar')))"), + + # Composition + ("(a and b) or (c and d)", ", ".join([ + "Tree(BinaryOp(BinaryOp(Text(String(u'a'))", "AND", + "Text(String(u'b')))", "OR", "BinaryOp(Text(String(u'c'))", "AND", + "Text(String(u'd')))))"])), + ("a and b or c and d", ", ".join([ + "Tree(BinaryOp(BinaryOp(Text(String(u'a'))", "AND", + "Text(String(u'b')))", "OR", "BinaryOp(Text(String(u'c'))", "AND", + "Text(String(u'd')))))"])), + ("a and b or c or d", ", ".join([ + "Tree(BinaryOp(BinaryOp(Text(String(u'a'))", "AND", + "Text(String(u'b')))", "OR", "BinaryOp(Text(String(u'c'))", "OR", + "Text(String(u'd')))))"])), + ("a and (b or c or d)", ", ".join([ + "Tree(BinaryOp(Text(String(u'a'))", "AND", + "BinaryOp(Text(String(u'b'))", "OR", "BinaryOp(Text(String(u'c'))", "OR", + "Text(String(u'd'))))))"])), + ("a not b", ", ".join([ + "Tree(BinaryOp(Text(String(u'a'))", "AND", "UnaryOp(NOT", + "Text(String(u'b')))))"])), ] class TestQueryParser(unittest.TestCase):