## DONE Phase: parse — recursive-descent parser All DoD items self-certified (BUILD phase — deferred Adversary review). --- ### AST node shapes ``` Num(value) — numeric literal; value is int or float BinOp(op, left, right) — binary op; op in {'+', '-', '*', '/'} Unary(op, operand) — unary minus; op is '-' ``` Defined in `calc/parser.py`. `ParseError` is also defined there. --- ### D1 — precedence WHAT: `*` and `/` bind tighter than `+` and `-`. HOW: `python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))"` EXPECTED: `BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))` WHERE: calc/parser.py `_expr` / `_term` levels --- ### D2 — left associativity WHAT: Same-precedence operators associate left. HOW: ``` python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8-3-2')))" # EXPECTED: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) ``` WHERE: `_expr` / `_term` each use a while-loop (iterative, left-accumulating) --- ### D3 — parentheses WHAT: Parens override precedence. HOW: ``` python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('(1+2)*3')))" # EXPECTED: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ``` WHERE: `_primary` handles LPAREN → `_expr` → RPAREN --- ### D4 — unary minus WHAT: Leading and nested unary minus parses correctly. HOW: ``` python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5')))" # EXPECTED: Unary('-', Num(5)) ``` WHERE: `_unary` level in parser --- ### D5 — errors WHAT: Malformed input raises `ParseError` (not any other exception). HOW: `parse(tokenize('1 +'))` raises `calc.parser.ParseError` WHERE: `_Parser.parse`, `_Parser._primary`, `_Parser._expect` --- ### D6 — tests green WHAT: 34 tests total (14 lex + 20 parser), 0 failures. HOW: `python -m unittest -q` EXPECTED: `Ran 34 tests in 0.001s\nOK` WHERE: calc/test_parser.py (20 new tests)