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