comparison expr.py @ 2:94a293b914af

add documentation, clean up interface slightly, tweak tests
author Ted Mielczarek <ted.mielczarek@gmail.com>
date Thu, 02 Jun 2011 07:44:25 -0400
parents c45135ec8c13
children 5ac8eed85684
comparison
equal deleted inserted replaced
1:c45135ec8c13 2:94a293b914af
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 2
3 # Implements a top-down parser/evaluator for simple boolean expressions.
4 # ideas taken from http://effbot.org/zone/simple-top-down-parsing.htm
5 #
6 # Rough grammar:
7 # expr := literal
8 # | '(' expr ')'
9 # | expr '&&' expr
10 # | expr '||' expr
11 # | expr '==' expr
12 # | expr '!=' expr
13 # literal := BOOL
14 # | INT
15 # | STRING
16 # | IDENT
17 # BOOL := true|false
18 # INT := [0-9]+
19 # STRING := "[^"]*"
20 # IDENT := [A-Za-z_]\w*
21
22 # Identifiers take their values from a mapping dictionary passed as the second
23 # argument.
24
25 __all__ = ['parse', 'ParseError']
3 import re, unittest 26 import re, unittest
4 27
5 # ideas taken from http://effbot.org/zone/simple-top-down-parsing.htm
6 # token classes 28 # token classes
7 class ident_token: 29 class ident_token:
8 def __init__(self, value): 30 def __init__(self, value):
9 self.value = value 31 self.value = value
10 def nud(self, parser): 32 def nud(self, parser):
69 return self.value 91 return self.value
70 92
71 class end_token: 93 class end_token:
72 # lowest left binding power, always ends parsing 94 # lowest left binding power, always ends parsing
73 lbp = 0 95 lbp = 0
96
97 class ParseError(Exception):
98 pass
74 99
75 class ExpressionParser(object): 100 class ExpressionParser(object):
76 def __init__(self, text, valuemapping): 101 def __init__(self, text, valuemapping):
77 """ 102 """
78 Initialize the parser with input |text|, and |valuemapping| as 103 Initialize the parser with input |text|, and |valuemapping| as
146 return left 171 return left
147 172
148 def parse(self): 173 def parse(self):
149 """ 174 """
150 Parse and return the value of the expression in the text 175 Parse and return the value of the expression in the text
151 passed to the constructor. 176 passed to the constructor. Raises a ParseError if the expression
152 """ 177 could not be parsed.
153 self.iter = self._tokenize() 178 """
154 self.token = self.iter.next() 179 try:
155 return self.expression() 180 self.iter = self._tokenize()
181 self.token = self.iter.next()
182 return self.expression()
183 except:
184 raise ParseError
185
186 def parse(text, values):
187 """
188 Parse and evaluate a boolean expression in |text|. Use |values| to look
189 up the value of identifiers referenced in the expression. Returns the final
190 value of the expression. A ParseError will be raised if parsing fails.
191 """
192 return ExpressionParser(text, values).parse()
156 193
157 class ExpressionParserUnittest(unittest.TestCase): 194 class ExpressionParserUnittest(unittest.TestCase):
158 def parse(self, text, values):
159 return ExpressionParser(text, values).parse()
160
161 def test_BasicValues(self): 195 def test_BasicValues(self):
162 self.assertEqual(1, self.parse("1", {})) 196 self.assertEqual(1, parse("1", {}))
163 self.assertEqual(100, self.parse("100", {})) 197 self.assertEqual(100, parse("100", {}))
164 self.assertEqual(True, self.parse("true", {})) 198 self.assertEqual(True, parse("true", {}))
165 self.assertEqual(False, self.parse("false", {})) 199 self.assertEqual(False, parse("false", {}))
166 self.assertEqual("", self.parse('""', {})) 200 self.assertEqual("", parse('""', {}))
167 self.assertEqual("foo bar", self.parse('"foo bar"', {})) 201 self.assertEqual("foo bar", parse('"foo bar"', {}))
168 self.assertEqual(1, self.parse("foo", {'foo':1})) 202 self.assertEqual(1, parse("foo", {'foo':1}))
169 self.assertEqual(True, self.parse("bar", {'bar':True})) 203 self.assertEqual(True, parse("bar", {'bar':True}))
170 self.assertEqual("xyz", self.parse("abc123", {'abc123':"xyz"})) 204 self.assertEqual("xyz", parse("abc123", {'abc123':"xyz"}))
171 205
172 def test_Equality(self): 206 def test_Equality(self):
173 self.assertTrue(self.parse("true == true", {})) 207 self.assertTrue(parse("true == true", {}))
174 self.assertTrue(self.parse("false == false", {})) 208 self.assertTrue(parse("false == false", {}))
175 self.assertTrue(self.parse("false == false", {})) 209 self.assertTrue(parse("false == false", {}))
176 self.assertTrue(self.parse("1 == 1", {})) 210 self.assertTrue(parse("1 == 1", {}))
177 self.assertTrue(self.parse("100 == 100", {})) 211 self.assertTrue(parse("100 == 100", {}))
178 self.assertTrue(self.parse('"some text" == "some text"', {})) 212 self.assertTrue(parse('"some text" == "some text"', {}))
179 self.assertTrue(self.parse("true != false", {})) 213 self.assertTrue(parse("true != false", {}))
180 self.assertTrue(self.parse("1 != 2", {})) 214 self.assertTrue(parse("1 != 2", {}))
181 self.assertTrue(self.parse('"text" != "other text"', {})) 215 self.assertTrue(parse('"text" != "other text"', {}))
182 self.assertTrue(self.parse("foo == true", {'foo': True})) 216 self.assertTrue(parse("foo == true", {'foo': True}))
183 self.assertTrue(self.parse("foo == 1", {'foo': 1})) 217 self.assertTrue(parse("foo == 1", {'foo': 1}))
184 self.assertTrue(self.parse('foo == "bar"', {'foo': 'bar'})) 218 self.assertTrue(parse('foo == "bar"', {'foo': 'bar'}))
185 self.assertTrue(self.parse("foo == bar", {'foo': True, 'bar': True})) 219 self.assertTrue(parse("foo == bar", {'foo': True, 'bar': True}))
186 self.assertTrue(self.parse("true == foo", {'foo': True})) 220 self.assertTrue(parse("true == foo", {'foo': True}))
187 self.assertTrue(self.parse("foo != true", {'foo': False})) 221 self.assertTrue(parse("foo != true", {'foo': False}))
188 self.assertTrue(self.parse("foo != 2", {'foo': 1})) 222 self.assertTrue(parse("foo != 2", {'foo': 1}))
189 self.assertTrue(self.parse('foo != "bar"', {'foo': 'abc'})) 223 self.assertTrue(parse('foo != "bar"', {'foo': 'abc'}))
190 self.assertTrue(self.parse("foo != bar", {'foo': True, 'bar': False})) 224 self.assertTrue(parse("foo != bar", {'foo': True, 'bar': False}))
191 self.assertTrue(self.parse("true != foo", {'foo': False})) 225 self.assertTrue(parse("true != foo", {'foo': False}))
192 226
193 def test_Conjunctions(self): 227 def test_Conjunctions(self):
194 self.assertTrue(self.parse("true && true", {})) 228 self.assertTrue(parse("true && true", {}))
195 self.assertTrue(self.parse("true || false", {})) 229 self.assertTrue(parse("true || false", {}))
196 self.assertFalse(self.parse("false || false", {})) 230 self.assertFalse(parse("false || false", {}))
197 self.assertFalse(self.parse("true && false", {})) 231 self.assertFalse(parse("true && false", {}))
232 self.assertTrue(parse("true || false && false", {}))
233
234 def test_Parens(self):
235 self.assertTrue(parse("(true)", {}))
236 self.assertEquals(10, parse("(10)", {}))
237 self.assertEquals('foo', parse('("foo")', {}))
238 self.assertEquals(1, parse("(foo)", {'foo':1}))
239 self.assertTrue(parse("(true == true)", {}))
240 self.assertTrue(parse("(true != false)", {}))
241 self.assertTrue(parse("(true && true)", {}))
242 self.assertTrue(parse("(true || false)", {}))
243 self.assertTrue(parse("(true && true || false)", {}))
244 self.assertFalse(parse("(true || false) && false", {}))
245 self.assertTrue(parse("(true || false) && true", {}))
246 self.assertTrue(parse("true && (true || false)", {}))
247 self.assertTrue(parse("true && (true || false)", {}))
198 248
199 if __name__ == '__main__': 249 if __name__ == '__main__':
200 unittest.main() 250 unittest.main()