4.0 KiB
STATUS — phase parse
AST Node Shapes
Num(value: int|float)
BinOp(op: str, left: Node, right: Node) -- op in {'+','-','*','/'}
Unary(op: str, operand: Node) -- op == '-'
All nodes are dataclasses with __repr__ returning the form above.
Exact Shape Assertions (for re-verification)
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)))
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))
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))
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)))
Gates
D1 — precedence
What: * and / bind tighter than + and -.
Command: 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)))
Observed: BinOp('+', Num(1), BinOp('*', Num(2), Num(3))) ✓
Also verified: 2*3+4 → BinOp('+', BinOp('*', Num(2), Num(3)), Num(4)) ✓
Also verified: 10-6/2 → BinOp('-', Num(10), BinOp('/', Num(6), Num(2))) ✓
D2 — left associativity
What: Same-precedence operators associate left.
Command: python -c "... print(parse(tokenize('8-3-2')))"
Expected: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2))
Observed: BinOp('-', BinOp('-', Num(8), Num(3)), Num(2)) ✓
Also verified: 8/4/2 → BinOp('/', BinOp('/', Num(8), Num(4)), Num(2)) ✓
Also verified: 1+2+3 → BinOp('+', BinOp('+', Num(1), Num(2)), Num(3)) ✓
D3 — parentheses
What: Parens override precedence.
Command: python -c "... print(parse(tokenize('(1+2)*3')))"
Expected: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3))
Observed: BinOp('*', BinOp('+', Num(1), Num(2)), Num(3)) ✓
Also verified: ((2+3)) → BinOp('+', Num(2), Num(3)) ✓
Also verified: 1*(2+3)*4 → BinOp('*', BinOp('*', Num(1), BinOp('+', Num(2), Num(3))), Num(4)) ✓
D4 — unary minus
What: Leading and nested unary minus parses. Commands and observations:
-5→Unary('-', Num(5))✓-(1+2)→Unary('-', BinOp('+', Num(1), Num(2)))✓3 * -2→BinOp('*', Num(3), Unary('-', Num(2)))✓--5→Unary('-', Unary('-', Num(5)))✓
D5 — errors
What: Malformed input raises ParseError (not any other exception).
Command:
from calc.lexer import tokenize
from calc.parser import parse, ParseError
cases = ['1 +', '(1', '1 2', ')(', '']
for src in cases:
try:
parse(tokenize(src))
print(f' FAIL no error for {src!r}')
except ParseError as e:
print(f' PASS ParseError for {src!r}: {e}')
Observed:
PASS ParseError for '1 +': unexpected end of input
PASS ParseError for '(1': expected 'RPAREN' but got 'EOF' (None)
PASS ParseError for '1 2': unexpected token 'NUMBER' (2) after expression
PASS ParseError for ')(': unexpected token 'RPAREN' (')')
PASS ParseError for '': empty input
✓
D6 — tests green
Command: python -m unittest -q
Expected: 0 failures
Observed:
----------------------------------------------------------------------
Ran 32 tests in 0.001s
OK
✓ (19 parser tests + 13 lexer tests)