4.2 KiB
4.2 KiB
STATUS — parse phase
DONE
All DoD gates D1–D6 implemented and self-certified (BUILD phase — DEFERRED review protocol).
What was built
calc/parser.py—ParseError,Num,BinOp,Unary,parse(tokens) -> Nodecalc/test_parser.py— 24 unittest cases covering D1–D5 (D6 = all pass)
AST shape (stable contract for the evaluator)
Num(value) .value — int or float
BinOp(op, left, right) .op — one of '+', '-', '*', '/'
.left — any Node
.right — any Node
Unary(op, operand) .op — '-'
.operand — any Node
All nodes implement __repr__ and __eq__.
D1 — Precedence ✓
1+2*3 parses as BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) — * tighter than +.
Verify:
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)))
D2 — Left associativity ✓
8-3-2 → BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))
8/4/2 → BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
Verify:
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))
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('8/4/2')))"
# Expected: BinOp('/', BinOp('/', Num(8), Num(4)), Num(2))
D3 — Parentheses ✓
(1+2)*3 → BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) — + is child of *.
Verify:
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))
D4 — Unary minus ✓
-5 → Unary('-', Num(5))
-(1+2) → Unary('-', BinOp('+', Num(1), Num(2)))
3 * -2 → BinOp('*', Num(3), Unary('-', Num(2)))
Verify:
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-5')))"
# Expected: Unary('-', Num(5))
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('-(1+2)')))"
# Expected: Unary('-', BinOp('+', Num(1), Num(2)))
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('3 * -2')))"
# Expected: BinOp('*', Num(3), Unary('-', Num(2)))
D5 — Errors ✓
Each of the following raises ParseError (not any other exception):
| Input | ParseError message |
|---|---|
'1 +' |
Unexpected end of input |
'(1' |
Expected RPAREN, got 'EOF' (None) |
'1 2' |
Unexpected token after expression: 'NUMBER' (2) |
')(' |
Unexpected token 'RPAREN' (')') |
'' |
Empty input |
Verify:
python -c "from calc.lexer import tokenize; from calc.parser import parse; parse(tokenize('1 +'))"
# Expected: raises ParseError: Unexpected end of input
python -c "from calc.lexer import tokenize; from calc.parser import parse; parse(tokenize('(1'))"
# Expected: raises ParseError: Expected RPAREN...
python -c "from calc.lexer import tokenize; from calc.parser import parse; parse(tokenize('1 2'))"
# Expected: raises ParseError: Unexpected token after expression...
python -c "from calc.lexer import tokenize; from calc.parser import parse; parse(tokenize(')('))"
# Expected: raises ParseError: Unexpected token 'RPAREN'...
python -c "from calc.lexer import tokenize; from calc.parser import parse; parse(tokenize(''))"
# Expected: raises ParseError: Empty input
D6 — Tests green ✓
44 total tests (20 lex + 24 parser), 0 failures.
Verify:
python -m unittest -q
# Expected: Ran 44 tests in 0.00xs / OK
Plan verify commands (from parse.md)
python -m unittest -q
# → Ran 44 tests in 0.001s / OK
python -c "from calc.lexer import tokenize; from calc.parser import parse; print(parse(tokenize('1+2*3')))"
# → BinOp('+', Num(1), BinOp('*', Num(2), Num(3)))
python -c "from calc.lexer import tokenize; from calc.parser import parse; parse(tokenize('1 +'))"
# → ParseError: Unexpected end of input