Initial revision
[python-rwhoisd.git] / rwhoisd / QueryParser.py
1
2 # queryparser.db must be set to a DB class instance.
3 db = None
4
5 # Define the Lexer for the RWhois query language
6
7 tokens = (
8     'VALUE',
9     'QUOTEDVALUE',
10     'CLASS',
11     'ATTR',
12     'AND',
13     'OR',
14     'EQ',
15     'NEQ'
16     )
17
18 # whitespace
19 t_ignore = ' \t'
20 # equality
21 t_EQ = r'='
22 # inequality
23 t_NEQ = r'!='
24
25 # for now, quoted values must have the wildcards inside the quotes.
26 # I kind of wonder if anyone would ever notice this.
27 t_QUOTEDVALUE = r'["\']\*?[^"*\n]+\*{0,2}["\']'
28
29 def t_firstvalue(t):
30     r'^\*?[^\s"\'=*]+\*{0,2}'
31
32     if db.is_objectclass(t.value):
33         t.type = 'CLASS'
34     else:
35         t.type = 'VALUE'
36     return t
37
38 def t_VALUE(t):
39     r'\*?[^\s"\'=!*]+\*{0,2}'
40
41     if t.value.upper() == 'AND':
42         t.type = 'AND'
43         t.value = t.value.upper()
44         return t
45     if t.value.upper() == 'OR':
46         t.type = 'OR'
47         t.value = t.value.upper()
48         return t
49     if db.is_attribute(t.value):
50         t.type = 'ATTR'
51     else:
52         t.type = 'VALUE'
53     return t
54
55
56 def t_error(t):
57     pass
58     # print "Illegal character '%r'" % t.value[0]
59     # t.type = 'ERR'
60     # t.skip(1)
61
62 # initalize the lexer
63 import lex
64 lex.lex()
65
66 # Define the parser for the query language
67
68 # 'value' productions are simple strings
69 # 'querystr' productions are tuples (either 1 or 3 values)
70 # 'query' productions are Query objects
71 # 'total' productions are Query objects
72
73 def p_total_class_query(t):
74     'total : CLASS query'
75
76     t[0] = t[2]
77     t[0].set_class(t[1])
78
79 def p_total_query(t):
80     'total : query'
81
82     t[0] = t[1]
83
84
85 def p_query_oper_querystr(t):
86     '''query : query AND querystr
87              | query OR  querystr'''
88
89     t[0] = t[1]
90     if t[2] == 'OR':
91         t[0].cur_clause  = [ t[3] ]
92         t[0].clauses.append(t[0].cur_clause)
93     else:
94         t[0].cur_clause.append(t[3])
95
96 def p_query_querystr(t):
97     'query : querystr'
98
99     t[0] = Query()
100     t[0].cur_clause = [ t[1] ]
101     t[0].clauses.append(t[0].cur_clause)
102
103 def p_querystr_attr_value(t):
104     '''querystr : ATTR EQ value
105                 | ATTR NEQ value'''
106
107     t[0] = (t[1], t[2], t[3])
108
109 def p_querystr_attr(t):
110     'querystr : ATTR'
111
112     t[0] = (None, '=', t[1])
113
114 def p_querystr_value(t):
115     'querystr : value'
116
117     t[0] = (None, '=', t[1])
118
119
120 def p_value(t):
121     'value : VALUE'
122
123     t[1] = t[1].strip()
124     if t[1]:
125         t[0] = t[1]
126
127 def p_quotedvalue(t):
128     'value : QUOTEDVALUE'
129
130     t[0] = t[1].strip('"')
131
132
133 def p_error(t):
134      # print "Syntax error at '%s:%s'" % (t.type, t.value)
135      raise yacc.YaccError, "Syntax error at %r" % t.value
136
137     
138 import types
139 class Query:
140     """A representation of a parsed RWhois query."""
141     
142     def __init__(self):
143         self.clauses     = []
144         self.cur_clause  = None
145         self.objectclass = None
146         self.prepared    = False
147         
148     def __str__(self):
149         self._prepare()
150         res = ''
151         for i in range(len(self.clauses)):
152             cl = self.clauses[i]
153             res += "clause %d:\n" % i
154             for item in cl:
155                 res += "  " + repr(item) + "\n"
156         return res
157
158     def __repr__(self):
159         return "<Query:\n" + str(self) + ">"
160
161     def _prepare(self):
162         """Prepare the query for use.  For now, this means propagating
163         an objectclass restriction to all query clauses."""
164         
165         if self.prepared: return
166         if self.objectclass:
167             for c in self.clauses:
168                 c.append(("class-name", "=", self.objectclass))
169
170     def clauses(self):
171         """Return the query clauses.  This is a list of AND clauses,
172         which are, in turn, lists of query terms.  Query terms are 3
173         element tuples: (attr, op, value)."""
174         
175         return self.clauses
176     
177     def set_class(self, objectclass):
178         """Set the query-wide objectclass restriction."""
179
180         # note: we don't allow the code to set this more than once,
181         # because we would have to code the removal of the previous
182         # class restriction from the query clauses, and it just isn't
183         # worth it.  Queries are built, used and thrown away.
184         assert not self.prepared
185         self.objectclass = objectclass
186         return
187
188
189 import yacc
190 import Rwhois
191
192 def get_parser():
193     """Return a parser instances.  Parser objects should not be shared
194     amongst threads."""
195
196     return yacc.yacc()
197
198 def parse(p, query):
199     """Parse a query, raising a RwhoisError in case of parse failure.
200     Returns a Query object."""
201
202     # before using any parser objects, the database backend must be
203     # set (and it shared by all parsers).
204     assert db
205     try:
206         return p.parse(query)
207     except (lex.LexError, yacc.YaccError):
208         raise Rwhois.RwhoisError, (350, "")
209
210 if __name__ == "__main__":
211     import sys
212     import MemDB
213
214     mydb = MemDB.MemDB()
215
216     print "loading schema:", sys.argv[1]
217     mydb.init_schema(sys.argv[1])
218     for data_file in sys.argv[2:]:
219         print "loading data file:", data_file
220         mydb.load_data(data_file)
221     mydb.index_data()
222
223     db = mydb
224     qp = get_parser()
225     
226     for line in sys.stdin.readlines():
227         line = line.rstrip('\n')
228         line = line.strip()
229         if not line: continue
230         print 'inputting:', `line`
231         try:
232             res = qp.parse(line)
233             print repr(res)
234         except (lex.LexError, yacc.YaccError), x:
235             print "parse error occurred:", x
236             print "query:", line
237
238
239 #         lex.input(line)
240 #         while 1:
241 #             tok = lex.token()
242 #             if not tok: break
243 #             print tok
244         
245