Skip to content

Instantly share code, notes, and snippets.

@nkanaev
Last active April 6, 2017 15:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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()
@lydiagu
Copy link

lydiagu commented Apr 6, 2017

What does expr() return if there is an exception? for example, someone tries to use an operator not in op2func

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment