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