Skip to content

Instantly share code, notes, and snippets.

@nkanaev
Last active April 6, 2017 15:04
Show Gist options
  • Save nkanaev/e96a55c8327a36a925a24e95d2aeb034 to your computer and use it in GitHub Desktop.
Save nkanaev/e96a55c8327a36a925a24e95d2aeb034 to your computer and use it in GitHub Desktop.
# 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