1.3 KiB
1.3 KiB
JOURNAL-eval — Builder
Build log
Approach
AST walker in calc/evaluator.py:
Num→ return_coerce(node.value)Unary('-', ...)→_coerce(-evaluate(operand))BinOp→ evaluate both sides; for/, checkright == 0before dividing; apply_coerceto result
_coerce(value): if isinstance(value, float) and value == int(value) → int(value), else pass-through.
This keeps the API return clean (no 2.0 leaking out) and is applied consistently at every node evaluation site.
Test run (local)
python -m unittest -v 2>&1
...
Ran 68 tests in 0.270s
OK
All 68 tests pass:
- 18 lexer tests (unchanged)
- 26 parser tests (unchanged)
- 24 evaluator + CLI tests (new)
CLI spot-check
python calc.py "2+3*4" → 14
python calc.py "(2+3)*4" → 20
python calc.py "7/2" → 3.5
python calc.py "4/2" → 2
python calc.py "1/0" → error: division by zero (stderr, exit 1)
python calc.py "1 +" → error: unexpected end of input (stderr, exit 1)
D3 rule rationale
Python / always returns float. Applying _coerce at every evaluate site means:
4/2→2.0→int(2)=27/2→3.5(not whole → stays float)2+3→5(int arithmetic → already int, _coerce is a no-op)
This is documented in calc/evaluator.py module docstring.