Last active
August 29, 2015 14:25
-
-
Save EricWF/361704883909e29a998a to your computer and use it in GitHub Desktop.
available_
This file contains hidden or 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
| import ast | |
| import decimal | |
| class FeatureTransformer(ast.NodeTransformer): | |
| ALLOWED_NAMES = set(['True', 'False', 'None']) | |
| ALLOWED_NODE_TYPES = set([ | |
| 'Num', # allow numbers too | |
| 'Name', # an identifier... | |
| 'Load', # loads a value of a variable with given identifier | |
| 'Call', # allow defined(...) | |
| 'UnaryOp', | |
| 'Not', | |
| 'BoolOp', | |
| 'And', | |
| 'Or', | |
| 'Compare', | |
| 'Eq', | |
| 'NotEq', | |
| 'Lt', | |
| 'Gt', | |
| 'LtE', | |
| 'GtE' | |
| ]) | |
| def __init__(self, allow_names): | |
| self.m_allowed_names = dict(allow_names) | |
| def visit_Name(self, node): | |
| def replace_id(ids): | |
| return self.generic_visit( | |
| ast.copy_location( | |
| ast.parse(repr(ids[node.id]), mode='eval'), | |
| node)) | |
| if node.id in self.ALLOWED_NAMES: | |
| if node.id == 'None': | |
| node.id = 'False' | |
| return node | |
| elif node.id in self.m_allowed_names: | |
| val = self.m_allowed_names[node.id] | |
| new_node = ast.parse(repr(val), mode='eval').body | |
| return self.generic_visit( | |
| ast.copy_location(new_node, node)) | |
| else: | |
| return ast.copy_location(ast.Num(n=0), node) | |
| def visit_Call(self, node): | |
| if node.func.id != 'defined': | |
| raise RuntimeError("Invalid call expression: function %s not allowed" \ | |
| % node.func.id) | |
| if len(node.args) != 1 or len(node.keywords) != 0 \ | |
| or node.starargs or node.kwargs: | |
| raise RuntimeError("Invalid call expression: defined takes exactly one arg.") | |
| arg = node.args[0] | |
| argtype = type(arg).__name__ | |
| if type(arg).__name__ != 'Name': | |
| raise RuntimeError("Invalid call to defined!") | |
| isDef = bool(arg.id in self.m_allowed_names) | |
| return ast.copy_location(ast.Num(n=isDef), node) | |
| def generic_visit(self, node): | |
| nodetype = type(node).__name__ | |
| if nodetype not in self.ALLOWED_NODE_TYPES: | |
| raise RuntimeError("Invalid expression: %s not allowed" % nodetype) | |
| return ast.NodeTransformer.generic_visit(self, node) | |
| def evaluate(self, strExpr, debug=False): | |
| tree = ast.parse(strExpr, mode='eval') | |
| if debug: | |
| print('%r' % ast.dump(tree)) | |
| tree.body = self.visit(tree.body) | |
| clause = compile(tree, '<AST>', 'eval') | |
| result = eval(clause, dict()) | |
| return bool(result) | |
| my_source = "True, False, None, not True, not False, True and True, True and False" | |
| my_source += ", True or False, False or False" | |
| my_source += ", a and b, (b and c) or not a, c == b, c <= d" | |
| my_source += ", not a, c != b, c < b, c > b, not a, defined(a), defined(n)" | |
| my_source += ", not defined(a), not defined(n)" | |
| my_transformer = FeatureTransformer({'a': True, 'b': 0, 'c': 1, 'd': -1 }) | |
| test_list = my_source.split(',') | |
| print('%r' % my_transformer.m_allowed_names) | |
| for t in test_list: | |
| t = t.strip() | |
| result = my_transformer.evaluate(t, debug=True) | |
| print("Had = %r, Got = '%r'" % (t, result)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment