import ast import operator from utils import typename BIN_OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Mod: operator.mod } UNARY_OPS = { ast.UAdd: operator.pos, ast.USub: operator.neg } def forgiving_eval(value): try: return arithmetic_eval(value) except: return value def arithmetic_eval(s): node = ast.parse(s, mode="eval") return ast_node_eval(node.body) def ast_node_eval(node): if isinstance(node, ast.Expression): return ast_node_eval(node.body) elif isinstance(node, ast.Str): return node.s elif isinstance(node, ast.Num): return node.n elif isinstance(node, ast.BinOp): op = get_operator(node, BIN_OPS) left = ast_node_eval(node.left) right = ast_node_eval(node.right) return op(left, right) elif isinstance(node, ast.UnaryOp): op = get_operator(node, UNARY_OPS) operand = ast_node_eval(node.operand) return op(operand) else: tn = typename(node) raise ArithmeticEvalError(f"Unsupported node type {tn}") def get_operator(node, ops): op_type = type(node.op) try: op = ops[op_type] except KeyError as e: nn = typename(node) on = typename(node.op) raise ArithmeticEvalError(f"Unsupported {nn} {on}") from e else: return op class ArithmeticEvalError(Exception): pass #TODO: #print like SyntaxError: # "something with an error here" # ^ #this needs full string and offset of current node within full string stored