Skip to content
Newer
Older
100644 223 lines (187 sloc) 6.42 KB
c00d076 Initial commit of open-sourced version into git (from source original…
Fluidinfo developers - ntoll authored Jul 20, 2010
1 # Copyright 2010 Fluidinfo Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you
4 # may not use this file except in compliance with the License. You
5 # may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 # implied. See the License for the specific language governing
13 # permissions and limitations under the License.
14
15 import ply.lex as lex
16 import ply.yacc as yacc
17 import re, sys
18
19 class QueryError(Exception): pass
20 class UnparseableError(QueryError): pass
21
22 class TokenError(QueryError):
23 def __init__(self, t):
24 Exception.__init__(self)
25 self.value = t.value
26 self.offset = t.lexpos
27
28 class QueryLexer(object):
29
30 # Ignore whitespace
31 t_ignore = ' \t\n'
32
33 tokens = (
34 'SCREENNAME',
35 'AND',
36 'ANDSYM',
37 'OR',
38 'ORSYM',
39 'LPAREN',
40 'RPAREN',
41 'EXCEPT',
42 'EXCEPTSYM',
43 )
44
45 reserved = (u'and', u'or', u'except')
46
47 t_LPAREN = '\('
48 t_RPAREN = '\)'
49
50 def t_EXCEPTSYM(self, t):
51 r'-'
52 t.value = 'except'
53 return t
54
55 def t_ORSYM(self, t):
56 r'\|'
57 t.value = 'or'
58 return t
59
60 def t_ANDSYM(self, t):
61 r'&'
62 t.value = 'and'
63 return t
64
65 def t_SCREENNAME(self, t):
66 r'@?\w+'
67 # The unicode conversions here probably aren't needed.
68 lower = unicode(t.value.lower())
69 if lower in self.reserved:
70 t.type = t.value.upper()
71 else:
72 t.type = 'SCREENNAME'
73 if t.value.startswith('@'):
74 t.value = unicode(t.value[1:])
75 else:
76 t.value = unicode(t.value)
77 return t
78
79 def t_error(self, t):
80 if isinstance(t, lex.LexToken):
81 raise TokenError(t)
82 else:
83 raise Exception(t)
84
85 # Build the lexer
86 def build(self, **kwargs):
87 self.lexer = lex.lex(reflags=re.UNICODE, object=self, **kwargs)
88
89
90 class Node(object):
91
92 PAREN = 0
93 BINOP = 1
94 SCREENNAME = 2
95
96 def __init__(self, type, left, right=None, detail=None):
97 self.type = type
98 self.left = left
99 self.right = right
100 self.detail = detail
101
102 def __str__(self):
103 if self.type == self.PAREN:
104 assert self.right is None
105 assert self.detail is None
106 return '(' + str(self.left) + ')'
107 elif self.type == self.BINOP:
108 return '%s %s %s' % (str(self.left), self.detail, str(self.right))
109 elif self.type == self.SCREENNAME:
110 assert self.left is None
111 assert self.right is None
112 return self.detail
113 else:
114 raise Exception('WTF? Node type is %r' % (self.type,))
115
116
117 class QueryParser(object):
118
119 precedence = (
120 ('left', 'OR'),
121 ('left', 'AND'),
122 ('left', 'EXCEPT'),
123 )
124
125 def __init__(self, tokens):
126 self.tokens = tokens
127
128 def p_query(self, p):
129 '''query : screenname_query
130 | paren_query
131 | binop_query'''
132 p[0] = p[1]
133
134 def p_query_screenname(self, p):
135 'screenname_query : SCREENNAME'
136 p[0] = Node(Node.SCREENNAME, left=None, detail=p[1])
137
138 def p_query_parens(self, p):
139 'paren_query : LPAREN query RPAREN'
140 p[0] = Node(Node.PAREN, p[2])
141
142 def p_query_binop(self, p):
143 '''binop_query : query OR query
144 | query ORSYM query
145 | query AND query
146 | query ANDSYM query
147 | query EXCEPT query
148 | query EXCEPTSYM query'''
149 p[0] = Node(Node.BINOP, left=p[1], right=p[3], detail=p[2])
150
151 def p_error(self, p):
152 if isinstance(p, lex.LexToken):
153 raise TokenError(p)
154 else:
155 raise UnparseableError(p)
156
157 def build(self, **kwargs):
158 self.yacc = yacc.yacc(**kwargs)
159
160 def parse(self, query, lexer):
161 return self.yacc.parse(input=query, lexer=lexer)
162
163
164 class ASTBuilder(object):
165 def __init__(self):
166 self.lexer = QueryLexer()
167 self.lexer.build()
168 self.parser = QueryParser(self.lexer.tokens)
169 if sys.platform.startswith('win'):
170 tmpDir = '.'
171 else:
172 tmpDir = '/tmp'
173 self.parser.build(module=self.parser, debug=False, outputdir=tmpDir)
174
175 def build(self, query):
176 try:
177 return self.parser.parse(query, self.lexer.lexer)
178 except RuntimeError:
179 raise UnparseableError()
180
181 def queryTreeToString(queryTree, fdbUsername, fdbNamespace):
182 """Return a query string that can be sent to FluidDB. Note that we turn
183 all screennames into lowercase in the complete tag names. That's
184 because that's the way we create the tags.
185 """
186 nodeType = queryTree.type
187 if nodeType == Node.PAREN:
188 assert queryTree.right is None
189 assert queryTree.detail is None
190 return u'(%s)' % queryTreeToString(
191 queryTree.left, fdbUsername, fdbNamespace)
192 elif nodeType == Node.BINOP:
193 return u'%s %s %s' % (
194 queryTreeToString(queryTree.left, fdbUsername, fdbNamespace),
195 queryTree.detail,
196 queryTreeToString(queryTree.right, fdbUsername, fdbNamespace))
197 elif nodeType == Node.SCREENNAME:
198 assert queryTree.left is None
199 assert queryTree.right is None
200 screenname = queryTree.detail
201 return u'has ' + u'/'.join(
202 [fdbUsername, fdbNamespace, screenname.lower()])
203 else:
204 raise Exception('WTF? Node type is %r' % (nodeType,))
205
206 def queryTreeExtractScreennames(queryTree, screennames):
207 """Pull all the screennames out of a query tree. Do this in a
208 case-insensitive way. Return a set of names that preserves case (first
209 casing found wins).
210 """
211 nodeType = queryTree.type
212 if nodeType == Node.PAREN:
213 queryTreeExtractScreennames(queryTree.left, screennames)
214 elif nodeType == Node.BINOP:
215 queryTreeExtractScreennames(queryTree.left, screennames)
216 queryTreeExtractScreennames(queryTree.right, screennames)
217 elif nodeType == Node.SCREENNAME:
218 screenname = queryTree.detail
219 if screenname.lower() not in screennames:
220 screennames.add(screenname)
221 else:
222 raise Exception('WTF? Node type is %r' % (nodeType,))
Something went wrong with that request. Please try again.