"""Evaluator for calc AST nodes. evaluate(node) -> int | float Result-type rule: whole-valued results are returned as int; fractional results as float. So 4/2 -> 2, 7/2 -> 3.5. """ from calc.parser import Num, BinOp, Unary class EvalError(Exception): pass def _normalize(value): """Convert a whole-valued float to int; leave everything else alone.""" if isinstance(value, float) and value.is_integer(): return int(value) return value def evaluate(node): """Walk an AST node and return its numeric value.""" if isinstance(node, Num): return _normalize(node.value) if isinstance(node, Unary): return _normalize(-evaluate(node.operand)) if isinstance(node, BinOp): left = evaluate(node.left) right = evaluate(node.right) if node.op == '+': return _normalize(left + right) if node.op == '-': return _normalize(left - right) if node.op == '*': return _normalize(left * right) if node.op == '/': if right == 0: raise EvalError('division by zero') return _normalize(left / right) raise EvalError(f'unknown node type: {type(node).__name__}')