"""Evaluator for the arithmetic AST produced by calc.parser.""" from __future__ import annotations from calc.parser import BinOp, Num, Node, Unary class EvalError(Exception): """Raised on a runtime evaluation error (e.g. division by zero).""" def evaluate(node: Node) -> int | float: """Walk *node* and return its numeric value. Result type rule: if the mathematical result is a whole number, return int; otherwise return float. This guarantees '4/2' → 2 and '7/2' → 3.5. Raises EvalError on division by zero. """ if isinstance(node, Num): return node.value if isinstance(node, Unary): v = evaluate(node.operand) return -v if isinstance(node, BinOp): left = evaluate(node.left) right = evaluate(node.right) if node.op == '+': result = left + right elif node.op == '-': result = left - right elif node.op == '*': result = left * right elif node.op == '/': if right == 0: raise EvalError("Division by zero") result = left / right else: raise EvalError(f"Unknown operator {node.op!r}") if isinstance(result, float) and result == int(result): return int(result) return result raise EvalError(f"Unknown node type {type(node)!r}")