Last active
April 6, 2017 15:04
-
-
Save nkanaev/e96a55c8327a36a925a24e95d2aeb034 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# simple expression evaluator | |
import ast | |
import operator | |
import contextlib | |
OPERATORS = { | |
ast.Add: operator.add, | |
ast.And: operator.and_, | |
ast.Or: operator.or_, | |
ast.Lt: operator.lt, | |
ast.LtE: operator.le, | |
ast.Gt: operator.gt, | |
ast.GtE: operator.ge, | |
ast.Eq: operator.eq, | |
} | |
def op2func(node): | |
return OPERATORS[node.__class__] | |
def is_safe(attr): | |
if attr.startswith('__') and attr.endswith('__'): | |
return False | |
return attr not in { | |
'func_globals', | |
'func_code', | |
'func_closure', | |
'im_class', | |
'im_func', | |
'im_self', | |
'gi_code', | |
'gi_frame', | |
'f_locals', | |
} | |
class Interpreter: | |
available_functions = (len,) | |
def __init__(self, context): | |
if not isinstance(context, dict): | |
context = {} | |
self.context = context | |
self.functions = {f.__name__: f for f in self.available_functions} | |
def run(self, node): | |
func = getattr(self, 'on_' + node.__class__.__name__.lower(), None) | |
if not func: | |
return None | |
return func(node) | |
def on_num(self, node): | |
return node.n | |
def on_str(self, node): | |
return node.s | |
def on_compare(self, node): | |
lval = self.run(node.left) | |
out = True | |
for op, rnode in zip(node.ops, node.comparators): | |
rval = self.run(rnode) | |
out = op2func(op)(lval, rval) | |
lval = rval | |
return out | |
def on_boolop(self, node): | |
val = self.run(node.values[0]) | |
func = op2func(node.op) | |
if isinstance(node.op, ast.And): | |
for n in node.values[1:]: | |
val = func(val, self.run(n)) | |
if not val: | |
return False | |
return val | |
elif isinstance(node.op, ast.Or): | |
for n in node.values[1:]: | |
val = func(val, self.run(n)) | |
return val | |
def on_binop(self, node): | |
return op2func(node.op)(self.run(node.left), self.run(node.right)) | |
def on_index(self, node): | |
return self.run(node.value) | |
def on_subscript(self, node): | |
val = self.run(node.value) | |
slice = self.run(node.slice) | |
return val.__getitem__(slice) | |
def on_attribute(self, node): | |
if is_safe(node.attr): | |
val = self.run(node.value) | |
if isinstance(val, dict): | |
return val.get(node.attr) | |
return getattr(val, node.attr) | |
def on_name(self, node): | |
return self.context.get(node.id, self.functions.get(node.id)) | |
def on_expr(self, node): | |
return self.run(node.value) | |
def on_call(self, node): | |
func = self.run(node.func) | |
args = [self.run(a) for a in node.args] | |
return func(*args) | |
def on_module(self, node): | |
return self.run(next(iter(node.body), None)) | |
def expr(code, context=None, safe=True): | |
node = ast.parse(code) | |
i = Interpreter(context) | |
if safe: | |
with contextlib.suppress(Exception): | |
return i.run(node) | |
return i.run(node) | |
def test(): | |
assert expr('1 + 2') == 3 | |
assert expr('x + 1', {'x': 2}) == 3 | |
assert expr('1 > 3') == False | |
assert expr('3 > 1') == True | |
assert expr('3 >= 3') == True | |
assert expr('3 <= 3') == True | |
assert expr('3 == 3') == True | |
assert expr('3 == 4') == False | |
assert expr('"test" == "test"') == True | |
assert expr('"HELLO WORLD".lower()') == 'hello world' | |
assert expr('3 < 4 and 5 > 3') == True | |
assert expr('3 < 4 and 5 < 3') == False | |
assert expr('3 < 4 or 4 > 5') == True | |
assert expr('3 > 4 or 4 > 5') == False | |
class City: | |
name = 'New York' | |
population = 8500000 | |
boroughs = { | |
'Bronx': { | |
'musicians': ['Stan Getz', 'Fat Joe'] | |
} | |
} | |
ctx = {'c': City()} | |
assert expr('c.name == "New York"', ctx) == True | |
assert expr('c.population', ctx) == 8500000 | |
assert expr('c.population < 8000000', ctx) == False | |
assert expr('c.population > 8000000', ctx) == True | |
assert expr('c.boroughs.Bronx.musicians[0]', ctx) == 'Stan Getz' | |
assert expr('len(c.boroughs.Bronx.musicians)', ctx) == 2 | |
if __name__ == '__main__': | |
test() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What does expr() return if there is an exception? for example, someone tries to use an operator not in op2func