from calc.parser import Num, Unary, BinOp, Node class EvalError(Exception): pass def evaluate(node: Node): """Walk the AST and return an int or float. Result-type rule: if the result is mathematically whole-valued it is returned as int; non-whole results are returned as float. This rule is applied after every operation so intermediate values are also normalised. """ if isinstance(node, Num): return _normalise(node.value) if isinstance(node, Unary): return _normalise(-evaluate(node.operand)) if isinstance(node, BinOp): left = evaluate(node.left) right = evaluate(node.right) if node.op == '+': return _normalise(left + right) if node.op == '-': return _normalise(left - right) if node.op == '*': return _normalise(left * right) if node.op == '/': if right == 0: raise EvalError("division by zero") return _normalise(left / right) raise EvalError(f"unknown node type: {type(node).__name__}") def _normalise(value): """Return int when value is whole-valued, float otherwise.""" if isinstance(value, float) and value == int(value): return int(value) return value