""" Evaluator for arithmetic AST nodes produced by calc.parser. evaluate(node) -> int | float Result-type rule: if the computed value is a whole number (no fractional part), it is returned as int; otherwise as float. This ensures "4/2" -> 2 and "7/2" -> 3.5 without callers having to special-case anything. Division-by-zero raises EvalError, not a bare ZeroDivisionError. """ from __future__ import annotations from calc.parser import Num, BinOp, Unary, Node class EvalError(Exception): pass def evaluate(node: Node) -> int | float: result = _eval(node) if isinstance(result, float) and result == int(result): return int(result) return result def _eval(node: Node) -> int | float: if isinstance(node, Num): return node.value if isinstance(node, Unary): return -_eval(node.operand) if isinstance(node, BinOp): left = _eval(node.left) right = _eval(node.right) if node.op == '+': return left + right if node.op == '-': return left - right if node.op == '*': return left * right if node.op == '/': if right == 0: raise EvalError("division by zero") return left / right raise EvalError(f"unknown node type: {type(node).__name__}")