925 B
JOURNAL — phase eval
Implementation notes
evaluator.py: Walks the AST recursively. Num returns its value directly. BinOp evaluates left/right then applies op. Division by zero is intercepted and re-raised as EvalError. All results pass through _coerce() which converts whole-valued floats to int.
_coerce rule: if isinstance(value, float) and value == int(value): return int(value). This handles 4/2 = 2.0 → 2 and -4/2 = -2.0 → -2 correctly. Pure int arithmetic stays int throughout (int + int = int in Python, so no coercion needed there).
calc.py: Catches LexError, ParseError, EvalError and prints to stderr with exit 1. No traceback exposed.
test_evaluator.py: 18 tests. D1 covers all 5 mandated expressions. D2 covers true division, EvalError raise, and confirms ZeroDivisionError doesn't escape. D3 checks isinstance for int/float. D4 uses subprocess to exercise CLI end-to-end.